third party library: apache-commons-math

Change-Id: I52a325624a7f0dd652b362a9840626d6d9f3c42b
diff --git a/Android.mk b/Android.mk
new file mode 100644
index 0000000..df7b569
--- /dev/null
+++ b/Android.mk
@@ -0,0 +1,26 @@
+#
+# Copyright (C) 2011 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+LOCAL_PATH := $(call my-dir)
+
+apache-commons-math_src_files := $(call all-java-files-under,src/main/java)
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := apache-commons-math
+LOCAL_MODULE_TAGS := optional
+LOCAL_SRC_FILES := $(apache-commons-math_src_files)
+LOCAL_JAVACFLAGS := -encoding UTF-8
+LOCAL_SDK_VERSION := current
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..275e288
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,376 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+
+
+APACHE COMMONS MATH DERIVATIVE WORKS: 
+
+The Apache commons-math library includes a number of subcomponents
+whose implementation is derived from original sources written
+in C or Fortran.  License terms of the original sources
+are reproduced below.
+
+===============================================================================
+For the lmder, lmpar and qrsolv Fortran routine from minpack and translated in
+the LevenbergMarquardtOptimizer class in package
+org.apache.commons.math.optimization.general 
+Original source copyright and license statement:
+
+Minpack Copyright Notice (1999) University of Chicago.  All rights reserved
+
+Redistribution and use in source and binary forms, with or
+without modification, are permitted provided that the
+following conditions are met:
+
+1. Redistributions of source code must retain the above
+copyright notice, this list of conditions and the following
+disclaimer.
+
+2. 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.
+
+3. The end-user documentation included with the
+redistribution, if any, must include the following
+acknowledgment:
+
+   "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.
+
+4. 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.
+
+5. 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.
+===============================================================================
+
+Copyright and license statement for the odex Fortran routine developed by
+E. Hairer and G. Wanner and translated in GraggBulirschStoerIntegrator class
+in package org.apache.commons.math.ode.nonstiff:
+
+
+Copyright (c) 2004, Ernst Hairer
+
+Redistribution and use in source and binary forms, with or without 
+modification, are permitted provided that the following conditions are 
+met:
+
+- Redistributions of source code must retain the above copyright 
+notice, this list of conditions and the following disclaimer.
+
+- 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.
+
+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.
+===============================================================================
+
+Copyright and license statement for the original lapack fortran routines
+translated in EigenDecompositionImpl class in package
+org.apache.commons.math.linear:
+
+Copyright (c) 1992-2008 The University of Tennessee.  All rights reserved.
+
+$COPYRIGHT$
+
+Additional copyrights may follow
+
+$HEADER$
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+- Redistributions of source code must retain the above copyright
+  notice, this list of conditions and the following disclaimer. 
+  
+- Redistributions in binary form must reproduce the above copyright
+  notice, this list of conditions and the following disclaimer listed
+  in this license in the documentation and/or other materials
+  provided with the distribution.
+  
+- Neither the name of the copyright holders nor the names of its
+  contributors may be used to endorse or promote products derived from
+  this software without specific prior written permission.
+  
+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. 
+===============================================================================
+
+Copyright and license statement for the original Mersenne twister C
+routines translated in MersenneTwister class in package 
+org.apache.commons.math.random:
+
+   Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura,
+   All rights reserved.                          
+
+   Redistribution and use in source and binary forms, with or without
+   modification, are permitted provided that the following conditions
+   are met:
+
+     1. Redistributions of source code must retain the above copyright
+        notice, this list of conditions and the following disclaimer.
+
+     2. 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.
+
+     3. The names of its contributors may not be used to endorse or promote 
+        products derived from this software without specific prior written 
+        permission.
+
+   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.
+
diff --git a/NOTICE b/NOTICE
new file mode 100644
index 0000000..977df71
--- /dev/null
+++ b/NOTICE
@@ -0,0 +1,56 @@
+Apache Commons Math
+Copyright 2001-2011 The Apache Software Foundation
+
+This product includes software developed by
+The Apache Software Foundation (http://www.apache.org/).
+
+===============================================================================
+
+The BracketFinder (package org.apache.commons.math.optimization.univariate)
+and PowellOptimizer (package org.apache.commons.math.optimization.general)
+classes are based on the Python code in module "optimize.py" (version 0.5)
+developed by Travis E. Oliphant for the SciPy library (http://www.scipy.org/)
+Copyright © 2003-2009 SciPy Developers.
+===============================================================================
+
+The LinearConstraint, LinearObjectiveFunction, LinearOptimizer,
+RelationShip, SimplexSolver and SimplexTableau classes in package
+org.apache.commons.math.optimization.linear include software developed by
+Benjamin McCann (http://www.benmccann.com) and distributed with
+the following copyright: Copyright 2009 Google Inc.
+===============================================================================
+
+This product includes software developed by the
+University of Chicago, as Operator of Argonne National
+Laboratory.
+The LevenbergMarquardtOptimizer class in package
+org.apache.commons.math.optimization.general includes software
+translated from the lmder, lmpar and qrsolv Fortran routines
+from the Minpack package
+Minpack Copyright Notice (1999) University of Chicago.  All rights reserved
+===============================================================================
+
+The GraggBulirschStoerIntegrator class in package
+org.apache.commons.math.ode.nonstiff includes software translated
+from the odex Fortran routine developed by E. Hairer and G. Wanner.
+Original source copyright:
+Copyright (c) 2004, Ernst Hairer
+===============================================================================
+
+The EigenDecompositionImpl class in package
+org.apache.commons.math.linear includes software translated
+from some LAPACK Fortran routines.  Original source copyright:
+Copyright (c) 1992-2008 The University of Tennessee.  All rights reserved.
+===============================================================================
+
+The MersenneTwister class in package org.apache.commons.math.random
+includes software translated from the 2002-01-26 version of
+the Mersenne-Twister generator written in C by Makoto Matsumoto and Takuji
+Nishimura. Original source copyright:
+Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura,
+All rights reserved
+===============================================================================
+
+The complete text of licenses and disclaimers associated with the the original
+sources enumerated above at the time of code translation are in the LICENSE.txt
+file.
diff --git a/src/main/java/org/apache/commons/math/ArgumentOutsideDomainException.java b/src/main/java/org/apache/commons/math/ArgumentOutsideDomainException.java
new file mode 100644
index 0000000..a43c6b9
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ArgumentOutsideDomainException.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.math;
+
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * Error thrown when a method is called with an out of bounds argument.
+ *
+ * @since 1.2
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ */
+public class ArgumentOutsideDomainException extends FunctionEvaluationException {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -4965972841162580234L;
+
+    /**
+     * Constructs an exception with specified formatted detail message.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param argument  the failing function argument
+     * @param lower lower bound of the domain
+     * @param upper upper bound of the domain
+     */
+    public ArgumentOutsideDomainException(double argument, double lower, double upper) {
+        super(argument, LocalizedFormats.ARGUMENT_OUTSIDE_DOMAIN, argument, lower, upper);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/ConvergenceException.java b/src/main/java/org/apache/commons/math/ConvergenceException.java
new file mode 100644
index 0000000..0cc3959
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ConvergenceException.java
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math;
+
+import org.apache.commons.math.exception.util.DummyLocalizable;
+import org.apache.commons.math.exception.util.Localizable;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * Error thrown when a numerical computation can not be performed because the
+ * numerical result failed to converge to a finite value.
+ *
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ */
+public class ConvergenceException extends MathException {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -1111352570797662604L;
+
+    /**
+     * Default constructor.
+     */
+    public ConvergenceException() {
+        super(LocalizedFormats.CONVERGENCE_FAILED);
+    }
+
+    /**
+     * Constructs an exception with specified formatted detail message.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @since 1.2
+     * @deprecated as of 2.2 replaced by {@link #ConvergenceException(Localizable, Object...)}
+     */
+    @Deprecated
+    public ConvergenceException(String pattern, Object ... arguments) {
+        this(new DummyLocalizable(pattern), arguments);
+    }
+
+    /**
+     * Constructs an exception with specified formatted detail message.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @since 2.2
+     */
+    public ConvergenceException(Localizable pattern, Object ... arguments) {
+        super(pattern, arguments);
+    }
+
+    /**
+     * Create an exception with a given root cause.
+     * @param cause  the exception or error that caused this exception to be thrown
+     */
+    public ConvergenceException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs an exception with specified formatted detail message and root cause.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param cause  the exception or error that caused this exception to be thrown
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @since 1.2
+     * @deprecated as of 2.2 replaced by {@link #ConvergenceException(Throwable, Localizable, Object...)}
+     */
+    @Deprecated
+    public ConvergenceException(Throwable cause, String pattern, Object ... arguments) {
+        this(cause, new DummyLocalizable(pattern), arguments);
+    }
+
+    /**
+     * Constructs an exception with specified formatted detail message and root cause.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param cause  the exception or error that caused this exception to be thrown
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @since 2.2
+     */
+    public ConvergenceException(Throwable cause, Localizable pattern, Object ... arguments) {
+        super(cause, pattern, arguments);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/ConvergingAlgorithm.java b/src/main/java/org/apache/commons/math/ConvergingAlgorithm.java
new file mode 100644
index 0000000..128f169
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ConvergingAlgorithm.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.math;
+
+
+/**
+ * Interface for algorithms handling convergence settings.
+ * <p>
+ * This interface only deals with convergence parameters setting, not
+ * execution of the algorithms per se.
+ * </p>
+ * @see ConvergenceException
+ * @version $Revision: 1042336 $ $Date: 2010-12-05 13:40:48 +0100 (dim. 05 déc. 2010) $
+ * @since 2.0
+ * @deprecated in 2.2 (to be removed in 3.0). The concept of "iteration" will
+ * be moved to a new {@code IterativeAlgorithm}. The concept of "accuracy" is
+ * currently is also contained in {@link org.apache.commons.math.optimization.SimpleRealPointChecker}
+ * and similar classes.
+ */
+@Deprecated
+public interface ConvergingAlgorithm {
+
+    /**
+     * Set the upper limit for the number of iterations.
+     * <p>
+     * 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.</p>
+     * <p>
+     * A {@link ConvergenceException} will be thrown if this number
+     * is exceeded.</p>
+     *
+     * @param count maximum number of iterations
+     */
+    void setMaximalIterationCount(int count);
+
+    /**
+     * Get the upper limit for the number of iterations.
+     *
+     * @return the actual upper limit
+     */
+    int getMaximalIterationCount();
+
+    /**
+     * Reset the upper limit for the number of iterations to the default.
+     * <p>
+     * The default value is supplied by the algorithm implementation.</p>
+     *
+     * @see #setMaximalIterationCount(int)
+     */
+    void resetMaximalIterationCount();
+
+    /**
+     * Set the absolute accuracy.
+     * <p>
+     * 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.</p>
+     * <p>
+     * Algorithms are advised to do a plausibility check with the relative
+     * accuracy, but clients should not rely on this.</p>
+     *
+     * @param accuracy the accuracy.
+     * @throws IllegalArgumentException if the accuracy can't be achieved by
+     * the solver or is otherwise deemed unreasonable.
+     */
+    void setAbsoluteAccuracy(double accuracy);
+
+    /**
+     * Get the actual absolute accuracy.
+     *
+     * @return the accuracy
+     */
+    double getAbsoluteAccuracy();
+
+    /**
+     * Reset the absolute accuracy to the default.
+     * <p>
+     * The default value is provided by the algorithm implementation.</p>
+     */
+    void resetAbsoluteAccuracy();
+
+    /**
+     * Set the relative accuracy.
+     * <p>
+     * This is used to stop iterations if the absolute accuracy can't be
+     * achieved due to large values or short mantissa length.</p>
+     * <p>
+     * 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.math.util.MathUtils#SAFE_MIN MathUtils.SAFE_MIN}.</p>
+     *
+     * @param accuracy the relative accuracy.
+     * @throws IllegalArgumentException if the accuracy can't be achieved by
+     *  the algorithm or is otherwise deemed unreasonable.
+     */
+    void setRelativeAccuracy(double accuracy);
+
+    /**
+     * Get the actual relative accuracy.
+     * @return the accuracy
+     */
+    double getRelativeAccuracy();
+
+    /**
+     * Reset the relative accuracy to the default.
+     * The default value is provided by the algorithm implementation.
+     */
+    void resetRelativeAccuracy();
+
+    /**
+     * Get the number of iterations in the last run of the algorithm.
+     * <p>
+     * This is mainly meant for testing purposes. It may occasionally
+     * help track down performance problems: if the iteration count
+     * is notoriously high, check whether the problem is evaluated
+     * properly, and whether another algorithm is more amenable to the
+     * problem.</p>
+     *
+     * @return the last iteration count.
+     * @throws IllegalStateException if there is no result available, either
+     * because no result was yet computed or the last attempt failed.
+     */
+    int getIterationCount();
+
+}
diff --git a/src/main/java/org/apache/commons/math/ConvergingAlgorithmImpl.java b/src/main/java/org/apache/commons/math/ConvergingAlgorithmImpl.java
new file mode 100644
index 0000000..e15b9a9
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ConvergingAlgorithmImpl.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.math;
+
+/**
+ * Provide a default implementation for several functions useful to generic
+ * converging algorithms.
+ *
+ * @version $Revision: 1062691 $ $Date: 2011-01-24 10:12:47 +0100 (lun. 24 janv. 2011) $
+ * @since 2.0
+ * @deprecated in 2.2 (to be removed in 3.0).
+ */
+public abstract class ConvergingAlgorithmImpl implements ConvergingAlgorithm {
+
+    /** Maximum absolute error. */
+    protected double absoluteAccuracy;
+
+    /** Maximum relative error. */
+    protected double relativeAccuracy;
+
+    /** Maximum number of iterations. */
+    protected int maximalIterationCount;
+
+    /** Default maximum absolute error. */
+    protected double defaultAbsoluteAccuracy;
+
+    /** Default maximum relative error. */
+    protected double defaultRelativeAccuracy;
+
+    /** Default maximum number of iterations. */
+    protected int defaultMaximalIterationCount;
+
+    /** The last iteration count. */
+    protected int iterationCount;
+
+    /**
+     * Construct an algorithm with given iteration count and accuracy.
+     *
+     * @param defaultAbsoluteAccuracy maximum absolute error
+     * @param defaultMaximalIterationCount maximum number of iterations
+     * @throws IllegalArgumentException if f is null or the
+     * defaultAbsoluteAccuracy is not valid
+     * @deprecated in 2.2. Derived classes should use the "setter" methods
+     * in order to assign meaningful values to all the instances variables.
+     */
+    @Deprecated
+    protected ConvergingAlgorithmImpl(final int defaultMaximalIterationCount,
+                                      final double defaultAbsoluteAccuracy) {
+        this.defaultAbsoluteAccuracy = defaultAbsoluteAccuracy;
+        this.defaultRelativeAccuracy = 1.0e-14;
+        this.absoluteAccuracy = defaultAbsoluteAccuracy;
+        this.relativeAccuracy = defaultRelativeAccuracy;
+        this.defaultMaximalIterationCount = defaultMaximalIterationCount;
+        this.maximalIterationCount = defaultMaximalIterationCount;
+        this.iterationCount = 0;
+    }
+
+    /**
+     * Default constructor.
+     *
+     * @since 2.2
+     * @deprecated in 2.2 (to be removed as soon as the single non-default one
+     * has been removed).
+     */
+    @Deprecated
+    protected ConvergingAlgorithmImpl() {}
+
+    /** {@inheritDoc} */
+    public int getIterationCount() {
+        return iterationCount;
+    }
+
+    /** {@inheritDoc} */
+    public void setAbsoluteAccuracy(double accuracy) {
+        absoluteAccuracy = accuracy;
+    }
+
+    /** {@inheritDoc} */
+    public double getAbsoluteAccuracy() {
+        return absoluteAccuracy;
+    }
+
+    /** {@inheritDoc} */
+    public void resetAbsoluteAccuracy() {
+        absoluteAccuracy = defaultAbsoluteAccuracy;
+    }
+
+    /** {@inheritDoc} */
+    public void setMaximalIterationCount(int count) {
+        maximalIterationCount = count;
+    }
+
+    /** {@inheritDoc} */
+    public int getMaximalIterationCount() {
+        return maximalIterationCount;
+    }
+
+    /** {@inheritDoc} */
+    public void resetMaximalIterationCount() {
+        maximalIterationCount = defaultMaximalIterationCount;
+    }
+
+    /** {@inheritDoc} */
+    public void setRelativeAccuracy(double accuracy) {
+        relativeAccuracy = accuracy;
+    }
+
+    /** {@inheritDoc} */
+    public double getRelativeAccuracy() {
+        return relativeAccuracy;
+    }
+
+    /** {@inheritDoc} */
+    public void resetRelativeAccuracy() {
+        relativeAccuracy = defaultRelativeAccuracy;
+    }
+
+    /**
+     * Reset the iterations counter to 0.
+     *
+     * @since 2.2
+     */
+    protected void resetIterationsCounter() {
+        iterationCount = 0;
+    }
+
+    /**
+     * Increment the iterations counter by 1.
+     *
+     * @throws MaxIterationsExceededException if the maximal number
+     * of iterations is exceeded.
+     * @since 2.2
+     */
+    protected void incrementIterationsCounter()
+        throws MaxIterationsExceededException {
+        if (++iterationCount > maximalIterationCount) {
+            throw new MaxIterationsExceededException(maximalIterationCount);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/DimensionMismatchException.java b/src/main/java/org/apache/commons/math/DimensionMismatchException.java
new file mode 100644
index 0000000..b1c3dc4
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/DimensionMismatchException.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.math;
+
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * Error thrown when two dimensions differ.
+ *
+ * @since 1.2
+ * @version $Revision: 1061778 $ $Date: 2011-01-21 13:12:39 +0100 (ven. 21 janv. 2011) $
+ * @deprecated in 2.2 (to be removed in 3.0). Please use its equivalent from package
+ * {@link org.apache.commons.math.exception}.
+ */
+public class DimensionMismatchException extends MathException {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -1316089546353786411L;
+
+    /** First dimension. */
+    private final int dimension1;
+
+    /** Second dimension. */
+    private final int dimension2;
+
+    /**
+     * Construct an exception from the mismatched dimensions
+     * @param dimension1 first dimension
+     * @param dimension2 second dimension
+     */
+    public DimensionMismatchException(final int dimension1, final int dimension2) {
+        super(LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, dimension1, dimension2);
+        this.dimension1 = dimension1;
+        this.dimension2 = dimension2;
+    }
+
+    /**
+     * Get the first dimension
+     * @return first dimension
+     */
+    public int getDimension1() {
+        return dimension1;
+    }
+
+    /**
+     * Get the second dimension
+     * @return second dimension
+     */
+    public int getDimension2() {
+        return dimension2;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/DuplicateSampleAbscissaException.java b/src/main/java/org/apache/commons/math/DuplicateSampleAbscissaException.java
new file mode 100644
index 0000000..125be75
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/DuplicateSampleAbscissaException.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.math;
+
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * Exception thrown when a sample contains several entries at the same abscissa.
+ *
+ * @since 1.2
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ */
+public class DuplicateSampleAbscissaException extends MathException  {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -2271007547170169872L;
+
+    /**
+     * Construct an exception indicating the duplicate abscissa.
+     * @param abscissa duplicate abscissa
+     * @param i1 index of one entry having the duplicate abscissa
+     * @param i2 index of another entry having the duplicate abscissa
+     */
+    public DuplicateSampleAbscissaException(double abscissa, int i1, int i2) {
+        super(LocalizedFormats.DUPLICATED_ABSCISSA,
+              abscissa, i1, i2);
+    }
+
+    /**
+     * Get the duplicate abscissa.
+     * @return duplicate abscissa
+     */
+    public double getDuplicateAbscissa() {
+        return ((Double) getArguments()[0]).doubleValue();
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/Field.java b/src/main/java/org/apache/commons/math/Field.java
new file mode 100644
index 0000000..7e69a8d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/Field.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.math;
+
+/**
+ * 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
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ * @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();
+
+}
diff --git a/src/main/java/org/apache/commons/math/FieldElement.java b/src/main/java/org/apache/commons/math/FieldElement.java
new file mode 100644
index 0000000..29757d4
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/FieldElement.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.math;
+
+
+/**
+ * Interface representing <a href="http://mathworld.wolfram.com/Field.html">field</a> elements.
+ * @param <T> the type of the field elements
+ * @see Field
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ * @since 2.0
+ */
+public interface FieldElement<T> {
+
+    /** Compute this + a.
+     * @param a element to add
+     * @return a new element representing this + a
+     */
+    T add(T a);
+
+    /** Compute this - a.
+     * @param a element to subtract
+     * @return a new element representing this - a
+     */
+    T subtract(T a);
+
+    /** Compute this &times; a.
+     * @param a element to multiply
+     * @return a new element representing this &times; a
+     */
+    T multiply(T a);
+
+    /** Compute this &divide; a.
+     * @param a element to add
+     * @return a new element representing this &divide; a
+     * @exception ArithmeticException if a is the zero of the
+     * additive operation (i.e. additive identity)
+     */
+    T divide(T a) throws ArithmeticException;
+
+    /** 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/math/FunctionEvaluationException.java b/src/main/java/org/apache/commons/math/FunctionEvaluationException.java
new file mode 100644
index 0000000..5d54d29
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/FunctionEvaluationException.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.math;
+
+import org.apache.commons.math.exception.util.DummyLocalizable;
+import org.apache.commons.math.exception.util.Localizable;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.linear.ArrayRealVector;
+
+/**
+ * Exception thrown when an error occurs evaluating a function.
+ * <p>
+ * Maintains an <code>argument</code> property holding the input value that
+ * caused the function evaluation to fail.
+ *
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ */
+public class FunctionEvaluationException extends MathException  {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 1384427981840836868L;
+
+    /** Argument causing function evaluation failure */
+    private double[] argument;
+
+    /**
+     * Construct an exception indicating the argument value
+     * that caused the function evaluation to fail.
+     *
+     * @param argument  the failing function argument
+     */
+    public FunctionEvaluationException(double argument) {
+        super(LocalizedFormats.EVALUATION_FAILED, argument);
+        this.argument = new double[] { argument };
+    }
+
+    /**
+     * Construct an exception indicating the argument value
+     * that caused the function evaluation to fail.
+     *
+     * @param argument  the failing function argument
+     * @since 2.0
+     */
+    public FunctionEvaluationException(double[] argument) {
+        super(LocalizedFormats.EVALUATION_FAILED, new ArrayRealVector(argument));
+        this.argument = argument.clone();
+    }
+
+    /**
+     * Constructs an exception with specified formatted detail message.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param argument  the failing function argument
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @since 1.2
+     */
+    public FunctionEvaluationException(double argument,
+                                       String pattern, Object ... arguments) {
+        this(argument, new DummyLocalizable(pattern), arguments);
+    }
+
+    /**
+     * Constructs an exception with specified formatted detail message.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param argument  the failing function argument
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @since 2.2
+     */
+    public FunctionEvaluationException(double argument,
+                                       Localizable pattern, Object ... arguments) {
+        super(pattern, arguments);
+        this.argument = new double[] { argument };
+    }
+
+    /**
+     * Constructs an exception with specified formatted detail message.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param argument  the failing function argument
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @since 2.0
+     */
+    public FunctionEvaluationException(double[] argument,
+                                       String pattern, Object ... arguments) {
+        this(argument, new DummyLocalizable(pattern), arguments);
+    }
+
+    /**
+     * Constructs an exception with specified formatted detail message.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param argument  the failing function argument
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @since 2.2
+     */
+    public FunctionEvaluationException(double[] argument,
+                                       Localizable pattern, Object ... arguments) {
+        super(pattern, arguments);
+        this.argument = argument.clone();
+    }
+
+    /**
+     * Constructs an exception with specified root cause.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param cause  the exception or error that caused this exception to be thrown
+     * @param argument  the failing function argument
+     * @since 1.2
+     */
+    public FunctionEvaluationException(Throwable cause, double argument) {
+        super(cause);
+        this.argument = new double[] { argument };
+    }
+
+    /**
+     * Constructs an exception with specified root cause.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param cause  the exception or error that caused this exception to be thrown
+     * @param argument  the failing function argument
+     * @since 2.0
+     */
+    public FunctionEvaluationException(Throwable cause, double[] argument) {
+        super(cause);
+        this.argument = argument.clone();
+    }
+
+    /**
+     * Constructs an exception with specified formatted detail message and root cause.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param cause  the exception or error that caused this exception to be thrown
+     * @param argument  the failing function argument
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @since 1.2
+     */
+    public FunctionEvaluationException(Throwable cause,
+                                       double argument, String pattern,
+                                       Object ... arguments) {
+        this(cause, argument, new DummyLocalizable(pattern), arguments);
+    }
+
+    /**
+     * Constructs an exception with specified formatted detail message and root cause.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param cause  the exception or error that caused this exception to be thrown
+     * @param argument  the failing function argument
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @since 2.2
+     */
+    public FunctionEvaluationException(Throwable cause,
+                                       double argument, Localizable pattern,
+                                       Object ... arguments) {
+        super(cause, pattern, arguments);
+        this.argument = new double[] { argument };
+    }
+
+    /**
+     * Constructs an exception with specified formatted detail message and root cause.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param cause  the exception or error that caused this exception to be thrown
+     * @param argument  the failing function argument
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @since 2.0
+     */
+    public FunctionEvaluationException(Throwable cause,
+                                       double[] argument, String pattern,
+                                       Object ... arguments) {
+        this(cause, argument, new DummyLocalizable(pattern), arguments);
+    }
+
+    /**
+     * Constructs an exception with specified formatted detail message and root cause.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param cause  the exception or error that caused this exception to be thrown
+     * @param argument  the failing function argument
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @since 2.2
+     */
+    public FunctionEvaluationException(Throwable cause,
+                                       double[] argument, Localizable pattern,
+                                       Object ... arguments) {
+        super(cause, pattern, arguments);
+        this.argument = argument.clone();
+    }
+
+    /**
+     * Returns the function argument that caused this exception.
+     *
+     * @return  argument that caused function evaluation to fail
+     */
+    public double[] getArgument() {
+        return argument.clone();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/MathConfigurationException.java b/src/main/java/org/apache/commons/math/MathConfigurationException.java
new file mode 100644
index 0000000..52a5b3e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/MathConfigurationException.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.math;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.exception.util.DummyLocalizable;
+import org.apache.commons.math.exception.util.Localizable;
+
+/**
+ * Signals a configuration problem with any of the factory methods.
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ */
+public class MathConfigurationException extends MathException implements Serializable{
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 5261476508226103366L;
+
+    /**
+     * Default constructor.
+     */
+    public MathConfigurationException() {
+        super();
+    }
+
+    /**
+     * Constructs an exception with specified formatted detail message.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @since 1.2
+     */
+    public MathConfigurationException(String pattern, Object ... arguments) {
+        this(new DummyLocalizable(pattern), arguments);
+    }
+
+    /**
+     * Constructs an exception with specified formatted detail message.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @since 2.2
+     */
+    public MathConfigurationException(Localizable pattern, Object ... arguments) {
+        super(pattern, arguments);
+    }
+
+    /**
+     * Create an exception with a given root cause.
+     * @param cause  the exception or error that caused this exception to be thrown
+     */
+    public MathConfigurationException(Throwable cause) {
+        super(cause);
+    }
+
+    /**
+     * Constructs an exception with specified formatted detail message and root cause.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param cause  the exception or error that caused this exception to be thrown
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @since 1.2
+     */
+    public MathConfigurationException(Throwable cause, String pattern, Object ... arguments) {
+        this(cause, new DummyLocalizable(pattern), arguments);
+    }
+
+    /**
+     * Constructs an exception with specified formatted detail message and root cause.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param cause  the exception or error that caused this exception to be thrown
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @since 2.2
+     */
+    public MathConfigurationException(Throwable cause, Localizable pattern, Object ... arguments) {
+        super(cause, pattern, arguments);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/MathException.java b/src/main/java/org/apache/commons/math/MathException.java
new file mode 100644
index 0000000..5f1a566
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/MathException.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.math;
+
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.text.MessageFormat;
+import java.util.Locale;
+
+import org.apache.commons.math.exception.MathThrowable;
+import org.apache.commons.math.exception.util.DummyLocalizable;
+import org.apache.commons.math.exception.util.Localizable;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+
+/**
+ * Base class for commons-math checked exceptions.
+ * <p>
+ * Supports nesting, emulating JDK 1.4 behavior if necessary.</p>
+ * <p>
+ * Adapted from <a href="http://commons.apache.org/collections/api-release/org/apache/commons/collections/FunctorException.html"/>.</p>
+ *
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ */
+public class MathException extends Exception implements MathThrowable {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 7428019509644517071L;
+
+    /**
+     * Pattern used to build the message.
+     */
+    private final Localizable pattern;
+
+    /**
+     * Arguments used to build the message.
+     */
+    private final Object[] arguments;
+
+    /**
+     * Constructs a new <code>MathException</code> with no
+     * detail message.
+     */
+    public MathException() {
+        this.pattern   = LocalizedFormats.SIMPLE_MESSAGE;
+        this.arguments = new Object[] { "" };
+    }
+
+    /**
+     * Constructs a new <code>MathException</code> with specified
+     * formatted detail message.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @deprecated as of 2.2 replaced by {@link #MathException(Localizable, Object...)}
+     */
+    @Deprecated
+    public MathException(String pattern, Object ... arguments) {
+      this(new DummyLocalizable(pattern), arguments);
+    }
+
+    /**
+     * Constructs a new <code>MathException</code> with specified
+     * formatted detail message.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @since 2.2
+     */
+    public MathException(Localizable pattern, Object ... arguments) {
+      this.pattern   = pattern;
+      this.arguments = (arguments == null) ? new Object[0] : arguments.clone();
+    }
+
+    /**
+     * Constructs a new <code>MathException</code> with specified
+     * nested <code>Throwable</code> root cause.
+     *
+     * @param rootCause  the exception or error that caused this exception
+     *                   to be thrown.
+     */
+    public MathException(Throwable rootCause) {
+        super(rootCause);
+        this.pattern   = LocalizedFormats.SIMPLE_MESSAGE;
+        this.arguments = new Object[] { (rootCause == null) ? "" : rootCause.getMessage() };
+    }
+
+    /**
+     * Constructs a new <code>MathException</code> with specified
+     * formatted detail message and nested <code>Throwable</code> root cause.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param rootCause the exception or error that caused this exception
+     * to be thrown.
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @since 1.2
+     * @deprecated as of 2.2 replaced by {@link #MathException(Throwable, Localizable, Object...)}
+     */
+    @Deprecated
+    public MathException(Throwable rootCause, String pattern, Object ... arguments) {
+        this(rootCause, new DummyLocalizable(pattern), arguments);
+    }
+
+    /**
+     * Constructs a new <code>MathException</code> with specified
+     * formatted detail message and nested <code>Throwable</code> root cause.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param rootCause the exception or error that caused this exception
+     * to be thrown.
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @since 2.2
+     */
+    public MathException(Throwable rootCause, Localizable pattern, Object ... arguments) {
+      super(rootCause);
+      this.pattern   = pattern;
+      this.arguments = (arguments == null) ? new Object[0] : arguments.clone();
+    }
+
+    /** Gets the pattern used to build the message of this throwable.
+     *
+     * @return the pattern used to build the message of this throwable
+     * @since 1.2
+     * @deprecated as of 2.2 replaced by {@link #getSpecificPattern()} and {@link #getGeneralPattern()}
+     */
+    @Deprecated
+    public String getPattern() {
+        return pattern.getSourceString();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 2.2
+     */
+    public Localizable getSpecificPattern() {
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 2.2
+     */
+    public Localizable getGeneralPattern() {
+        return pattern;
+    }
+
+    /** {@inheritDoc} */
+    public Object[] getArguments() {
+        return arguments.clone();
+    }
+
+    /** Gets the message in a specified locale.
+     *
+     * @param locale Locale in which the message should be translated
+     *
+     * @return localized message
+     * @since 1.2
+     */
+    public String getMessage(final Locale locale) {
+        if (pattern != null) {
+            return new MessageFormat(pattern.getLocalizedString(locale), locale).format(arguments);
+        }
+        return "";
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String getMessage() {
+        return getMessage(Locale.US);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String getLocalizedMessage() {
+        return getMessage(Locale.getDefault());
+    }
+
+    /**
+     * Prints the stack trace of this exception to the standard error stream.
+     */
+    @Override
+    public void printStackTrace() {
+        printStackTrace(System.err);
+    }
+
+    /**
+     * Prints the stack trace of this exception to the specified stream.
+     *
+     * @param out  the <code>PrintStream</code> to use for output
+     */
+    @Override
+    public void printStackTrace(PrintStream out) {
+        synchronized (out) {
+            PrintWriter pw = new PrintWriter(out, false);
+            printStackTrace(pw);
+            // Flush the PrintWriter before it's GC'ed.
+            pw.flush();
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/MathRuntimeException.java b/src/main/java/org/apache/commons/math/MathRuntimeException.java
new file mode 100644
index 0000000..3b9b89c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/MathRuntimeException.java
@@ -0,0 +1,717 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.text.MessageFormat;
+import java.text.ParseException;
+import java.util.ConcurrentModificationException;
+import java.util.Locale;
+import java.util.NoSuchElementException;
+
+import org.apache.commons.math.exception.MathThrowable;
+import org.apache.commons.math.exception.util.DummyLocalizable;
+import org.apache.commons.math.exception.util.Localizable;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+* Base class for commons-math unchecked exceptions.
+*
+* @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+* @since 2.0
+*/
+public class MathRuntimeException extends RuntimeException implements MathThrowable {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 9058794795027570002L;
+
+    /**
+     * Pattern used to build the message.
+     */
+    private final Localizable pattern;
+
+    /**
+     * Arguments used to build the message.
+     */
+    private final Object[] arguments;
+
+    /**
+     * Constructs a new <code>MathRuntimeException</code> with specified
+     * formatted detail message.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @deprecated as of 2.2 replaced by {@link #MathRuntimeException(Localizable, Object...)}
+     */
+    @Deprecated
+    public MathRuntimeException(final String pattern, final Object ... arguments) {
+        this(new DummyLocalizable(pattern), arguments);
+    }
+
+    /**
+     * Constructs a new <code>MathRuntimeException</code> with specified
+     * formatted detail message.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @since 2.2
+     */
+    public MathRuntimeException(final Localizable pattern, final Object ... arguments) {
+        this.pattern   = pattern;
+        this.arguments = (arguments == null) ? new Object[0] : arguments.clone();
+    }
+
+    /**
+     * Constructs a new <code>MathRuntimeException</code> with specified
+     * nested <code>Throwable</code> root cause.
+     *
+     * @param rootCause  the exception or error that caused this exception
+     *                   to be thrown.
+     */
+    public MathRuntimeException(final Throwable rootCause) {
+        super(rootCause);
+        this.pattern   = LocalizedFormats.SIMPLE_MESSAGE;
+        this.arguments = new Object[] { (rootCause == null) ? "" : rootCause.getMessage() };
+    }
+
+    /**
+     * Constructs a new <code>MathRuntimeException</code> with specified
+     * formatted detail message and nested <code>Throwable</code> root cause.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param rootCause the exception or error that caused this exception
+     * to be thrown.
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @deprecated as of 2.2 replaced by {@link #MathRuntimeException(Throwable, Localizable, Object...)}
+     */
+    @Deprecated
+    public MathRuntimeException(final Throwable rootCause,
+                                final String pattern, final Object ... arguments) {
+        this(rootCause, new DummyLocalizable(pattern), arguments);
+    }
+
+    /**
+     * Constructs a new <code>MathRuntimeException</code> with specified
+     * formatted detail message and nested <code>Throwable</code> root cause.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param rootCause the exception or error that caused this exception
+     * to be thrown.
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @since 2.2
+     */
+    public MathRuntimeException(final Throwable rootCause,
+                                final Localizable pattern, final Object ... arguments) {
+        super(rootCause);
+        this.pattern   = pattern;
+        this.arguments = (arguments == null) ? new Object[0] : arguments.clone();
+    }
+
+    /**
+     * Builds a message string by from a pattern and its arguments.
+     * @param locale Locale in which the message should be translated
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @return a message string
+     * @since 2.2
+     */
+    private static String buildMessage(final Locale locale, final Localizable pattern,
+                                       final Object ... arguments) {
+        return new MessageFormat(pattern.getLocalizedString(locale), locale).format(arguments);
+    }
+
+    /** Gets the pattern used to build the message of this throwable.
+    *
+    * @return the pattern used to build the message of this throwable
+    * @deprecated as of 2.2 replaced by {@link #getSpecificPattern()} and {@link #getGeneralPattern()}
+    */
+    @Deprecated
+    public String getPattern() {
+        return pattern.getSourceString();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 2.2
+     */
+    public Localizable getSpecificPattern() {
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @since 2.2
+     */
+    public Localizable getGeneralPattern() {
+        return pattern;
+    }
+
+    /** {@inheritDoc} */
+    public Object[] getArguments() {
+        return arguments.clone();
+    }
+
+    /** Gets the message in a specified locale.
+     *
+     * @param locale Locale in which the message should be translated
+     *
+     * @return localized message
+     */
+    public String getMessage(final Locale locale) {
+        if (pattern != null) {
+            return buildMessage(locale, pattern, arguments);
+        }
+        return "";
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String getMessage() {
+        return getMessage(Locale.US);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String getLocalizedMessage() {
+        return getMessage(Locale.getDefault());
+    }
+
+    /**
+     * Prints the stack trace of this exception to the standard error stream.
+     */
+    @Override
+    public void printStackTrace() {
+        printStackTrace(System.err);
+    }
+
+    /**
+     * Prints the stack trace of this exception to the specified stream.
+     *
+     * @param out  the <code>PrintStream</code> to use for output
+     */
+    @Override
+    public void printStackTrace(final PrintStream out) {
+        synchronized (out) {
+            PrintWriter pw = new PrintWriter(out, false);
+            printStackTrace(pw);
+            // Flush the PrintWriter before it's GC'ed.
+            pw.flush();
+        }
+    }
+
+    /**
+     * Constructs a new <code>ArithmeticException</code> with specified formatted detail message.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @return built exception
+     * @deprecated as of 2.2 replaced by {@link #createArithmeticException(Localizable, Object...)}
+     */
+    @Deprecated
+    public static ArithmeticException createArithmeticException(final String pattern,
+                                                                final Object ... arguments) {
+        return createArithmeticException(new DummyLocalizable(pattern), arguments);
+    }
+
+    /**
+     * Constructs a new <code>ArithmeticException</code> with specified formatted detail message.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @return built exception
+     * @since 2.2
+     */
+    public static ArithmeticException createArithmeticException(final Localizable pattern,
+                                                                final Object ... arguments) {
+        return new ArithmeticException() {
+
+            /** Serializable version identifier. */
+            private static final long serialVersionUID = 5305498554076846637L;
+
+            /** {@inheritDoc} */
+            @Override
+            public String getMessage() {
+                return buildMessage(Locale.US, pattern, arguments);
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public String getLocalizedMessage() {
+                return buildMessage(Locale.getDefault(), pattern, arguments);
+            }
+
+        };
+    }
+
+    /**
+     * Constructs a new <code>ArrayIndexOutOfBoundsException</code> with specified formatted detail message.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @return built exception
+     * @deprecated as of 2.2 replaced by {@link #createArrayIndexOutOfBoundsException(Localizable, Object...)}
+     */
+    @Deprecated
+    public static ArrayIndexOutOfBoundsException createArrayIndexOutOfBoundsException(final String pattern,
+                                                                                      final Object ... arguments) {
+        return createArrayIndexOutOfBoundsException(new DummyLocalizable(pattern), arguments);
+    }
+
+    /**
+     * Constructs a new <code>ArrayIndexOutOfBoundsException</code> with specified formatted detail message.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @return built exception
+     * @since 2.2
+     */
+    public static ArrayIndexOutOfBoundsException createArrayIndexOutOfBoundsException(final Localizable pattern,
+                                                                                      final Object ... arguments) {
+        return new ArrayIndexOutOfBoundsException() {
+
+            /** Serializable version identifier. */
+            private static final long serialVersionUID = 6718518191249632175L;
+
+            /** {@inheritDoc} */
+            @Override
+            public String getMessage() {
+                return buildMessage(Locale.US, pattern, arguments);
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public String getLocalizedMessage() {
+                return buildMessage(Locale.getDefault(), pattern, arguments);
+            }
+
+        };
+    }
+
+    /**
+     * Constructs a new <code>EOFException</code> with specified formatted detail message.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @return built exception
+     * @deprecated as of 2.2 replaced by {@link #createEOFException(Localizable, Object...)}
+     */
+    @Deprecated
+    public static EOFException createEOFException(final String pattern,
+                                                  final Object ... arguments) {
+        return createEOFException(new DummyLocalizable(pattern), arguments);
+    }
+
+    /**
+     * Constructs a new <code>EOFException</code> with specified formatted detail message.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @return built exception
+     * @since 2.2
+     */
+    public static EOFException createEOFException(final Localizable pattern,
+                                                  final Object ... arguments) {
+        return new EOFException() {
+
+            /** Serializable version identifier. */
+            private static final long serialVersionUID = 6067985859347601503L;
+
+            /** {@inheritDoc} */
+            @Override
+            public String getMessage() {
+                return buildMessage(Locale.US, pattern, arguments);
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public String getLocalizedMessage() {
+                return buildMessage(Locale.getDefault(), pattern, arguments);
+            }
+
+        };
+    }
+
+    /**
+     * Constructs a new <code>IOException</code> with specified nested
+     * <code>Throwable</code> root cause.
+     * <p>This factory method allows chaining of other exceptions within an
+     * <code>IOException</code> even for Java 5. The constructor for
+     * <code>IOException</code> with a cause parameter was introduced only
+     * with Java 6.</p>
+     * @param rootCause the exception or error that caused this exception
+     * to be thrown.
+     * @return built exception
+     */
+    public static IOException createIOException(final Throwable rootCause) {
+        IOException ioe = new IOException(rootCause.getLocalizedMessage());
+        ioe.initCause(rootCause);
+        return ioe;
+    }
+
+    /**
+     * Constructs a new <code>IllegalArgumentException</code> with specified formatted detail message.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @return built exception
+     * @deprecated as of 2.2 replaced by {@link #createIllegalArgumentException(Localizable, Object...)}
+     */
+    @Deprecated
+    public static IllegalArgumentException createIllegalArgumentException(final String pattern,
+                                                                          final Object ... arguments) {
+        return createIllegalArgumentException(new DummyLocalizable(pattern), arguments);
+    }
+
+    /**
+     * Constructs a new <code>IllegalArgumentException</code> with specified formatted detail message.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @return built exception
+     * @since 2.2
+     */
+    public static IllegalArgumentException createIllegalArgumentException(final Localizable pattern,
+                                                                          final Object ... arguments) {
+        return new IllegalArgumentException() {
+
+            /** Serializable version identifier. */
+            private static final long serialVersionUID = -4284649691002411505L;
+
+            /** {@inheritDoc} */
+            @Override
+            public String getMessage() {
+                return buildMessage(Locale.US, pattern, arguments);
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public String getLocalizedMessage() {
+                return buildMessage(Locale.getDefault(), pattern, arguments);
+            }
+
+        };
+    }
+
+    /**
+     * Constructs a new <code>IllegalArgumentException</code> with specified nested
+     * <code>Throwable</code> root cause.
+     * @param rootCause the exception or error that caused this exception
+     * to be thrown.
+     * @return built exception
+     */
+    public static IllegalArgumentException createIllegalArgumentException(final Throwable rootCause) {
+        IllegalArgumentException iae = new IllegalArgumentException(rootCause.getLocalizedMessage());
+        iae.initCause(rootCause);
+        return iae;
+    }
+
+    /**
+     * Constructs a new <code>IllegalStateException</code> with specified formatted detail message.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @return built exception
+     * @deprecated as of 2.2 replaced by {@link #createIllegalStateException(Localizable, Object...)}
+     */
+    @Deprecated
+    public static IllegalStateException createIllegalStateException(final String pattern,
+                                                                    final Object ... arguments) {
+        return createIllegalStateException(new DummyLocalizable(pattern), arguments);
+    }
+
+    /**
+     * Constructs a new <code>IllegalStateException</code> with specified formatted detail message.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @return built exception
+     * @since 2.2
+     */
+    public static IllegalStateException createIllegalStateException(final Localizable pattern,
+                                                                    final Object ... arguments) {
+        return new IllegalStateException() {
+
+            /** Serializable version identifier. */
+            private static final long serialVersionUID = 6880901520234515725L;
+
+            /** {@inheritDoc} */
+            @Override
+            public String getMessage() {
+                return buildMessage(Locale.US, pattern, arguments);
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public String getLocalizedMessage() {
+                return buildMessage(Locale.getDefault(), pattern, arguments);
+            }
+
+        };
+    }
+
+    /**
+     * Constructs a new <code>ConcurrentModificationException</code> with specified formatted detail message.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @return built exception
+     * @deprecated as of 2.2 replaced by {@link #createConcurrentModificationException(Localizable, Object...)}
+     */
+    @Deprecated
+    public static ConcurrentModificationException createConcurrentModificationException(final String pattern,
+                                                                                        final Object ... arguments) {
+        return createConcurrentModificationException(new DummyLocalizable(pattern), arguments);
+    }
+
+    /**
+     * Constructs a new <code>ConcurrentModificationException</code> with specified formatted detail message.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @return built exception
+     * @since 2.2
+     */
+    public static ConcurrentModificationException createConcurrentModificationException(final Localizable pattern,
+                                                                                        final Object ... arguments) {
+        return new ConcurrentModificationException() {
+
+            /** Serializable version identifier. */
+            private static final long serialVersionUID = -1878427236170442052L;
+
+            /** {@inheritDoc} */
+            @Override
+            public String getMessage() {
+                return buildMessage(Locale.US, pattern, arguments);
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public String getLocalizedMessage() {
+                return buildMessage(Locale.getDefault(), pattern, arguments);
+            }
+
+        };
+    }
+
+    /**
+     * Constructs a new <code>NoSuchElementException</code> with specified formatted detail message.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @return built exception
+     * @deprecated as of 2.2 replaced by {@link #createNoSuchElementException(Localizable, Object...)}
+     */
+    @Deprecated
+    public static NoSuchElementException createNoSuchElementException(final String pattern,
+                                                                      final Object ... arguments) {
+        return createNoSuchElementException(new DummyLocalizable(pattern), arguments);
+    }
+
+    /**
+     * Constructs a new <code>NoSuchElementException</code> with specified formatted detail message.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @return built exception
+     * @since 2.2
+     */
+    public static NoSuchElementException createNoSuchElementException(final Localizable pattern,
+                                                                      final Object ... arguments) {
+        return new NoSuchElementException() {
+
+            /** Serializable version identifier. */
+            private static final long serialVersionUID = 1632410088350355086L;
+
+            /** {@inheritDoc} */
+            @Override
+            public String getMessage() {
+                return buildMessage(Locale.US, pattern, arguments);
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public String getLocalizedMessage() {
+                return buildMessage(Locale.getDefault(), pattern, arguments);
+            }
+
+        };
+    }
+
+    /**
+     * Constructs a new <code>UnsupportedOperationException</code> with specified formatted detail message.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @return built exception
+     * @since 2.2
+     * @deprecated in 2.2. Please use {@link org.apache.commons.math.exception.MathUnsupportedOperationException}
+     * instead.
+     */
+    @Deprecated
+    public static UnsupportedOperationException createUnsupportedOperationException(final Localizable pattern,
+                                                                                    final Object ... arguments) {
+        return new UnsupportedOperationException() {
+
+            /** Serializable version identifier. */
+            private static final long serialVersionUID = -4284649691002411505L;
+
+            /** {@inheritDoc} */
+            @Override
+            public String getMessage() {
+                return buildMessage(Locale.US, pattern, arguments);
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public String getLocalizedMessage() {
+                return buildMessage(Locale.getDefault(), pattern, arguments);
+            }
+
+        };
+    }
+
+    /**
+     * Constructs a new <code>NullPointerException</code> with specified formatted detail message.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @return built exception
+     * @deprecated as of 2.2 replaced by {@link #createNullPointerException(Localizable, Object...)}
+     */
+    @Deprecated
+    public static NullPointerException createNullPointerException(final String pattern,
+                                                                  final Object ... arguments) {
+        return createNullPointerException(new DummyLocalizable(pattern), arguments);
+    }
+
+    /**
+     * Constructs a new <code>NullPointerException</code> with specified formatted detail message.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @return built exception
+     * @since 2.2
+     * @deprecated in 2.2. Checks for "null" must not be performed in Commons-Math.
+     */
+    @Deprecated
+    public static NullPointerException createNullPointerException(final Localizable pattern,
+                                                                  final Object ... arguments) {
+        return new NullPointerException() {
+
+            /** Serializable version identifier. */
+            private static final long serialVersionUID = 451965530686593945L;
+
+            /** {@inheritDoc} */
+            @Override
+            public String getMessage() {
+                return buildMessage(Locale.US, pattern, arguments);
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public String getLocalizedMessage() {
+                return buildMessage(Locale.getDefault(), pattern, arguments);
+            }
+
+        };
+    }
+
+   /**
+     * Constructs a new <code>ParseException</code> with specified
+     * formatted detail message.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param offset offset at which error occurred
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @return built exception
+     * @deprecated as of 2.2 replaced by {@link #createParseException(int, Localizable, Object...)}
+     */
+    @Deprecated
+    public static ParseException createParseException(final int offset,
+                                                      final String pattern,
+                                                      final Object ... arguments) {
+        return createParseException(offset, new DummyLocalizable(pattern), arguments);
+    }
+
+    /**
+     * Constructs a new <code>ParseException</code> with specified
+     * formatted detail message.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param offset offset at which error occurred
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @return built exception
+     * @since 2.2
+     */
+    public static ParseException createParseException(final int offset,
+                                                      final Localizable pattern,
+                                                      final Object ... arguments) {
+        return new ParseException(null, offset) {
+
+            /** Serializable version identifier. */
+            private static final long serialVersionUID = 8153587599409010120L;
+
+            /** {@inheritDoc} */
+            @Override
+            public String getMessage() {
+                return buildMessage(Locale.US, pattern, arguments);
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public String getLocalizedMessage() {
+                return buildMessage(Locale.getDefault(), pattern, arguments);
+            }
+
+        };
+    }
+
+    /** Create an {@link java.lang.RuntimeException} for an internal error.
+     * @param cause underlying cause
+     * @return an {@link java.lang.RuntimeException} for an internal error
+     */
+    public static RuntimeException createInternalError(final Throwable cause) {
+
+        final String argument = "https://issues.apache.org/jira/browse/MATH";
+
+        return new RuntimeException(cause) {
+
+            /** Serializable version identifier. */
+            private static final long serialVersionUID = -201865440834027016L;
+
+            /** {@inheritDoc} */
+            @Override
+            public String getMessage() {
+                return buildMessage(Locale.US, LocalizedFormats.INTERNAL_ERROR, argument);
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public String getLocalizedMessage() {
+                return buildMessage(Locale.getDefault(), LocalizedFormats.INTERNAL_ERROR, argument);
+            }
+
+        };
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/MaxEvaluationsExceededException.java b/src/main/java/org/apache/commons/math/MaxEvaluationsExceededException.java
new file mode 100644
index 0000000..06730bc
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/MaxEvaluationsExceededException.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.math;
+
+import org.apache.commons.math.ConvergenceException;
+import org.apache.commons.math.exception.util.DummyLocalizable;
+import org.apache.commons.math.exception.util.Localizable;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * Error thrown when a numerical computation exceeds its allowed
+ * number of functions evaluations.
+ *
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ * @since 2.0
+ */
+public class MaxEvaluationsExceededException extends ConvergenceException {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -5921271447220129118L;
+
+    /** Maximal number of evaluations allowed. */
+    private final int maxEvaluations;
+
+    /**
+     * Constructs an exception with a default detail message.
+     * @param maxEvaluations maximal number of evaluations allowed
+     */
+    public MaxEvaluationsExceededException(final int maxEvaluations) {
+        super(LocalizedFormats.MAX_EVALUATIONS_EXCEEDED, maxEvaluations);
+        this.maxEvaluations = maxEvaluations;
+    }
+
+    /**
+     * Constructs an exception with specified formatted detail message.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param maxEvaluations the exceeded maximal number of evaluations
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @deprecated as of 2.2 replaced by {@link #MaxEvaluationsExceededException(int, Localizable, Object...)}
+     */
+    @Deprecated
+    public MaxEvaluationsExceededException(final int maxEvaluations,
+                                          final String pattern, final Object ... arguments) {
+        this(maxEvaluations, new DummyLocalizable(pattern), arguments);
+    }
+
+    /**
+     * Constructs an exception with specified formatted detail message.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param maxEvaluations the exceeded maximal number of evaluations
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @since 2.2
+     */
+    public MaxEvaluationsExceededException(final int maxEvaluations,
+                                           final Localizable pattern, final Object ... arguments) {
+        super(pattern, arguments);
+        this.maxEvaluations = maxEvaluations;
+    }
+
+    /** Get the maximal number of evaluations allowed.
+     * @return maximal number of evaluations allowed
+     */
+    public int getMaxEvaluations() {
+        return maxEvaluations;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/MaxIterationsExceededException.java b/src/main/java/org/apache/commons/math/MaxIterationsExceededException.java
new file mode 100644
index 0000000..51b2cce
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/MaxIterationsExceededException.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.math;
+
+import org.apache.commons.math.ConvergenceException;
+import org.apache.commons.math.exception.util.DummyLocalizable;
+import org.apache.commons.math.exception.util.Localizable;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * Error thrown when a numerical computation exceeds its allowed
+ * number of iterations.
+ *
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ * @since 1.2
+ */
+public class MaxIterationsExceededException extends ConvergenceException {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -7821226672760574694L;
+
+    /** Maximal number of iterations allowed. */
+    private final int maxIterations;
+
+    /**
+     * Constructs an exception with a default detail message.
+     * @param maxIterations maximal number of iterations allowed
+     */
+    public MaxIterationsExceededException(final int maxIterations) {
+        super(LocalizedFormats.MAX_ITERATIONS_EXCEEDED, maxIterations);
+        this.maxIterations = maxIterations;
+    }
+
+    /**
+     * Constructs an exception with specified formatted detail message.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param maxIterations the exceeded maximal number of iterations
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @deprecated as of 2.2 replaced by {@link #MaxIterationsExceededException(int, Localizable, Object...)}
+     */
+    @Deprecated
+    public MaxIterationsExceededException(final int maxIterations,
+                                          final String pattern, final Object ... arguments) {
+        this(maxIterations, new DummyLocalizable(pattern), arguments);
+    }
+
+    /**
+     * Constructs an exception with specified formatted detail message.
+     * Message formatting is delegated to {@link java.text.MessageFormat}.
+     * @param maxIterations the exceeded maximal number of iterations
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @since 2.2
+     */
+    public MaxIterationsExceededException(final int maxIterations,
+                                           final Localizable pattern, final Object ... arguments) {
+        super(pattern, arguments);
+        this.maxIterations = maxIterations;
+    }
+
+    /** Get the maximal number of iterations allowed.
+     * @return maximal number of iterations allowed
+     */
+    public int getMaxIterations() {
+        return maxIterations;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/BinaryFunction.java b/src/main/java/org/apache/commons/math/analysis/BinaryFunction.java
new file mode 100644
index 0000000..227b72e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/BinaryFunction.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.math.analysis;
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.util.FastMath;
+
+
+
+/**
+ * Base class for {@link BivariateRealFunction} that can be composed with other functions.
+ *
+ * @since 2.1
+ * @version $Revision: 1073498 $ $Date: 2011-02-22 21:57:26 +0100 (mar. 22 févr. 2011) $
+ * @deprecated in 2.2
+ */
+@Deprecated
+public abstract class BinaryFunction implements BivariateRealFunction {
+
+    /** The + operator method wrapped as a {@link BinaryFunction}. */
+    public static final BinaryFunction ADD = new BinaryFunction() {
+        /** {@inheritDoc} */
+        @Override
+        public double value(double x, double y) {
+            return x + y;
+        }
+    };
+
+    /** The - operator method wrapped as a {@link BinaryFunction}. */
+    public static final BinaryFunction SUBTRACT = new BinaryFunction() {
+        /** {@inheritDoc} */
+        @Override
+        public double value(double x, double y) {
+            return x - y;
+        }
+    };
+
+    /** The * operator method wrapped as a {@link BinaryFunction}. */
+    public static final BinaryFunction MULTIPLY = new BinaryFunction() {
+        /** {@inheritDoc} */
+        @Override
+        public double value(double x, double y) {
+            return x * y;
+        }
+    };
+
+    /** The / operator method wrapped as a {@link BinaryFunction}. */
+    public static final BinaryFunction DIVIDE = new BinaryFunction() {
+        /** {@inheritDoc} */
+        @Override
+        public double value(double x, double y) {
+            return x / y;
+        }
+    };
+
+    /** The {@code FastMath.pow} method wrapped as a {@link BinaryFunction}. */
+    public static final BinaryFunction POW = new BinaryFunction() {
+        /** {@inheritDoc} */
+        @Override
+        public double value(double x, double y) {
+            return FastMath.pow(x, y);
+        }
+    };
+
+    /** The {@code FastMath.atan2} method wrapped as a {@link BinaryFunction}. */
+    public static final BinaryFunction ATAN2 = new BinaryFunction() {
+        /** {@inheritDoc} */
+        @Override
+        public double value(double x, double y) {
+            return FastMath.atan2(x, y);
+        }
+    };
+
+    /** {@inheritDoc} */
+    public abstract double value(double x, double y) throws FunctionEvaluationException;
+
+    /** Get a composable function by fixing the first argument of the instance.
+     * @param fixedX fixed value of the first argument
+     * @return a function such that {@code f.value(y) == value(fixedX, y)}
+     */
+    public ComposableFunction fix1stArgument(final double fixedX) {
+        return new ComposableFunction() {
+            @Override
+            /** {@inheritDoc} */
+            public double value(double x) throws FunctionEvaluationException {
+                return BinaryFunction.this.value(fixedX, x);
+            }
+        };
+    }
+
+    /** Get a composable function by fixing the second argument of the instance.
+     * @param fixedY fixed value of the second argument
+     * @return a function such that {@code f.value(x) == value(x, fixedY)}
+     */
+    public ComposableFunction fix2ndArgument(final double fixedY) {
+        return new ComposableFunction() {
+            @Override
+            /** {@inheritDoc} */
+            public double value(double x) throws FunctionEvaluationException {
+                return BinaryFunction.this.value(x, fixedY);
+            }
+        };
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/BivariateRealFunction.java b/src/main/java/org/apache/commons/math/analysis/BivariateRealFunction.java
new file mode 100644
index 0000000..4b12312
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/BivariateRealFunction.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.math.analysis;
+
+import org.apache.commons.math.FunctionEvaluationException;
+
+/**
+ * An interface representing a bivariate real function.
+ *
+ * @since 2.1
+ * @version $Revision: 1073498 $ $Date: 2011-02-22 21:57:26 +0100 (mar. 22 févr. 2011) $
+ */
+public interface BivariateRealFunction {
+    /**
+     * 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.
+     * @throws FunctionEvaluationException if the function evaluation fails.
+     */
+    double value(double x, double y)
+        throws FunctionEvaluationException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/ComposableFunction.java b/src/main/java/org/apache/commons/math/analysis/ComposableFunction.java
new file mode 100644
index 0000000..91c8132
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/ComposableFunction.java
@@ -0,0 +1,506 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.analysis;
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.util.FastMath;
+
+
+/**
+ * Base class for {@link UnivariateRealFunction} that can be composed with other functions.
+ *
+ * @since 2.1
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ */
+public abstract class ComposableFunction implements UnivariateRealFunction {
+
+    /** The constant function always returning 0. */
+    public static final ComposableFunction ZERO = new ComposableFunction() {
+        /** {@inheritDoc} */
+        @Override
+        public double value(double d) {
+            return 0;
+        }
+    };
+
+    /** The constant function always returning 1. */
+    public static final ComposableFunction ONE = new ComposableFunction() {
+        /** {@inheritDoc} */
+        @Override
+        public double value(double d) {
+            return 1;
+        }
+    };
+
+    /** The identity function. */
+    public static final ComposableFunction IDENTITY = new ComposableFunction() {
+        /** {@inheritDoc} */
+        @Override
+        public double value(double d) {
+            return d;
+        }
+    };
+
+    /** The {@code FastMath.abs} method wrapped as a {@link ComposableFunction}. */
+    public static final ComposableFunction ABS = new ComposableFunction() {
+        /** {@inheritDoc} */
+        @Override
+        public double value(double d) {
+            return FastMath.abs(d);
+        }
+    };
+
+    /** The - operator wrapped as a {@link ComposableFunction}. */
+    public static final ComposableFunction NEGATE = new ComposableFunction() {
+        /** {@inheritDoc} */
+        @Override
+        public double value(double d) {
+            return -d;
+        }
+    };
+
+    /** The invert operator wrapped as a {@link ComposableFunction}. */
+    public static final ComposableFunction INVERT = new ComposableFunction () {
+        /** {@inheritDoc} */
+        @Override
+        public double value(double d){
+            return 1/d;
+        }
+    };
+
+    /** The {@code FastMath.sin} method wrapped as a {@link ComposableFunction}. */
+    public static final ComposableFunction SIN = new ComposableFunction() {
+        /** {@inheritDoc} */
+        @Override
+        public double value(double d) {
+            return FastMath.sin(d);
+        }
+    };
+
+    /** The {@code FastMath.sqrt} method wrapped as a {@link ComposableFunction}. */
+    public static final ComposableFunction SQRT = new ComposableFunction() {
+        /** {@inheritDoc} */
+        @Override
+        public double value(double d) {
+            return FastMath.sqrt(d);
+        }
+    };
+
+    /** The {@code FastMath.sinh} method wrapped as a {@link ComposableFunction}. */
+    public static final ComposableFunction SINH = new ComposableFunction() {
+        /** {@inheritDoc} */
+        @Override
+        public double value(double d) {
+            return FastMath.sinh(d);
+        }
+    };
+
+    /** The {@code FastMath.exp} method wrapped as a {@link ComposableFunction}. */
+    public static final ComposableFunction EXP = new ComposableFunction() {
+        /** {@inheritDoc} */
+        @Override
+        public double value(double d) {
+            return FastMath.exp(d);
+        }
+    };
+
+    /** The {@code FastMath.expm1} method wrapped as a {@link ComposableFunction}. */
+    public static final ComposableFunction EXPM1 = new ComposableFunction() {
+        /** {@inheritDoc} */
+        @Override
+        public double value(double d) {
+            return FastMath.expm1(d);
+        }
+    };
+
+    /** The {@code FastMath.asin} method wrapped as a {@link ComposableFunction}. */
+    public static final ComposableFunction ASIN = new ComposableFunction() {
+        /** {@inheritDoc} */
+        @Override
+        public double value(double d) {
+            return FastMath.asin(d);
+        }
+    };
+
+    /** The {@code FastMath.atan} method wrapped as a {@link ComposableFunction}. */
+    public static final ComposableFunction ATAN = new ComposableFunction() {
+        /** {@inheritDoc} */
+        @Override
+        public double value(double d) {
+            return FastMath.atan(d);
+        }
+    };
+
+    /** The {@code FastMath.tan} method wrapped as a {@link ComposableFunction}. */
+    public static final ComposableFunction TAN = new ComposableFunction() {
+        /** {@inheritDoc} */
+        @Override
+        public double value(double d) {
+            return FastMath.tan(d);
+        }
+    };
+
+    /** The {@code FastMath.tanh} method wrapped as a {@link ComposableFunction}. */
+    public static final ComposableFunction TANH = new ComposableFunction() {
+        /** {@inheritDoc} */
+        @Override
+        public double value(double d) {
+            return FastMath.tanh(d);
+        }
+    };
+
+    /** The {@code FastMath.cbrt} method wrapped as a {@link ComposableFunction}. */
+    public static final ComposableFunction CBRT = new ComposableFunction() {
+        /** {@inheritDoc} */
+        @Override
+        public double value(double d) {
+            return FastMath.cbrt(d);
+        }
+    };
+
+    /** The {@code FastMath.ceil} method wrapped as a {@link ComposableFunction}. */
+    public static final ComposableFunction CEIL = new ComposableFunction() {
+        /** {@inheritDoc} */
+        @Override
+        public double value(double d) {
+            return FastMath.ceil(d);
+        }
+    };
+
+    /** The {@code FastMath.floor} method wrapped as a {@link ComposableFunction}. */
+    public static final ComposableFunction FLOOR = new ComposableFunction() {
+        /** {@inheritDoc} */
+        @Override
+        public double value(double d) {
+            return FastMath.floor(d);
+        }
+    };
+
+    /** The {@code FastMath.log} method wrapped as a {@link ComposableFunction}. */
+    public static final ComposableFunction LOG = new ComposableFunction() {
+        /** {@inheritDoc} */
+        @Override
+        public double value(double d) {
+            return FastMath.log(d);
+        }
+    };
+
+    /** The {@code FastMath.log10} method wrapped as a {@link ComposableFunction}. */
+    public static final ComposableFunction LOG10 = new ComposableFunction() {
+        /** {@inheritDoc} */
+        @Override
+        public double value(double d) {
+            return FastMath.log10(d);
+        }
+    };
+
+    /** The {@code FastMath.log1p} method wrapped as a {@link ComposableFunction}. */
+    public static final ComposableFunction LOG1P = new ComposableFunction () {
+        @Override
+        public double value(double d){
+            return FastMath.log1p(d);
+        }
+    };
+
+    /** The {@code FastMath.cos} method wrapped as a {@link ComposableFunction}. */
+    public static final ComposableFunction COS = new ComposableFunction() {
+        /** {@inheritDoc} */
+        @Override
+        public double value(double d) {
+            return FastMath.cos(d);
+        }
+    };
+
+    /** The {@code FastMath.abs} method wrapped as a {@link ComposableFunction}. */
+    public static final ComposableFunction ACOS = new ComposableFunction() {
+        /** {@inheritDoc} */
+        @Override
+        public double value(double d) {
+            return FastMath.acos(d);
+        }
+    };
+
+    /** The {@code FastMath.cosh} method wrapped as a {@link ComposableFunction}. */
+    public static final ComposableFunction COSH = new ComposableFunction() {
+        /** {@inheritDoc} */
+        @Override
+        public double value(double d) {
+            return FastMath.cosh(d);
+        }
+    };
+
+    /** The {@code FastMath.rint} method wrapped as a {@link ComposableFunction}. */
+    public static final ComposableFunction RINT = new ComposableFunction() {
+        /** {@inheritDoc} */
+        @Override
+        public double value(double d) {
+            return FastMath.rint(d);
+        }
+    };
+
+    /** The {@code FastMath.signum} method wrapped as a {@link ComposableFunction}. */
+    public static final ComposableFunction SIGNUM = new ComposableFunction() {
+        /** {@inheritDoc} */
+        @Override
+        public double value(double d) {
+            return FastMath.signum(d);
+        }
+    };
+
+    /** The {@code FastMath.ulp} method wrapped as a {@link ComposableFunction}. */
+    public static final ComposableFunction ULP = new ComposableFunction() {
+        /** {@inheritDoc} */
+        @Override
+        public double value(double d) {
+            return FastMath.ulp(d);
+        }
+    };
+
+    /** Precompose the instance with another function.
+     * <p>
+     * The composed function h created by {@code h = g.of(f)} is such
+     * that {@code h.value(x) == g.value(f.value(x))} for all x.
+     * </p>
+     * @param f function to compose with
+     * @return a new function which computes {@code this.value(f.value(x))}
+     * @see #postCompose(UnivariateRealFunction)
+     */
+    public ComposableFunction of(final UnivariateRealFunction f) {
+        return new ComposableFunction() {
+            @Override
+            /** {@inheritDoc} */
+            public double value(double x) throws FunctionEvaluationException {
+                return ComposableFunction.this.value(f.value(x));
+            }
+        };
+    }
+
+    /** Postcompose the instance with another function.
+     * <p>
+     * The composed function h created by {@code h = g.postCompose(f)} is such
+     * that {@code h.value(x) == f.value(g.value(x))} for all x.
+     * </p>
+     * @param f function to compose with
+     * @return a new function which computes {@code f.value(this.value(x))}
+     * @see #of(UnivariateRealFunction)
+     */
+    public ComposableFunction postCompose(final UnivariateRealFunction f) {
+        return new ComposableFunction() {
+            @Override
+            /** {@inheritDoc} */
+            public double value(double x) throws FunctionEvaluationException {
+                return f.value(ComposableFunction.this.value(x));
+            }
+        };
+    }
+
+    /**
+     * Return a function combining the instance and another function.
+     * <p>
+     * The function h created by {@code h = g.combine(f, combiner)} is such that
+     * {@code h.value(x) == combiner.value(g.value(x), f.value(x))} for all x.
+     * </p>
+     * @param f function to combine with the instance
+     * @param combiner bivariate function used for combining
+     * @return a new function which computes {@code combine.value(this.value(x), f.value(x))}
+     */
+    public ComposableFunction combine(final UnivariateRealFunction f,
+                                      final BivariateRealFunction combiner) {
+        return new ComposableFunction() {
+            @Override
+            /** {@inheritDoc} */
+            public double value(double x) throws FunctionEvaluationException {
+                return combiner.value(ComposableFunction.this.value(x), f.value(x));
+            }
+        };
+    }
+
+    /**
+     * Return a function adding the instance and another function.
+     * @param f function to combine with the instance
+     * @return a new function which computes {@code this.value(x) + f.value(x)}
+     */
+    public ComposableFunction add(final UnivariateRealFunction f) {
+        return new ComposableFunction() {
+            @Override
+            /** {@inheritDoc} */
+            public double value(double x) throws FunctionEvaluationException {
+                return ComposableFunction.this.value(x) + f.value(x);
+            }
+        };
+    }
+
+    /**
+     * Return a function adding a constant term to the instance.
+     * @param a term to add
+     * @return a new function which computes {@code this.value(x) + a}
+     */
+    public ComposableFunction add(final double a) {
+        return new ComposableFunction() {
+            @Override
+            /** {@inheritDoc} */
+            public double value(double x) throws FunctionEvaluationException {
+                return ComposableFunction.this.value(x) + a;
+            }
+        };
+    }
+
+    /**
+     * Return a function subtracting another function from the instance.
+     * @param f function to combine with the instance
+     * @return a new function which computes {@code this.value(x) - f.value(x)}
+     */
+    public ComposableFunction subtract(final UnivariateRealFunction f) {
+        return new ComposableFunction() {
+            @Override
+            /** {@inheritDoc} */
+            public double value(double x) throws FunctionEvaluationException {
+                return ComposableFunction.this.value(x) - f.value(x);
+            }
+        };
+    }
+
+    /**
+     * Return a function multiplying the instance and another function.
+     * @param f function to combine with the instance
+     * @return a new function which computes {@code this.value(x) * f.value(x)}
+     */
+    public ComposableFunction multiply(final UnivariateRealFunction f) {
+        return new ComposableFunction() {
+            @Override
+            /** {@inheritDoc} */
+            public double value(double x) throws FunctionEvaluationException {
+                return ComposableFunction.this.value(x) * f.value(x);
+            }
+        };
+    }
+
+    /**
+     * Return a function scaling the instance by a constant factor.
+     * @param scaleFactor constant scaling factor
+     * @return a new function which computes {@code this.value(x) * scaleFactor}
+     */
+    public ComposableFunction multiply(final double scaleFactor) {
+        return new ComposableFunction() {
+            @Override
+            /** {@inheritDoc} */
+            public double value(double x) throws FunctionEvaluationException {
+                return ComposableFunction.this.value(x) * scaleFactor;
+            }
+        };
+    }
+    /**
+     * Return a function dividing the instance by another function.
+     * @param f function to combine with the instance
+     * @return a new function which computes {@code this.value(x) / f.value(x)}
+     */
+    public ComposableFunction divide(final UnivariateRealFunction f) {
+        return new ComposableFunction() {
+            @Override
+            /** {@inheritDoc} */
+            public double value(double x) throws FunctionEvaluationException {
+                return ComposableFunction.this.value(x) / f.value(x);
+            }
+        };
+    }
+
+    /**
+     * Generates a function that iteratively apply instance function on all
+     * elements of an array.
+     * <p>
+     * The generated function behaves as follows:
+     * <ul>
+     *   <li>initialize result = initialValue</li>
+     *   <li>iterate: {@code result = combiner.value(result,
+     *   this.value(nextMultivariateEntry));}</li>
+     *   <li>return result</li>
+     * </ul>
+     * </p>
+     * @param combiner combiner to use between entries
+     * @param initialValue initial value to use before first entry
+     * @return a new function that iteratively apply instance function on all
+     * elements of an array.
+     */
+    public MultivariateRealFunction asCollector(final BivariateRealFunction combiner,
+                                                final double initialValue) {
+        return new MultivariateRealFunction() {
+            /** {@inheritDoc} */
+            public double value(double[] point)
+                throws FunctionEvaluationException, IllegalArgumentException {
+                double result = initialValue;
+                for (final double entry : point) {
+                    result = combiner.value(result, ComposableFunction.this.value(entry));
+                }
+                return result;
+            }
+        };
+    }
+
+    /**
+     * Generates a function that iteratively apply instance function on all
+     * elements of an array.
+     * <p>
+     * Calling this method is equivalent to call {@link
+     * #asCollector(BivariateRealFunction, double) asCollector(BivariateRealFunction, 0.0)}.
+     * </p>
+     * @param combiner combiner to use between entries
+     * @return a new function that iteratively apply instance function on all
+     * elements of an array.
+     * @see #asCollector(BivariateRealFunction, double)
+     */
+    public  MultivariateRealFunction asCollector(final BivariateRealFunction combiner) {
+        return asCollector(combiner, 0.0);
+    }
+
+    /**
+     * Generates a function that iteratively apply instance function on all
+     * elements of an array.
+     * <p>
+     * Calling this method is equivalent to call {@link
+     * #asCollector(BivariateRealFunction, double) asCollector(BinaryFunction.ADD, initialValue)}.
+     * </p>
+     * @param initialValue initial value to use before first entry
+     * @return a new function that iteratively apply instance function on all
+     * elements of an array.
+     * @see #asCollector(BivariateRealFunction, double)
+     * @see BinaryFunction#ADD
+     */
+    public  MultivariateRealFunction asCollector(final double initialValue) {
+        return asCollector(BinaryFunction.ADD, initialValue);
+    }
+
+    /**
+     * Generates a function that iteratively apply instance function on all
+     * elements of an array.
+     * <p>
+     * Calling this method is equivalent to call {@link
+     * #asCollector(BivariateRealFunction, double) asCollector(BinaryFunction.ADD, 0.0)}.
+     * </p>
+     * @return a new function that iteratively apply instance function on all
+     * elements of an array.
+     * @see #asCollector(BivariateRealFunction, double)
+     * @see BinaryFunction#ADD
+     */
+    public  MultivariateRealFunction asCollector() {
+        return asCollector(BinaryFunction.ADD, 0.0);
+    }
+
+    /** {@inheritDoc} */
+    public abstract double value(double x) throws FunctionEvaluationException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/DifferentiableMultivariateRealFunction.java b/src/main/java/org/apache/commons/math/analysis/DifferentiableMultivariateRealFunction.java
new file mode 100644
index 0000000..8d4f0c9
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/DifferentiableMultivariateRealFunction.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.math.analysis;
+
+/**
+ * Extension of {@link MultivariateRealFunction} representing a differentiable
+ * multivariate real function.
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ * @since 2.0
+ */
+public interface DifferentiableMultivariateRealFunction extends MultivariateRealFunction {
+
+    /**
+     * 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.
+     * </p>
+     * @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
+     */
+    MultivariateRealFunction 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.</p>
+     * @return the gradient function
+     */
+    MultivariateVectorialFunction gradient();
+
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/DifferentiableMultivariateVectorialFunction.java b/src/main/java/org/apache/commons/math/analysis/DifferentiableMultivariateVectorialFunction.java
new file mode 100644
index 0000000..cc4ab8e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/DifferentiableMultivariateVectorialFunction.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.math.analysis;
+
+
+/**
+ * Extension of {@link MultivariateVectorialFunction} representing a differentiable
+ * multivariate vectorial function.
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ * @since 2.0
+ */
+public interface DifferentiableMultivariateVectorialFunction
+    extends MultivariateVectorialFunction {
+
+    /**
+     * Returns the jacobian function.
+     * @return the jacobian function
+     */
+    MultivariateMatrixFunction jacobian();
+
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/DifferentiableUnivariateMatrixFunction.java b/src/main/java/org/apache/commons/math/analysis/DifferentiableUnivariateMatrixFunction.java
new file mode 100644
index 0000000..5f2f11b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/DifferentiableUnivariateMatrixFunction.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.math.analysis;
+
+/**
+ * Extension of {@link UnivariateMatrixFunction} representing a differentiable univariate matrix function.
+ *
+ * @version $Revision: 811786 $ $Date: 2009-09-06 11:36:08 +0200 (dim. 06 sept. 2009) $
+ * @since 2.0
+ */
+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/math/analysis/DifferentiableUnivariateRealFunction.java b/src/main/java/org/apache/commons/math/analysis/DifferentiableUnivariateRealFunction.java
new file mode 100644
index 0000000..1faaad3
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/DifferentiableUnivariateRealFunction.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.math.analysis;
+
+/**
+ * Extension of {@link UnivariateRealFunction} representing a differentiable univariate real function.
+ *
+ * @version $Revision: 811786 $ $Date: 2009-09-06 11:36:08 +0200 (dim. 06 sept. 2009) $
+ */
+public interface DifferentiableUnivariateRealFunction
+    extends UnivariateRealFunction {
+
+    /**
+     * Returns the derivative of the function
+     *
+     * @return  the derivative function
+     */
+    UnivariateRealFunction derivative();
+
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/DifferentiableUnivariateVectorialFunction.java b/src/main/java/org/apache/commons/math/analysis/DifferentiableUnivariateVectorialFunction.java
new file mode 100644
index 0000000..6566afe
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/DifferentiableUnivariateVectorialFunction.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.math.analysis;
+
+/**
+ * Extension of {@link UnivariateVectorialFunction} representing a differentiable univariate vectorial function.
+ *
+ * @version $Revision: 811786 $ $Date: 2009-09-06 11:36:08 +0200 (dim. 06 sept. 2009) $
+ * @since 2.0
+ */
+public interface DifferentiableUnivariateVectorialFunction
+    extends UnivariateVectorialFunction {
+
+    /**
+     * Returns the derivative of the function
+     *
+     * @return  the derivative function
+     */
+    UnivariateVectorialFunction derivative();
+
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/MultivariateMatrixFunction.java b/src/main/java/org/apache/commons/math/analysis/MultivariateMatrixFunction.java
new file mode 100644
index 0000000..a5bad26
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/MultivariateMatrixFunction.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.math.analysis;
+
+
+import org.apache.commons.math.FunctionEvaluationException;
+
+/**
+ * An interface representing a multivariate matrix function.
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @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 FunctionEvaluationException if the function evaluation fails
+     * @exception IllegalArgumentException if points dimension is wrong
+     */
+    double[][] value(double[] point)
+        throws FunctionEvaluationException, IllegalArgumentException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/MultivariateRealFunction.java b/src/main/java/org/apache/commons/math/analysis/MultivariateRealFunction.java
new file mode 100644
index 0000000..f054c51
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/MultivariateRealFunction.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.math.analysis;
+
+import org.apache.commons.math.FunctionEvaluationException;
+
+/**
+ * An interface representing a multivariate real function.
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ * @since 2.0
+ */
+public interface MultivariateRealFunction {
+
+    /**
+     * 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 FunctionEvaluationException if the function evaluation fails
+     * @exception IllegalArgumentException if points dimension is wrong
+     */
+    double value(double[] point)
+        throws FunctionEvaluationException, IllegalArgumentException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/MultivariateVectorialFunction.java b/src/main/java/org/apache/commons/math/analysis/MultivariateVectorialFunction.java
new file mode 100644
index 0000000..7e83a7c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/MultivariateVectorialFunction.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.math.analysis;
+
+import org.apache.commons.math.FunctionEvaluationException;
+
+/**
+ * An interface representing a multivariate vectorial function.
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ * @since 2.0
+ */
+public interface MultivariateVectorialFunction {
+
+    /**
+     * 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 FunctionEvaluationException if the function evaluation fails
+     * @exception IllegalArgumentException if points dimension is wrong
+     */
+    double[] value(double[] point)
+        throws FunctionEvaluationException, IllegalArgumentException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/TrivariateRealFunction.java b/src/main/java/org/apache/commons/math/analysis/TrivariateRealFunction.java
new file mode 100644
index 0000000..3ebf24d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/TrivariateRealFunction.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.math.analysis;
+
+import org.apache.commons.math.FunctionEvaluationException;
+
+/**
+ * An interface representing a trivariate real function.
+ *
+ * @since 2.2
+ * @version $Revision$ $Date$
+ */
+public interface TrivariateRealFunction {
+    /**
+     * 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.
+     * @throws FunctionEvaluationException if the function evaluation fails.
+     */
+    double value(double x, double y, double z)
+        throws FunctionEvaluationException;
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/UnivariateMatrixFunction.java b/src/main/java/org/apache/commons/math/analysis/UnivariateMatrixFunction.java
new file mode 100644
index 0000000..2db7349
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/UnivariateMatrixFunction.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.math.analysis;
+
+import org.apache.commons.math.FunctionEvaluationException;
+
+/**
+ * An interface representing a univariate matrix function.
+ *
+ * @version $Revision: 1073498 $ $Date: 2011-02-22 21:57:26 +0100 (mar. 22 févr. 2011) $
+ * @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
+     * @throws FunctionEvaluationException if the function evaluation fails
+     */
+    double[][] value(double x) throws FunctionEvaluationException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/UnivariateRealFunction.java b/src/main/java/org/apache/commons/math/analysis/UnivariateRealFunction.java
new file mode 100644
index 0000000..298d8a7
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/UnivariateRealFunction.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.math.analysis;
+
+import org.apache.commons.math.FunctionEvaluationException;
+
+/**
+ * An interface representing a univariate real function.
+ *
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ */
+public interface UnivariateRealFunction {
+
+    /**
+     * Compute the value for the function.
+     * @param x the point for which the function value should be computed
+     * @return the value
+     * @throws FunctionEvaluationException if the function evaluation fails
+     */
+    double value(double x) throws FunctionEvaluationException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/UnivariateVectorialFunction.java b/src/main/java/org/apache/commons/math/analysis/UnivariateVectorialFunction.java
new file mode 100644
index 0000000..64e7e15
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/UnivariateVectorialFunction.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.math.analysis;
+
+import org.apache.commons.math.FunctionEvaluationException;
+
+/**
+ * An interface representing a univariate vectorial function.
+ *
+ * @version $Revision: 1073498 $ $Date: 2011-02-22 21:57:26 +0100 (mar. 22 févr. 2011) $
+ * @since 2.0
+ */
+public interface UnivariateVectorialFunction {
+
+    /**
+     * Compute the value for the function.
+     * @param x the point for which the function value should be computed
+     * @return the value
+     * @throws FunctionEvaluationException if the function evaluation fails
+     */
+    double[] value(double x) throws FunctionEvaluationException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/integration/LegendreGaussIntegrator.java b/src/main/java/org/apache/commons/math/analysis/integration/LegendreGaussIntegrator.java
new file mode 100644
index 0000000..db6b76c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/integration/LegendreGaussIntegrator.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.math.analysis.integration;
+
+import org.apache.commons.math.ConvergenceException;
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.MaxIterationsExceededException;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.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 functions evaluations. A
+ * Legendre-Gauss integrator using an n-points quadrature formula can
+ * integrate exactly 2n-1 degree polynomials.
+ * </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>
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ * @since 1.2
+ */
+
+public class LegendreGaussIntegrator extends UnivariateRealIntegratorImpl {
+
+    /** 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.
+     * @param n number of points desired (must be between 2 and 5 inclusive)
+     * @param defaultMaximalIterationCount maximum number of iterations
+     * @exception IllegalArgumentException if the number of points is not
+     * in the supported range
+     */
+    public LegendreGaussIntegrator(final int n, final int defaultMaximalIterationCount)
+        throws IllegalArgumentException {
+        super(defaultMaximalIterationCount);
+        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 MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.N_POINTS_GAUSS_LEGENDRE_INTEGRATOR_NOT_SUPPORTED,
+                    n, 2, 5);
+        }
+
+    }
+
+    /** {@inheritDoc} */
+    @Deprecated
+    public double integrate(final double min, final double max)
+        throws ConvergenceException,  FunctionEvaluationException, IllegalArgumentException {
+        return integrate(f, min, max);
+    }
+
+    /** {@inheritDoc} */
+    public double integrate(final UnivariateRealFunction f, final double min, final double max)
+        throws ConvergenceException,  FunctionEvaluationException, IllegalArgumentException {
+
+        clearResult();
+        verifyInterval(min, max);
+        verifyIterationCount();
+
+        // compute first estimate with a single step
+        double oldt = stage(f, min, max, 1);
+
+        int n = 2;
+        for (int i = 0; i < maximalIterationCount; ++i) {
+
+            // improve integral with a larger number of steps
+            final double t = stage(f, min, max, n);
+
+            // estimate error
+            final double delta = FastMath.abs(t - oldt);
+            final double limit =
+                FastMath.max(absoluteAccuracy,
+                         relativeAccuracy * (FastMath.abs(oldt) + FastMath.abs(t)) * 0.5);
+
+            // check convergence
+            if ((i + 1 >= minimalIterationCount) && (delta <= limit)) {
+                setResult(t, i);
+                return result;
+            }
+
+            // 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;
+
+        }
+
+        throw new MaxIterationsExceededException(maximalIterationCount);
+
+    }
+
+    /**
+     * Compute the n-th stage integral.
+     * @param f the integrand function
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @param n number of steps
+     * @return the value of n-th stage integral
+     * @throws FunctionEvaluationException if an error occurs evaluating the
+     * function
+     */
+    private double stage(final UnivariateRealFunction f,
+                         final double min, final double max, final int n)
+        throws FunctionEvaluationException {
+
+        // set up the step for the current stage
+        final double step     = (max - min) / n;
+        final double halfStep = step / 2.0;
+
+        // integrate over all elementary steps
+        double midPoint = min + halfStep;
+        double sum = 0.0;
+        for (int i = 0; i < n; ++i) {
+            for (int j = 0; j < abscissas.length; ++j) {
+                sum += weights[j] * f.value(midPoint + halfStep * abscissas[j]);
+            }
+            midPoint += step;
+        }
+
+        return halfStep * sum;
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/integration/RombergIntegrator.java b/src/main/java/org/apache/commons/math/analysis/integration/RombergIntegrator.java
new file mode 100644
index 0000000..9650af5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/integration/RombergIntegrator.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.math.analysis.integration;
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.MaxIterationsExceededException;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.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>
+ *
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ * @since 1.2
+ */
+public class RombergIntegrator extends UnivariateRealIntegratorImpl {
+
+    /**
+     * Construct an integrator for the given function.
+     *
+     * @param f function to integrate
+     * @deprecated as of 2.0 the integrand function is passed as an argument
+     * to the {@link #integrate(UnivariateRealFunction, double, double)}method.
+     */
+    @Deprecated
+    public RombergIntegrator(UnivariateRealFunction f) {
+        super(f, 32);
+    }
+
+    /**
+     * Construct an integrator.
+     */
+    public RombergIntegrator() {
+        super(32);
+    }
+
+    /** {@inheritDoc} */
+    @Deprecated
+    public double integrate(final double min, final double max)
+        throws MaxIterationsExceededException, FunctionEvaluationException, IllegalArgumentException {
+        return integrate(f, min, max);
+    }
+
+    /** {@inheritDoc} */
+    public double integrate(final UnivariateRealFunction f, final double min, final double max)
+        throws MaxIterationsExceededException, FunctionEvaluationException, IllegalArgumentException {
+
+        final int m = maximalIterationCount + 1;
+        double previousRow[] = new double[m];
+        double currentRow[]  = new double[m];
+
+        clearResult();
+        verifyInterval(min, max);
+        verifyIterationCount();
+
+        TrapezoidIntegrator qtrap = new TrapezoidIntegrator();
+        currentRow[0] = qtrap.stage(f, min, max, 0);
+        double olds = currentRow[0];
+        for (int i = 1; i <= maximalIterationCount; ++i) {
+
+            // switch rows
+            final double[] tmpRow = previousRow;
+            previousRow = currentRow;
+            currentRow = tmpRow;
+
+            currentRow[0] = qtrap.stage(f, min, max, i);
+            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 >= minimalIterationCount) {
+                final double delta  = FastMath.abs(s - olds);
+                final double rLimit = relativeAccuracy * (FastMath.abs(olds) + FastMath.abs(s)) * 0.5;
+                if ((delta <= rLimit) || (delta <= absoluteAccuracy)) {
+                    setResult(s, i);
+                    return result;
+                }
+            }
+            olds = s;
+        }
+        throw new MaxIterationsExceededException(maximalIterationCount);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void verifyIterationCount() throws IllegalArgumentException {
+        super.verifyIterationCount();
+        // at most 32 bisection refinements due to higher order divider
+        if (maximalIterationCount > 32) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.INVALID_ITERATIONS_LIMITS,
+                    0, 32);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/integration/SimpsonIntegrator.java b/src/main/java/org/apache/commons/math/analysis/integration/SimpsonIntegrator.java
new file mode 100644
index 0000000..045b54e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/integration/SimpsonIntegrator.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.math.analysis.integration;
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.MaxIterationsExceededException;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Implements the <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 basic trapezoid rule as building blocks to
+ * calculate the Simpson's rule of alternating 2/3 and 4/3.</p>
+ *
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ * @since 1.2
+ */
+public class SimpsonIntegrator extends UnivariateRealIntegratorImpl {
+
+    /**
+     * Construct an integrator for the given function.
+     *
+     * @param f function to integrate
+     * @deprecated as of 2.0 the integrand function is passed as an argument
+     * to the {@link #integrate(UnivariateRealFunction, double, double)}method.
+     */
+    @Deprecated
+    public SimpsonIntegrator(UnivariateRealFunction f) {
+        super(f, 64);
+    }
+
+    /**
+     * Construct an integrator.
+     */
+    public SimpsonIntegrator() {
+        super(64);
+    }
+
+    /** {@inheritDoc} */
+    @Deprecated
+    public double integrate(final double min, final double max)
+        throws MaxIterationsExceededException, FunctionEvaluationException, IllegalArgumentException {
+        return integrate(f, min, max);
+    }
+
+    /** {@inheritDoc} */
+    public double integrate(final UnivariateRealFunction f, final double min, final double max)
+        throws MaxIterationsExceededException, FunctionEvaluationException, IllegalArgumentException {
+
+        clearResult();
+        verifyInterval(min, max);
+        verifyIterationCount();
+
+        TrapezoidIntegrator qtrap = new TrapezoidIntegrator();
+        if (minimalIterationCount == 1) {
+            final double s = (4 * qtrap.stage(f, min, max, 1) - qtrap.stage(f, min, max, 0)) / 3.0;
+            setResult(s, 1);
+            return result;
+        }
+        // Simpson's rule requires at least two trapezoid stages.
+        double olds = 0;
+        double oldt = qtrap.stage(f, min, max, 0);
+        for (int i = 1; i <= maximalIterationCount; ++i) {
+            final double t = qtrap.stage(f, min, max, i);
+            final double s = (4 * t - oldt) / 3.0;
+            if (i >= minimalIterationCount) {
+                final double delta = FastMath.abs(s - olds);
+                final double rLimit =
+                    relativeAccuracy * (FastMath.abs(olds) + FastMath.abs(s)) * 0.5;
+                if ((delta <= rLimit) || (delta <= absoluteAccuracy)) {
+                    setResult(s, i);
+                    return result;
+                }
+            }
+            olds = s;
+            oldt = t;
+        }
+        throw new MaxIterationsExceededException(maximalIterationCount);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void verifyIterationCount() throws IllegalArgumentException {
+        super.verifyIterationCount();
+        // at most 64 bisection refinements
+        if (maximalIterationCount > 64) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.INVALID_ITERATIONS_LIMITS,
+                    0, 64);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/integration/TrapezoidIntegrator.java b/src/main/java/org/apache/commons/math/analysis/integration/TrapezoidIntegrator.java
new file mode 100644
index 0000000..88903f5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/integration/TrapezoidIntegrator.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.math.analysis.integration;
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.MaxIterationsExceededException;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Implements the <a href="http://mathworld.wolfram.com/TrapezoidalRule.html">
+ * Trapezoidal 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>
+ *
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ * @since 1.2
+ */
+public class TrapezoidIntegrator extends UnivariateRealIntegratorImpl {
+
+    /** Intermediate result. */
+    private double s;
+
+    /**
+     * Construct an integrator for the given function.
+     *
+     * @param f function to integrate
+     * @deprecated as of 2.0 the integrand function is passed as an argument
+     * to the {@link #integrate(UnivariateRealFunction, double, double)}method.
+     */
+    @Deprecated
+    public TrapezoidIntegrator(UnivariateRealFunction f) {
+        super(f, 64);
+    }
+
+    /**
+     * Construct an integrator.
+     */
+    public TrapezoidIntegrator() {
+        super(64);
+    }
+
+    /**
+     * 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
+     * alrealy computed values.</p>
+     *
+     * @param f the integrand function
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @param n the stage of 1/2 refinement, n = 0 is no refinement
+     * @return the value of n-th stage integral
+     * @throws FunctionEvaluationException if an error occurs evaluating the function
+     */
+    double stage(final UnivariateRealFunction f,
+                 final double min, final double max, final int n)
+        throws FunctionEvaluationException {
+
+        if (n == 0) {
+            s = 0.5 * (max - min) * (f.value(min) + f.value(max));
+            return s;
+        } else {
+            final long np = 1L << (n-1);           // number of new points in this stage
+            double sum = 0;
+            final double spacing = (max - min) / np; // spacing between adjacent new points
+            double x = min + 0.5 * spacing;    // the first new point
+            for (long i = 0; i < np; i++) {
+                sum += f.value(x);
+                x += spacing;
+            }
+            // add the new sum to previously calculated result
+            s = 0.5 * (s + sum * spacing);
+            return s;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Deprecated
+    public double integrate(final double min, final double max)
+        throws MaxIterationsExceededException, FunctionEvaluationException, IllegalArgumentException {
+        return integrate(f, min, max);
+    }
+
+    /** {@inheritDoc} */
+    public double integrate(final UnivariateRealFunction f, final double min, final double max)
+        throws MaxIterationsExceededException, FunctionEvaluationException, IllegalArgumentException {
+
+        clearResult();
+        verifyInterval(min, max);
+        verifyIterationCount();
+
+        double oldt = stage(f, min, max, 0);
+        for (int i = 1; i <= maximalIterationCount; ++i) {
+            final double t = stage(f, min, max, i);
+            if (i >= minimalIterationCount) {
+                final double delta = FastMath.abs(t - oldt);
+                final double rLimit =
+                    relativeAccuracy * (FastMath.abs(oldt) + FastMath.abs(t)) * 0.5;
+                if ((delta <= rLimit) || (delta <= absoluteAccuracy)) {
+                    setResult(t, i);
+                    return result;
+                }
+            }
+            oldt = t;
+        }
+        throw new MaxIterationsExceededException(maximalIterationCount);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void verifyIterationCount() throws IllegalArgumentException {
+        super.verifyIterationCount();
+        // at most 64 bisection refinements
+        if (maximalIterationCount > 64) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.INVALID_ITERATIONS_LIMITS,
+                    0, 64);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/integration/UnivariateRealIntegrator.java b/src/main/java/org/apache/commons/math/analysis/integration/UnivariateRealIntegrator.java
new file mode 100644
index 0000000..184bb44
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/integration/UnivariateRealIntegrator.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.math.analysis.integration;
+
+import org.apache.commons.math.ConvergenceException;
+import org.apache.commons.math.ConvergingAlgorithm;
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+
+/**
+ * Interface for univariate real integration algorithms.
+ *
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ * @since 1.2
+ */
+public interface UnivariateRealIntegrator extends ConvergingAlgorithm {
+
+   /**
+     * Set the lower limit for the number of iterations.
+     * <p>
+     * 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.</p>
+     * <p>
+     * A <code>ConvergenceException</code> will be thrown if this number
+     * is not met.</p>
+     *
+     * @param count minimum number of iterations
+     */
+    void setMinimalIterationCount(int count);
+
+    /**
+     * Get the lower limit for the number of iterations.
+     *
+     * @return the actual lower limit
+     */
+    int getMinimalIterationCount();
+
+    /**
+     * Reset the lower limit for the number of iterations to the default.
+     * <p>
+     * The default value is supplied by the implementation.</p>
+     *
+     * @see #setMinimalIterationCount(int)
+     */
+    void resetMinimalIterationCount();
+
+    /**
+     * Integrate the function in the given interval.
+     *
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @return the value of integral
+     * @throws ConvergenceException if the maximum iteration count is exceeded
+     * or the integrator detects convergence problems otherwise
+     * @throws FunctionEvaluationException if an error occurs evaluating the
+     * function
+     * @throws IllegalArgumentException if min > max or the endpoints do not
+     * satisfy the requirements specified by the integrator
+     * @deprecated replaced by {@link #integrate(UnivariateRealFunction, double, double)}
+     * since 2.0
+     */
+    @Deprecated
+    double integrate(double min, double max)
+        throws ConvergenceException, FunctionEvaluationException, IllegalArgumentException;
+
+    /**
+     * Integrate the function in the given interval.
+     *
+     * @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 ConvergenceException if the maximum iteration count is exceeded
+     * or the integrator detects convergence problems otherwise
+     * @throws FunctionEvaluationException if an error occurs evaluating the function
+     * @throws IllegalArgumentException if min > max or the endpoints do not
+     * satisfy the requirements specified by the integrator
+     */
+    double integrate(UnivariateRealFunction f, double min, double max)
+        throws ConvergenceException, FunctionEvaluationException, IllegalArgumentException;
+
+    /**
+     * Get the result of the last run of the integrator.
+     *
+     * @return the last result
+     * @throws IllegalStateException if there is no result available, either
+     * because no result was yet computed or the last attempt failed
+     */
+    double getResult() throws IllegalStateException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/integration/UnivariateRealIntegratorImpl.java b/src/main/java/org/apache/commons/math/analysis/integration/UnivariateRealIntegratorImpl.java
new file mode 100644
index 0000000..655a852
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/integration/UnivariateRealIntegratorImpl.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.math.analysis.integration;
+
+import org.apache.commons.math.ConvergingAlgorithmImpl;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.exception.NullArgumentException;
+
+/**
+ * Provide a default implementation for several generic functions.
+ *
+ * @version $Revision: 1072409 $ $Date: 2011-02-19 19:50:36 +0100 (sam. 19 févr. 2011) $
+ * @since 1.2
+ */
+public abstract class UnivariateRealIntegratorImpl
+    extends ConvergingAlgorithmImpl implements UnivariateRealIntegrator {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 6248808456637441533L;
+
+    /** minimum number of iterations */
+    protected int minimalIterationCount;
+
+    /** default minimum number of iterations */
+    protected int defaultMinimalIterationCount;
+
+    /** indicates whether an integral has been computed */
+    protected boolean resultComputed = false;
+
+    /** the last computed integral */
+    protected double result;
+
+    /**
+     * The integrand function.
+     *
+     * @deprecated as of 2.0 the integrand function is passed as an argument
+     * to the {@link #integrate(UnivariateRealFunction, double, double)}method.
+     */
+    @Deprecated
+    protected UnivariateRealFunction f;
+
+    /**
+     * Construct an integrator with given iteration count and accuracy.
+     *
+     * @param f the integrand function
+     * @param defaultMaximalIterationCount maximum number of iterations
+     * @throws IllegalArgumentException if f is null or the iteration
+     * limits are not valid
+     * @deprecated as of 2.0 the integrand function is passed as an argument
+     * to the {@link #integrate(UnivariateRealFunction, double, double)}method.
+     */
+    @Deprecated
+    protected UnivariateRealIntegratorImpl(final UnivariateRealFunction f,
+                                           final int defaultMaximalIterationCount)
+        throws IllegalArgumentException {
+        super(defaultMaximalIterationCount, 1.0e-15);
+        if (f == null) {
+            throw new NullArgumentException(LocalizedFormats.FUNCTION);
+        }
+
+        this.f = f;
+
+        // parameters that are problem specific
+        setRelativeAccuracy(1.0e-6);
+        this.defaultMinimalIterationCount = 3;
+        this.minimalIterationCount = defaultMinimalIterationCount;
+
+        verifyIterationCount();
+    }
+
+    /**
+     * Construct an integrator with given iteration count and accuracy.
+     *
+     * @param defaultMaximalIterationCount maximum number of iterations
+     * @throws IllegalArgumentException if f is null or the iteration
+     * limits are not valid
+     */
+    protected UnivariateRealIntegratorImpl(final int defaultMaximalIterationCount)
+        throws IllegalArgumentException {
+        super(defaultMaximalIterationCount, 1.0e-15);
+
+        // parameters that are problem specific
+        setRelativeAccuracy(1.0e-6);
+        this.defaultMinimalIterationCount = 3;
+        this.minimalIterationCount = defaultMinimalIterationCount;
+
+        verifyIterationCount();
+    }
+
+    /**
+     * Access the last computed integral.
+     *
+     * @return the last computed integral
+     * @throws IllegalStateException if no integral has been computed
+     */
+    public double getResult() throws IllegalStateException {
+        if (resultComputed) {
+            return result;
+        } else {
+            throw MathRuntimeException.createIllegalStateException(LocalizedFormats.NO_RESULT_AVAILABLE);
+        }
+    }
+
+    /**
+     * Convenience function for implementations.
+     *
+     * @param newResult the result to set
+     * @param iterationCount the iteration count to set
+     */
+    protected final void setResult(double newResult, int iterationCount) {
+        this.result         = newResult;
+        this.iterationCount = iterationCount;
+        this.resultComputed = true;
+    }
+
+    /**
+     * Convenience function for implementations.
+     */
+    protected final void clearResult() {
+        this.iterationCount = 0;
+        this.resultComputed = false;
+    }
+
+    /** {@inheritDoc} */
+    public void setMinimalIterationCount(int count) {
+        minimalIterationCount = count;
+    }
+
+    /** {@inheritDoc} */
+    public int getMinimalIterationCount() {
+        return minimalIterationCount;
+    }
+
+    /** {@inheritDoc} */
+    public void resetMinimalIterationCount() {
+        minimalIterationCount = defaultMinimalIterationCount;
+    }
+
+    /**
+     * Verifies that the endpoints specify an interval.
+     *
+     * @param lower lower endpoint
+     * @param upper upper endpoint
+     * @throws IllegalArgumentException if not interval
+     */
+    protected void verifyInterval(double lower, double upper) throws
+        IllegalArgumentException {
+        if (lower >= upper) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.ENDPOINTS_NOT_AN_INTERVAL,
+                    lower, upper);
+        }
+    }
+
+    /**
+     * Verifies that the upper and lower limits of iterations are valid.
+     *
+     * @throws IllegalArgumentException if not valid
+     */
+    protected void verifyIterationCount() throws IllegalArgumentException {
+        if ((minimalIterationCount <= 0) || (maximalIterationCount <= minimalIterationCount)) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.INVALID_ITERATIONS_LIMITS,
+                    minimalIterationCount, maximalIterationCount);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/integration/package.html b/src/main/java/org/apache/commons/math/analysis/integration/package.html
new file mode 100644
index 0000000..5bbab0e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/integration/package.html
@@ -0,0 +1,22 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $ -->
+    <body>
+     Numerical integration (quadrature) algorithms for univariate real functions.
+    </body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/analysis/interpolation/BicubicSplineInterpolatingFunction.java b/src/main/java/org/apache/commons/math/analysis/interpolation/BicubicSplineInterpolatingFunction.java
new file mode 100644
index 0000000..c786a4d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/interpolation/BicubicSplineInterpolatingFunction.java
@@ -0,0 +1,558 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.analysis.interpolation;
+
+import org.apache.commons.math.DimensionMismatchException;
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.analysis.BivariateRealFunction;
+import org.apache.commons.math.exception.NoDataException;
+import org.apache.commons.math.exception.OutOfRangeException;
+import org.apache.commons.math.util.MathUtils;
+
+/**
+ * Function that implements the
+ * <a href="http://en.wikipedia.org/wiki/Bicubic_interpolation">
+ * bicubic spline interpolation</a>.
+ *
+ * @version $Revision$ $Date$
+ * @since 2.1
+ */
+public class BicubicSplineInterpolatingFunction
+    implements BivariateRealFunction {
+    /**
+     * 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 BivariateRealFunction[][][] partialDerivatives = null;
+
+    /**
+     * @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 org.apache.commons.math.exception.NonMonotonousSequenceException
+     * 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 {
+        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);
+        }
+
+        MathUtils.checkOrder(x);
+        MathUtils.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));
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public double value(double x, double y) {
+        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 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);
+    }
+
+    /**
+     * @param x x-coordinate.
+     * @param y y-coordinate.
+     * @return the value at point (x, y) of the first partial derivative with
+     * respect to x.
+     * @since 2.2
+     */
+    public double partialDerivativeX(double x, double y) {
+        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.
+     * @since 2.2
+     */
+    public double partialDerivativeY(double x, double y) {
+        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.
+     * @since 2.2
+     */
+    public double partialDerivativeXX(double x, double y) {
+        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.
+     * @since 2.2
+     */
+    public double partialDerivativeYY(double x, double y) {
+        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.
+     * @since 2.2
+     */
+    public double partialDerivativeXY(double x, double y) {
+        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 FunctionEvaluationException
+     */
+    private double partialDerivative(int which, double x, double y) {
+        if (partialDerivatives == null) {
+            computePartialDerivatives();
+        }
+
+        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 double xN = (x - xval[i]) / (xval[i + 1] - xval[i]);
+        final double yN = (y - yval[j]) / (yval[j + 1] - yval[j]);
+
+        try {
+            return partialDerivatives[which][i][j].value(xN, yN);
+        } catch (FunctionEvaluationException fee) {
+            // this should never happen
+            throw new RuntimeException(fee);
+        }
+
+    }
+
+    /**
+     * Compute all partial derivatives.
+     */
+    private void computePartialDerivatives() {
+        final int lastI = xval.length - 1;
+        final int lastJ = yval.length - 1;
+        partialDerivatives = new BivariateRealFunction[5][lastI][lastJ];
+
+        for (int i = 0; i < lastI; i++) {
+            for (int j = 0; j < lastJ; j++) {
+                final BicubicSplineFunction f = splines[i][j];
+                partialDerivatives[0][i][j] = f.partialDerivativeX();
+                partialDerivatives[1][i][j] = f.partialDerivativeY();
+                partialDerivatives[2][i][j] = f.partialDerivativeXX();
+                partialDerivatives[3][i][j] = f.partialDerivativeYY();
+                partialDerivatives[4][i][j] = f.partialDerivativeXY();
+            }
+        }
+    }
+
+    /**
+     * @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)</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[16];
+
+        for (int i = 0; i < 16; i++) {
+            double result = 0;
+            final double[] row = AINV[i];
+            for (int j = 0; j < 16; j++) {
+                result += row[j] * beta[j];
+            }
+            a[i] = result;
+        }
+
+        return a;
+    }
+}
+
+/**
+ * 2D-spline function.
+ *
+ * @version $Revision$ $Date$
+ */
+class BicubicSplineFunction
+    implements BivariateRealFunction {
+
+    /** Number of points. */
+    private static final short N = 4;
+
+    /** Coefficients */
+    private final double[][] a;
+
+    /** First partial derivative along x. */
+    private BivariateRealFunction partialDerivativeX;
+
+    /** First partial derivative along y. */
+    private BivariateRealFunction partialDerivativeY;
+
+    /** Second partial derivative along x. */
+    private BivariateRealFunction partialDerivativeXX;
+
+    /** Second partial derivative along y. */
+    private BivariateRealFunction partialDerivativeYY;
+
+    /** Second crossed partial derivative. */
+    private BivariateRealFunction partialDerivativeXY;
+
+    /**
+     * Simple constructor.
+     * @param a Spline coefficients
+     */
+    public BicubicSplineFunction(double[] a) {
+        this.a = new double[N][N];
+        for (int i = 0; i < N; i++) {
+            for (int j = 0; j < N; j++) {
+                this.a[i][j] = a[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++) {
+            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 BivariateRealFunction partialDerivativeX() {
+        if (partialDerivativeX == null) {
+            computePartialDerivatives();
+        }
+
+        return partialDerivativeX;
+    }
+    /**
+     * @return the partial derivative wrt {@code y}.
+     */
+    public BivariateRealFunction partialDerivativeY() {
+        if (partialDerivativeY == null) {
+            computePartialDerivatives();
+        }
+
+        return partialDerivativeY;
+    }
+    /**
+     * @return the second partial derivative wrt {@code x}.
+     */
+    public BivariateRealFunction partialDerivativeXX() {
+        if (partialDerivativeXX == null) {
+            computePartialDerivatives();
+        }
+
+        return partialDerivativeXX;
+    }
+    /**
+     * @return the second partial derivative wrt {@code y}.
+     */
+    public BivariateRealFunction partialDerivativeYY() {
+        if (partialDerivativeYY == null) {
+            computePartialDerivatives();
+        }
+
+        return partialDerivativeYY;
+    }
+    /**
+     * @return the second partial cross-derivative.
+     */
+    public BivariateRealFunction partialDerivativeXY() {
+        if (partialDerivativeXY == null) {
+            computePartialDerivatives();
+        }
+
+        return partialDerivativeXY;
+    }
+
+    /**
+     * Compute all partial derivatives functions.
+     */
+    private void computePartialDerivatives() {
+        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 BivariateRealFunction() {
+                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 BivariateRealFunction() {
+                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 BivariateRealFunction() {
+                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 BivariateRealFunction() {
+                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 BivariateRealFunction() {
+                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);
+                }
+            };
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/interpolation/BicubicSplineInterpolator.java b/src/main/java/org/apache/commons/math/analysis/interpolation/BicubicSplineInterpolator.java
new file mode 100644
index 0000000..42f73c8
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/interpolation/BicubicSplineInterpolator.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.math.analysis.interpolation;
+
+import org.apache.commons.math.DimensionMismatchException;
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.analysis.polynomials.PolynomialSplineFunction;
+import org.apache.commons.math.exception.NoDataException;
+import org.apache.commons.math.util.MathUtils;
+
+/**
+ * Generates a bicubic interpolating function.
+ *
+ * @version $Revision: 980944 $ $Date: 2010-07-30 22:31:11 +0200 (ven. 30 juil. 2010) $
+ * @since 2.2
+ */
+public class BicubicSplineInterpolator
+    implements BivariateRealGridInterpolator {
+    /**
+     * {@inheritDoc}
+     */
+    public BicubicSplineInterpolatingFunction interpolate(final double[] xval,
+                                                          final double[] yval,
+                                                          final double[][] fval)
+        throws MathException, IllegalArgumentException {
+        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);
+        }
+
+        MathUtils.checkOrder(xval);
+        MathUtils.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 UnivariateRealFunction 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 UnivariateRealFunction 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);
+    }
+
+    /**
+     * 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/math/analysis/interpolation/BivariateRealGridInterpolator.java b/src/main/java/org/apache/commons/math/analysis/interpolation/BivariateRealGridInterpolator.java
new file mode 100644
index 0000000..218d328
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/interpolation/BivariateRealGridInterpolator.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.math.analysis.interpolation;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.analysis.BivariateRealFunction;
+
+/**
+ * Interface representing a bivariate real interpolating function where the
+ * sample points must be specified on a regular grid.
+ *
+ * @version $Revision: 936391 $ $Date: 2010-04-21 19:00:56 +0200 (mer. 21 avril 2010) $
+ */
+public interface BivariateRealGridInterpolator {
+    /**
+     * Computes an interpolating function for the data set.
+     *
+     * @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 data set.
+     * @throws MathException if arguments violate assumptions made by the
+     *         interpolation algorithm.
+     */
+    BivariateRealFunction interpolate(double[] xval, double[] yval, double[][] fval)
+        throws MathException;
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/interpolation/DividedDifferenceInterpolator.java b/src/main/java/org/apache/commons/math/analysis/interpolation/DividedDifferenceInterpolator.java
new file mode 100644
index 0000000..9b80079
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/interpolation/DividedDifferenceInterpolator.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.math.analysis.interpolation;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.DuplicateSampleAbscissaException;
+import org.apache.commons.math.analysis.polynomials.PolynomialFunctionLagrangeForm;
+import org.apache.commons.math.analysis.polynomials.PolynomialFunctionNewtonForm;
+
+/**
+ * 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>
+ *
+ * @version $Revision: 825919 $ $Date: 2009-10-16 16:51:55 +0200 (ven. 16 oct. 2009) $
+ * @since 1.2
+ */
+public class DividedDifferenceInterpolator implements UnivariateRealInterpolator,
+    Serializable {
+
+    /** serializable version identifier */
+    private static final long serialVersionUID = 107049519551235069L;
+
+    /**
+     * Computes an interpolating function for the data set.
+     *
+     * @param x the interpolating points array
+     * @param y the interpolating values array
+     * @return a function which interpolates the data set
+     * @throws DuplicateSampleAbscissaException if arguments are invalid
+     */
+    public PolynomialFunctionNewtonForm interpolate(double x[], double y[]) throws
+        DuplicateSampleAbscissaException {
+
+        /**
+         * 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);
+
+        /**
+         * 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);
+
+    }
+
+    /**
+     * Returns 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>
+     * <p>
+     * The computational complexity is O(N^2).</p>
+     *
+     * @param x the interpolating points array
+     * @param y the interpolating values array
+     * @return a fresh copy of the divided difference array
+     * @throws DuplicateSampleAbscissaException if any abscissas coincide
+     */
+    protected static double[] computeDividedDifference(final double x[], final double y[])
+        throws DuplicateSampleAbscissaException {
+
+        PolynomialFunctionLagrangeForm.verifyInterpolationArray(x, y);
+
+        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];
+                if (denominator == 0.0) {
+                    // This happens only when two abscissas are identical.
+                    throw new DuplicateSampleAbscissaException(x[j], j, j+i);
+                }
+                divdiff[j] = (divdiff[j+1] - divdiff[j]) / denominator;
+            }
+            a[i] = divdiff[0];
+        }
+
+        return a;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/interpolation/LinearInterpolator.java b/src/main/java/org/apache/commons/math/analysis/interpolation/LinearInterpolator.java
new file mode 100644
index 0000000..71ab9a9
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/interpolation/LinearInterpolator.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.math.analysis.interpolation;
+
+import org.apache.commons.math.exception.DimensionMismatchException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.exception.NumberIsTooSmallException;
+import org.apache.commons.math.analysis.polynomials.PolynomialFunction;
+import org.apache.commons.math.analysis.polynomials.PolynomialSplineFunction;
+import org.apache.commons.math.util.MathUtils;
+
+/**
+ * Implements a linear function for interpolation of real univariate functions.
+ * @version $Revision$ $Date$
+ * @since 2.2
+ */
+public class LinearInterpolator implements UnivariateRealInterpolator {
+    /**
+     * 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 org.apache.commons.math.exception.NonMonotonousSequenceException
+     * 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[]) {
+        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;
+
+        MathUtils.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]);
+        }
+
+        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/math/analysis/interpolation/LoessInterpolator.java b/src/main/java/org/apache/commons/math/analysis/interpolation/LoessInterpolator.java
new file mode 100644
index 0000000..5f00e14
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/interpolation/LoessInterpolator.java
@@ -0,0 +1,463 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.analysis.interpolation;
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.analysis.polynomials.PolynomialSplineFunction;
+import org.apache.commons.math.exception.util.Localizable;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Implements the <a href="http://en.wikipedia.org/wiki/Local_regression">
+ * Local Regression Algorithm</a> (also Loess, Lowess) for interpolation of
+ * real univariate functions.
+ * <p/>
+ * For reference, see
+ * <a href="http://www.math.tau.ac.il/~yekutiel/MA seminar/Cleveland 1979.pdf">
+ * William S. Cleveland - Robust Locally Weighted Regression and Smoothing
+ * Scatterplots</a>
+ * <p/>
+ * This class implements both the loess method and serves as an interpolation
+ * adapter to it, allowing to build a spline on the obtained loess fit.
+ *
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ * @since 2.0
+ */
+public class LoessInterpolator
+        implements UnivariateRealInterpolator, 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.
+     */
+    private final double bandwidth;
+
+    /**
+     * The number of robustness iterations parameter: this many
+     * robustness iterations are done.
+     * <p/>
+     * A sensible value is usually 0 (just the initial fit without any
+     * robustness iterations) to 4.
+     */
+    private final int robustnessIters;
+
+    /**
+     * 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;
+    }
+
+    /**
+     * Constructs 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.</br>
+     * A sensible value is usually 0.25 to 0.5, the default value is
+     * {@link #DEFAULT_BANDWIDTH}.
+     * @param robustnessIters This many robustness iterations are done.</br>
+     * A sensible value is usually 0 (just the initial fit without any
+     * robustness iterations) to 4, the default value is
+     * {@link #DEFAULT_ROBUSTNESS_ITERS}.
+     * @throws MathException if bandwidth does not lie in the interval [0,1]
+     * or if robustnessIters is negative.
+     * @see #LoessInterpolator(double, int, double)
+     */
+    public LoessInterpolator(double bandwidth, int robustnessIters) throws MathException {
+        this(bandwidth, robustnessIters, DEFAULT_ACCURACY);
+    }
+
+    /**
+     * Constructs 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.</br>
+     * A sensible value is usually 0.25 to 0.5, the default value is
+     * {@link #DEFAULT_BANDWIDTH}.
+     * @param robustnessIters This many robustness iterations are done.</br>
+     * A sensible value is usually 0 (just the initial fit without any
+     * robustness iterations) to 4, the default value is
+     * {@link #DEFAULT_ROBUSTNESS_ITERS}.
+     * @param accuracy If the median residual at a certain robustness iteration
+     * is less than this amount, no more iterations are done.
+     * @throws MathException if bandwidth does not lie in the interval [0,1]
+     * or if robustnessIters is negative.
+     * @see #LoessInterpolator(double, int)
+     * @since 2.1
+     */
+    public LoessInterpolator(double bandwidth, int robustnessIters, double accuracy) throws MathException {
+        if (bandwidth < 0 || bandwidth > 1) {
+            throw new MathException(LocalizedFormats.BANDWIDTH_OUT_OF_INTERVAL,
+                                    bandwidth);
+        }
+        this.bandwidth = bandwidth;
+        if (robustnessIters < 0) {
+            throw new MathException(LocalizedFormats.NEGATIVE_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.math.analysis.interpolation.SplineInterpolator}
+     * on the resulting fit.
+     *
+     * @param xval the arguments for the interpolation points
+     * @param yval the values for the interpolation points
+     * @return A cubic spline built upon a loess fit to the data at the original abscissae
+     * @throws MathException  if some of the following conditions are false:
+     * <ul>
+     * <li> Arguments and values are of the same size that is greater than zero</li>
+     * <li> The arguments are in a strictly increasing order</li>
+     * <li> All arguments and values are finite real numbers</li>
+     * </ul>
+     */
+    public final PolynomialSplineFunction interpolate(
+            final double[] xval, final double[] yval) throws MathException {
+        return new SplineInterpolator().interpolate(xval, smooth(xval, yval));
+    }
+
+    /**
+     * Compute a weighted 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
+     * @param weights point weights: coefficients by which the robustness weight of a point is multiplied
+     * @return values of the loess fit at corresponding original abscissae
+     * @throws MathException if some of the following conditions are false:
+     * <ul>
+     * <li> Arguments and values are of the same size that is greater than zero</li>
+     * <li> The arguments are in a strictly increasing order</li>
+     * <li> All arguments and values are finite real numbers</li>
+     * </ul>
+     * @since 2.1
+     */
+    public final double[] smooth(final double[] xval, final double[] yval, final double[] weights)
+            throws MathException {
+        if (xval.length != yval.length) {
+            throw new MathException(LocalizedFormats.MISMATCHED_LOESS_ABSCISSA_ORDINATE_ARRAYS,
+                                    xval.length, yval.length);
+        }
+
+        final int n = xval.length;
+
+        if (n == 0) {
+            throw new MathException(LocalizedFormats.LOESS_EXPECTS_AT_LEAST_ONE_POINT);
+        }
+
+        checkAllFiniteReal(xval, LocalizedFormats.NON_REAL_FINITE_ABSCISSA);
+        checkAllFiniteReal(yval, LocalizedFormats.NON_REAL_FINITE_ORDINATE);
+        checkAllFiniteReal(weights, LocalizedFormats.NON_REAL_FINITE_WEIGHT);
+
+        checkStrictlyIncreasing(xval);
+
+        if (n == 1) {
+            return new double[]{yval[0]};
+        }
+
+        if (n == 2) {
+            return new double[]{yval[0], yval[1]};
+        }
+
+        int bandwidthInPoints = (int) (bandwidth * n);
+
+        if (bandwidthInPoints < 2) {
+            throw new MathException(LocalizedFormats.TOO_SMALL_BANDWIDTH,
+                                    n, 2.0 / n, bandwidth);
+        }
+
+        final double[] res = new double[n];
+
+        final double[] residuals = new double[n];
+        final double[] sortedResiduals = new double[n];
+
+        final double[] robustnessWeights = new double[n];
+
+        // Do an initial fit and 'robustnessIters' robustness iterations.
+        // This is equivalent to doing 'robustnessIters+1' robustness iterations
+        // starting with all robustness weights set to 1.
+        Arrays.fill(robustnessWeights, 1);
+
+        for (int iter = 0; iter <= robustnessIters; ++iter) {
+            final int[] bandwidthInterval = {0, bandwidthInPoints - 1};
+            // At each x, compute a local weighted linear regression
+            for (int i = 0; i < n; ++i) {
+                final double x = xval[i];
+
+                // Find out the interval of source points on which
+                // a regression is to be made.
+                if (i > 0) {
+                    updateBandwidthInterval(xval, 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 MathException if some of the following conditions are false:
+     * <ul>
+     * <li> Arguments and values are of the same size that is greater than zero</li>
+     * <li> The arguments are in a strictly increasing order</li>
+     * <li> All arguments and values are finite real numbers</li>
+     * </ul>
+     */
+    public final double[] smooth(final double[] xval, final double[] yval)
+            throws MathException {
+        if (xval.length != yval.length) {
+            throw new MathException(LocalizedFormats.MISMATCHED_LOESS_ABSCISSA_ORDINATE_ARRAYS,
+                                    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 xval[i-1], update the interval so that it embraces
+     * the same number of points closest to xval[i], ignoring zero weights.
+     *
+     * @param xval arguments array
+     * @param weights weights array
+     * @param i the index around which the new interval should be computed
+     * @param bandwidthInterval a two-element array {left, right} such that: <p/>
+     * <tt>(left==0 or xval[i] - xval[left-1] > xval[right] - xval[i])</tt>
+     * <p/> and also <p/>
+     * <tt>(right==xval.length-1 or xval[right+1] - xval[i] > xval[i] - xval[left])</tt>.
+     * The array will be updated.
+     */
+    private static void updateBandwidthInterval(final double[] xval, final 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;
+        }
+    }
+
+    /**
+     * Returns the smallest index j such that j > i && (j==weights.length || weights[j] != 0)
+     * @param weights weights array
+     * @param i the index from which to start search; must be < weights.length
+     * @return the smallest index j such that j > i && (j==weights.length || weights[j] != 0)
+     */
+    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 the argument
+     * @return (1-|x|^3)^3
+     */
+    private static double tricube(final double x) {
+        final double tmp = 1 - x * x * x;
+        return tmp * tmp * tmp;
+    }
+
+    /**
+     * Check that all elements of an array are finite real numbers.
+     *
+     * @param values the values array
+     * @param pattern pattern of the error message
+     * @throws MathException if one of the values is not a finite real number
+     */
+    private static void checkAllFiniteReal(final double[] values, final Localizable pattern)
+        throws MathException {
+        for (int i = 0; i < values.length; i++) {
+            final double x = values[i];
+            if (Double.isInfinite(x) || Double.isNaN(x)) {
+                throw new MathException(pattern, i, x);
+            }
+        }
+    }
+
+    /**
+     * Check that elements of the abscissae array are in a strictly
+     * increasing order.
+     *
+     * @param xval the abscissae array
+     * @throws MathException if the abscissae array
+     * is not in a strictly increasing order
+     */
+    private static void checkStrictlyIncreasing(final double[] xval)
+        throws MathException {
+        for (int i = 0; i < xval.length; ++i) {
+            if (i >= 1 && xval[i - 1] >= xval[i]) {
+                throw new MathException(LocalizedFormats.OUT_OF_ORDER_ABSCISSA_ARRAY,
+                                        i - 1, xval[i - 1], i, xval[i]);
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/interpolation/MicrosphereInterpolatingFunction.java b/src/main/java/org/apache/commons/math/analysis/interpolation/MicrosphereInterpolatingFunction.java
new file mode 100644
index 0000000..a710e82
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/interpolation/MicrosphereInterpolatingFunction.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.math.analysis.interpolation;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.math.DimensionMismatchException;
+import org.apache.commons.math.analysis.MultivariateRealFunction;
+import org.apache.commons.math.exception.NoDataException;
+import org.apache.commons.math.linear.ArrayRealVector;
+import org.apache.commons.math.linear.RealVector;
+import org.apache.commons.math.random.UnitSphereRandomVectorGenerator;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Interpolating function that implements the
+ * <a href="http://www.dudziak.com/microsphere.php">Microsphere Projection</a>.
+ *
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ */
+public class MicrosphereInterpolatingFunction
+    implements MultivariateRealFunction {
+    /**
+     * 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 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 dimension} is thus the dimension of the sampled
+     * space).
+     * @param yval the 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 are no data (xval null or zero length)
+     */
+    public MicrosphereInterpolatingFunction(double[][] xval,
+                                            double[] yval,
+                                            int brightnessExponent,
+                                            int microsphereElements,
+                                            UnitSphereRandomVectorGenerator rand)
+        throws DimensionMismatchException, NoDataException {
+        if (xval.length == 0 || xval[0] == null) {
+            throw new NoDataException();
+        }
+
+        if (xval.length != yval.length) {
+            throw new DimensionMismatchException(xval.length, yval.length);
+        }
+
+        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.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.
+     */
+    public double value(double[] point) {
+
+        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 cosine of the angle
+     */
+    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/math/analysis/interpolation/MicrosphereInterpolator.java b/src/main/java/org/apache/commons/math/analysis/interpolation/MicrosphereInterpolator.java
new file mode 100644
index 0000000..c2a4009
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/interpolation/MicrosphereInterpolator.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.math.analysis.interpolation;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.analysis.MultivariateRealFunction;
+import org.apache.commons.math.exception.NotPositiveException;
+import org.apache.commons.math.exception.NotStrictlyPositiveException;
+import org.apache.commons.math.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
+ *
+ * @version $Revision: 980944 $ $Date: 2010-07-30 22:31:11 +0200 (ven. 30 juil. 2010) $
+ */
+public class MicrosphereInterpolator
+    implements MultivariateRealInterpolator {
+
+    /**
+     * 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 int microsphereElements;
+
+    /**
+     * Exponent used in the power law that computes the weights of the
+     * sample data.
+     */
+    private int brightnessExponent;
+
+    /** Create a microsphere interpolator with default settings.
+     * <p>Calling this constructor is equivalent to call {@link
+     * #MicrosphereInterpolator(int, int)
+     * MicrosphereInterpolator(MicrosphereInterpolator.DEFAULT_MICROSPHERE_ELEMENTS,
+     * MicrosphereInterpolator.DEFAULT_BRIGHTNESS_EXPONENT)}.</p>
+     */
+    public MicrosphereInterpolator() {
+        this(DEFAULT_MICROSPHERE_ELEMENTS, DEFAULT_BRIGHTNESS_EXPONENT);
+    }
+
+    /** Create a microsphere interpolator.
+     * @param microsphereElements number of surface elements of the microsphere.
+     * @param brightnessExponent exponent used in the power law that computes the
+     * weights of the sample data.
+     * @throws NotPositiveException if {@code microsphereElements <= 0}
+     * or {@code brightnessExponent < 0}.
+     */
+    public MicrosphereInterpolator(final int microsphereElements,
+                                   final int brightnessExponent) {
+        setMicropshereElements(microsphereElements);
+        setBrightnessExponent(brightnessExponent);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public MultivariateRealFunction interpolate(final double[][] xval,
+                                                final double[] yval)
+        throws MathException, IllegalArgumentException {
+        final UnitSphereRandomVectorGenerator rand
+            = new UnitSphereRandomVectorGenerator(xval[0].length);
+        return new MicrosphereInterpolatingFunction(xval, yval,
+                                                    brightnessExponent,
+                                                    microsphereElements,
+                                                    rand);
+    }
+
+    /**
+     * Set the brightness exponent.
+     * @param exponent Exponent for computing the distance dimming
+     * factor.
+     * @throws NotPositiveException if {@code exponent < 0}.
+     */
+    public void setBrightnessExponent(final int exponent) {
+        if (exponent < 0) {
+            throw new NotPositiveException(exponent);
+        }
+        brightnessExponent = exponent;
+    }
+
+    /**
+     * Set the number of microsphere elements.
+     * @param elements Number of surface elements of the microsphere.
+     * @throws NotStrictlyPositiveException if {@code elements <= 0}.
+     */
+    public void setMicropshereElements(final int elements) {
+        if (elements <= 0) {
+            throw new NotStrictlyPositiveException(elements);
+        }
+        microsphereElements = elements;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/interpolation/MultivariateRealInterpolator.java b/src/main/java/org/apache/commons/math/analysis/interpolation/MultivariateRealInterpolator.java
new file mode 100644
index 0000000..ed7690c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/interpolation/MultivariateRealInterpolator.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.math.analysis.interpolation;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.analysis.MultivariateRealFunction;
+
+/**
+ * Interface representing a univariate real interpolating function.
+ *
+ * @since 2.1
+ * @version $Revision: 924794 $ $Date: 2010-03-18 15:15:50 +0100 (jeu. 18 mars 2010) $
+ */
+public interface MultivariateRealInterpolator {
+
+    /**
+     * 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 MathException if arguments violate assumptions made by the
+     *         interpolation algorithm or some dimension mismatch occurs
+     * @throws IllegalArgumentException if there are no data (xval null or zero length)
+     */
+    MultivariateRealFunction interpolate(double[][] xval, double[] yval)
+        throws MathException, IllegalArgumentException;
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/interpolation/NevilleInterpolator.java b/src/main/java/org/apache/commons/math/analysis/interpolation/NevilleInterpolator.java
new file mode 100644
index 0000000..27e89cd
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/interpolation/NevilleInterpolator.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.math.analysis.interpolation;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.analysis.polynomials.PolynomialFunctionLagrangeForm;
+
+/**
+ * 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 evalution is in PolynomialFunctionLagrangeForm,
+ * this class provides an easy-to-use interface to it.</p>
+ *
+ * @version $Revision: 799857 $ $Date: 2009-08-01 15:07:12 +0200 (sam. 01 août 2009) $
+ * @since 1.2
+ */
+public class NevilleInterpolator implements UnivariateRealInterpolator,
+    Serializable {
+
+    /** serializable version identifier */
+    static final long serialVersionUID = 3003707660147873733L;
+
+    /**
+     * Computes an interpolating function for the data set.
+     *
+     * @param x the interpolating points array
+     * @param y the interpolating values array
+     * @return a function which interpolates the data set
+     * @throws MathException if arguments are invalid
+     */
+    public PolynomialFunctionLagrangeForm interpolate(double x[], double y[])
+        throws MathException {
+        return new PolynomialFunctionLagrangeForm(x, y);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/interpolation/SmoothingBicubicSplineInterpolator.java b/src/main/java/org/apache/commons/math/analysis/interpolation/SmoothingBicubicSplineInterpolator.java
new file mode 100644
index 0000000..5514433
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/interpolation/SmoothingBicubicSplineInterpolator.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.math.analysis.interpolation;
+
+import org.apache.commons.math.DimensionMismatchException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.util.MathUtils;
+import org.apache.commons.math.util.MathUtils.OrderDirection;
+import org.apache.commons.math.analysis.BivariateRealFunction;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.analysis.polynomials.PolynomialSplineFunction;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * Generates a bicubic interpolation function.
+ * Before interpolating, smoothing of the input data is performed using
+ * splines.
+ * See <b>Handbook on splines for the user</b>, ISBN 084939404X,
+ * chapter 2.
+ *
+ * @version $Revision: 1059400 $ $Date: 2011-01-15 20:35:27 +0100 (sam. 15 janv. 2011) $
+ * @since 2.1
+ * @deprecated This class does not perform smoothing; the name is thus misleading.
+ * Please use {@link org.apache.commons.math.analysis.interpolation.BicubicSplineInterpolator}
+ * instead. If smoothing is desired, a tentative implementation is provided in class
+ * {@link org.apache.commons.math.analysis.interpolation.SmoothingPolynomialBicubicSplineInterpolator}.
+ * This class will be removed in math 3.0.
+ */
+@Deprecated
+public class SmoothingBicubicSplineInterpolator
+    implements BivariateRealGridInterpolator {
+    /**
+     * {@inheritDoc}
+     */
+    public BivariateRealFunction interpolate(final double[] xval,
+                                                          final double[] yval,
+                                                          final double[][] zval)
+        throws MathException, IllegalArgumentException {
+        if (xval.length == 0 || yval.length == 0 || zval.length == 0) {
+            throw MathRuntimeException.createIllegalArgumentException(LocalizedFormats.NO_DATA);
+        }
+        if (xval.length != zval.length) {
+            throw new DimensionMismatchException(xval.length, zval.length);
+        }
+
+        MathUtils.checkOrder(xval, OrderDirection.INCREASING, true);
+        MathUtils.checkOrder(yval, OrderDirection.INCREASING, true);
+
+        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
+        // zX[j][i] = f(xval[i], yval[j])
+        final double[][] zX = new double[yLen][xLen];
+        for (int i = 0; i < xLen; i++) {
+            if (zval[i].length != yLen) {
+                throw new DimensionMismatchException(zval[i].length, yLen);
+            }
+
+            for (int j = 0; j < yLen; j++) {
+                zX[j][i] = zval[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, zX[j]);
+        }
+
+        // For every knot (xval[i], yval[j]) of the grid, calculate corrected
+        // values zY_1
+        final double[][] zY_1 = new double[xLen][yLen];
+        for (int j = 0; j < yLen; j++) {
+            final PolynomialSplineFunction f = ySplineX[j];
+            for (int i = 0; i < xLen; i++) {
+                zY_1[i][j] = f.value(xval[i]);
+            }
+        }
+
+        // For each line x[i] (0 <= i < xLen), construct a 1D spline with
+        // respect to variable y generated by array zY_1[i]
+        final PolynomialSplineFunction[] xSplineY = new PolynomialSplineFunction[xLen];
+        for (int i = 0; i < xLen; i++) {
+            xSplineY[i] = spInterpolator.interpolate(yval, zY_1[i]);
+        }
+
+        // For every knot (xval[i], yval[j]) of the grid, calculate corrected
+        // values zY_2
+        final double[][] zY_2 = new double[xLen][yLen];
+        for (int i = 0; i < xLen; i++) {
+            final PolynomialSplineFunction f = xSplineY[i];
+            for (int j = 0; j < yLen; j++) {
+                zY_2[i][j] = f.value(yval[j]);
+            }
+        }
+
+        // Partial derivatives with respect to x at the grid knots
+        final double[][] dZdX = new double[xLen][yLen];
+        for (int j = 0; j < yLen; j++) {
+            final UnivariateRealFunction f = ySplineX[j].derivative();
+            for (int i = 0; i < xLen; i++) {
+                dZdX[i][j] = f.value(xval[i]);
+            }
+        }
+
+        // Partial derivatives with respect to y at the grid knots
+        final double[][] dZdY = new double[xLen][yLen];
+        for (int i = 0; i < xLen; i++) {
+            final UnivariateRealFunction f = xSplineY[i].derivative();
+            for (int j = 0; j < yLen; j++) {
+                dZdY[i][j] = f.value(yval[j]);
+            }
+        }
+
+        // Cross partial derivatives
+        final double[][] dZdXdY = 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);
+                dZdXdY[i][j] = (zY_2[nI][nJ] - zY_2[nI][pJ] -
+                                zY_2[pI][nJ] + zY_2[pI][pJ]) /
+                    ((xval[nI] - xval[pI]) * (yval[nJ] - yval[pJ]));
+            }
+        }
+
+        // Create the interpolating splines
+        return new BicubicSplineInterpolatingFunction(xval, yval, zY_2,
+                                                      dZdX, dZdY, dZdXdY);
+    }
+
+    /**
+     * 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/math/analysis/interpolation/SmoothingPolynomialBicubicSplineInterpolator.java b/src/main/java/org/apache/commons/math/analysis/interpolation/SmoothingPolynomialBicubicSplineInterpolator.java
new file mode 100644
index 0000000..406d603
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/interpolation/SmoothingPolynomialBicubicSplineInterpolator.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.math.analysis.interpolation;
+
+import org.apache.commons.math.exception.DimensionMismatchException;
+import org.apache.commons.math.exception.NoDataException;
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.util.MathUtils;
+import org.apache.commons.math.optimization.general.GaussNewtonOptimizer;
+import org.apache.commons.math.optimization.fitting.PolynomialFitter;
+import org.apache.commons.math.analysis.polynomials.PolynomialFunction;
+
+/**
+ * Generates a bicubic interpolation function.
+ * Prior to generating the interpolating function, the input is smoothed using
+ * polynomial fitting.
+ *
+ * @version $Revision: 1003892 $ $Date: 2010-10-02 23:28:56 +0200 (sam. 02 oct. 2010) $
+ * @since 2.2
+ */
+public class SmoothingPolynomialBicubicSplineInterpolator
+    extends BicubicSplineInterpolator {
+
+    /** Fitter for x. */
+    private final PolynomialFitter xFitter;
+
+    /** Fitter for y. */
+    private final PolynomialFitter yFitter;
+
+    /**
+     * Default constructor. The degree of the fitting polynomials is set to 3.
+     */
+    public SmoothingPolynomialBicubicSplineInterpolator() {
+        this(3);
+    }
+
+    /**
+     * @param degree Degree of the polynomial fitting functions.
+     */
+    public SmoothingPolynomialBicubicSplineInterpolator(int degree) {
+        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.
+     */
+    public SmoothingPolynomialBicubicSplineInterpolator(int xDegree,
+                                                        int yDegree) {
+        xFitter = new PolynomialFitter(xDegree, new GaussNewtonOptimizer(false));
+        yFitter = new PolynomialFitter(yDegree, new GaussNewtonOptimizer(false));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public BicubicSplineInterpolatingFunction interpolate(final double[] xval,
+                                                          final double[] yval,
+                                                          final double[][] fval)
+        throws MathException {
+        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);
+            }
+        }
+
+        MathUtils.checkOrder(xval);
+        MathUtils.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]);
+            }
+
+            yPolyX[j] = xFitter.fit();
+        }
+
+        // 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]);
+            }
+
+            xPolyY[i] = yFitter.fit();
+        }
+
+        // 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/math/analysis/interpolation/SplineInterpolator.java b/src/main/java/org/apache/commons/math/analysis/interpolation/SplineInterpolator.java
new file mode 100644
index 0000000..f25ba83
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/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.math.analysis.interpolation;
+
+import org.apache.commons.math.exception.DimensionMismatchException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.exception.NumberIsTooSmallException;
+import org.apache.commons.math.analysis.polynomials.PolynomialFunction;
+import org.apache.commons.math.analysis.polynomials.PolynomialSplineFunction;
+import org.apache.commons.math.util.MathUtils;
+
+/**
+ * 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,
+ * x[0] < x[i] ... < x[n].  The x values are referred to as "knot points."</p>
+ * <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>
+ * <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>
+ *
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ *
+ */
+public class SplineInterpolator implements UnivariateRealInterpolator {
+
+    /**
+     * 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 org.apache.commons.math.exception.NonMonotonousSequenceException
+     * 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[]) {
+        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.
+        int n = x.length - 1;
+
+        MathUtils.checkOrder(x);
+
+        // Differences between knot points
+        double h[] = new double[n];
+        for (int i = 0; i < n; i++) {
+            h[i] = x[i + 1] - x[i];
+        }
+
+        double mu[] = new double[n];
+        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)
+        double b[] = new double[n];
+        double c[] = new double[n + 1];
+        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]);
+        }
+
+        PolynomialFunction polynomials[] = new PolynomialFunction[n];
+        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/math/analysis/interpolation/TricubicSplineInterpolatingFunction.java b/src/main/java/org/apache/commons/math/analysis/interpolation/TricubicSplineInterpolatingFunction.java
new file mode 100644
index 0000000..ea435a3
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/interpolation/TricubicSplineInterpolatingFunction.java
@@ -0,0 +1,483 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.analysis.interpolation;
+
+import org.apache.commons.math.analysis.TrivariateRealFunction;
+import org.apache.commons.math.exception.DimensionMismatchException;
+import org.apache.commons.math.exception.NoDataException;
+import org.apache.commons.math.exception.OutOfRangeException;
+import org.apache.commons.math.util.MathUtils;
+
+/**
+ * Function that implements the
+ * <a href="http://en.wikipedia.org/wiki/Tricubic_interpolation">
+ * tricubic spline interpolation</a>, as proposed in
+ * <quote>
+ *  Tricubic interpolation in three dimensions<br/>
+ *  F. Lekien and J. Marsden<br/>
+ *  <em>Int. J. Numer. Meth. Engng</em> 2005; <b>63</b>:455-471
+ * </quote>
+ *
+ * @version $Revision$ $Date$
+ * @since 2.2
+ */
+public class TricubicSplineInterpolatingFunction
+    implements TrivariateRealFunction {
+    /**
+     * 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 IllegalArgumentException 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) {
+        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);
+        }
+
+        MathUtils.checkOrder(x);
+        MathUtils.checkOrder(y);
+        MathUtils.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}
+     */
+    public double value(double x, double y, double z) {
+        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.
+ *
+ * @version $Revision$ $Date$
+ */
+class TricubicSplineFunction
+    implements TrivariateRealFunction {
+    /** 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.
+     */
+    public 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.
+     */
+    public double value(double x, double y, double z) {
+        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/math/analysis/interpolation/TricubicSplineInterpolator.java b/src/main/java/org/apache/commons/math/analysis/interpolation/TricubicSplineInterpolator.java
new file mode 100644
index 0000000..dac7f26
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/interpolation/TricubicSplineInterpolator.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.math.analysis.interpolation;
+
+import org.apache.commons.math.exception.DimensionMismatchException;
+import org.apache.commons.math.exception.NoDataException;
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.util.MathUtils;
+
+/**
+ * Generates a tricubic interpolating function.
+ *
+ * @version $Revision$ $Date$
+ * @since 2.2
+ */
+public class TricubicSplineInterpolator
+    implements TrivariateRealGridInterpolator {
+    /**
+     * {@inheritDoc}
+     */
+    public TricubicSplineInterpolatingFunction interpolate(final double[] xval,
+                                                           final double[] yval,
+                                                           final double[] zval,
+                                                           final double[][][] fval)
+        throws MathException {
+        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);
+        }
+
+        MathUtils.checkOrder(xval);
+        MathUtils.checkOrder(yval);
+        MathUtils.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();
+
+        // 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/math/analysis/interpolation/TrivariateRealGridInterpolator.java b/src/main/java/org/apache/commons/math/analysis/interpolation/TrivariateRealGridInterpolator.java
new file mode 100644
index 0000000..c42d7aa
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/interpolation/TrivariateRealGridInterpolator.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.math.analysis.interpolation;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.analysis.TrivariateRealFunction;
+
+/**
+ * Interface representing a trivariate real interpolating function where the
+ * sample points must be specified on a regular grid.
+ *
+ * @version $Revision$ $Date$
+ * @since 2.2
+ */
+public interface TrivariateRealGridInterpolator {
+    /**
+     * Computes an interpolating function for the data set.
+     *
+     * @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 org.apache.commons.math.exception.NoDataException if any of the arrays has zero length.
+     * @throws org.apache.commons.math.exception.DimensionMismatchException if the array lengths are inconsistent.
+     * @throws MathException if arguments violate assumptions made by the
+     *         interpolation algorithm.
+     */
+    TrivariateRealFunction interpolate(double[] xval, double[] yval, double[] zval, double[][][] fval)
+        throws MathException;
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/interpolation/UnivariateRealInterpolator.java b/src/main/java/org/apache/commons/math/analysis/interpolation/UnivariateRealInterpolator.java
new file mode 100644
index 0000000..09f5487
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/interpolation/UnivariateRealInterpolator.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.math.analysis.interpolation;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+
+/**
+ * Interface representing a univariate real interpolating function.
+ *
+ * @version $Revision: 821626 $ $Date: 2009-10-04 23:57:30 +0200 (dim. 04 oct. 2009) $
+ */
+public interface UnivariateRealInterpolator {
+
+    /**
+     * Computes an interpolating function for the data set.
+     * @param xval the arguments for the interpolation points
+     * @param yval the values for the interpolation points
+     * @return a function which interpolates the data set
+     * @throws MathException if arguments violate assumptions made by the
+     *         interpolation algorithm
+     */
+    UnivariateRealFunction interpolate(double xval[], double yval[])
+        throws MathException;
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/interpolation/package.html b/src/main/java/org/apache/commons/math/analysis/interpolation/package.html
new file mode 100644
index 0000000..136c576
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/interpolation/package.html
@@ -0,0 +1,22 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $ -->
+    <body>
+     Univariate real functions interpolation algorithms.
+    </body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/analysis/package.html b/src/main/java/org/apache/commons/math/analysis/package.html
new file mode 100644
index 0000000..63b64a8
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/package.html
@@ -0,0 +1,33 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $ -->
+  <body>
+    <p>
+      Parent package for common numerical analysis procedures, including root finding,
+      function interpolation and integration. Note that the optimization (i.e. minimization
+      and maximization) is a huge separate top package, despite it also operate on functions
+      as defined by this top-level package.
+    </p>
+    <p>
+      Functions interfaces are intended to be implemented by user code to represent their
+      domain problems. The algorithms provided by the library will then operate on these
+      function 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.
+    </p>
+  </body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/analysis/polynomials/PolynomialFunction.java b/src/main/java/org/apache/commons/math/analysis/polynomials/PolynomialFunction.java
new file mode 100644
index 0000000..ec528b4
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/polynomials/PolynomialFunction.java
@@ -0,0 +1,350 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.analysis.polynomials;
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.exception.NoDataException;
+import org.apache.commons.math.analysis.DifferentiableUnivariateRealFunction;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * 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>
+ *
+ * @version $Revision: 1042376 $ $Date: 2010-12-05 16:54:55 +0100 (dim. 05 déc. 2010) $
+ */
+public class PolynomialFunction implements DifferentiableUnivariateRealFunction, 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 NullPointerException if c is null
+     * @throws NoDataException if c is empty
+     */
+    public PolynomialFunction(double c[]) {
+        super();
+        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 <br>
+     *   <code>coefficients[n] * x^n + ... + coefficients[1] * x  + coefficients[0]</code>
+     * </p>
+     *
+     * @param x the argument for which the function value should be computed
+     * @return the value of the polynomial at the given point
+     * @see UnivariateRealFunction#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  the coefficients of the polynomial to evaluate
+     * @param argument  the input value
+     * @return  the value of the polynomial
+     * @throws NoDataException if coefficients is empty
+     * @throws NullPointerException if coefficients is null
+     */
+    protected static double evaluate(double[] coefficients, double argument) {
+        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;
+    }
+
+    /**
+     * Add a polynomial to the instance.
+     * @param p polynomial to add
+     * @return a new polynomial which is the sum of the instance and 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 difference the instance minus 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
+     */
+    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
+     */
+    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  the coefficients of the polynomial to differentiate
+     * @return the coefficients of the derivative or null if coefficients has length 1.
+     * @throws NoDataException if coefficients is empty
+     * @throws NullPointerException if coefficients is null
+     */
+    protected static double[] differentiate(double[] 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 PolynomialRealFunction
+     *
+     * @return  the derivative polynomial
+     */
+    public PolynomialFunction polynomialDerivative() {
+        return new PolynomialFunction(differentiate(coefficients));
+    }
+
+    /**
+     * Returns the derivative as a UnivariateRealFunction
+     *
+     * @return  the derivative function
+     */
+    public UnivariateRealFunction 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(Double.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(Double.toString(absAi));
+             s.append(' ');
+           }
+
+           s.append("x");
+           if (i > 1) {
+             s.append('^');
+             s.append(Integer.toString(i));
+           }
+         }
+
+       }
+
+       return s.toString();
+
+     }
+
+    /** {@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;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/polynomials/PolynomialFunctionLagrangeForm.java b/src/main/java/org/apache/commons/math/analysis/polynomials/PolynomialFunctionLagrangeForm.java
new file mode 100644
index 0000000..b40bf60
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/polynomials/PolynomialFunctionLagrangeForm.java
@@ -0,0 +1,305 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.analysis.polynomials;
+
+import org.apache.commons.math.DuplicateSampleAbscissaException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * 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>
+ *
+ * @version $Revision: 1073498 $ $Date: 2011-02-22 21:57:26 +0100 (mar. 22 févr. 2011) $
+ * @since 1.2
+ */
+public class PolynomialFunctionLagrangeForm implements UnivariateRealFunction {
+
+    /**
+     * 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 IllegalArgumentException if input arrays are not valid
+     */
+    public PolynomialFunctionLagrangeForm(double x[], double y[])
+        throws IllegalArgumentException {
+
+        verifyInterpolationArray(x, y);
+        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;
+    }
+
+    /** {@inheritDoc} */
+    public double value(double z) throws FunctionEvaluationException {
+        try {
+            return evaluate(x, y, z);
+        } catch (DuplicateSampleAbscissaException e) {
+            throw new FunctionEvaluationException(z, e.getSpecificPattern(), e.getGeneralPattern(), e.getArguments());
+        }
+    }
+
+    /**
+     * 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.
+     * <p>
+     * This function is made public static so that users can call it directly
+     * without instantiating PolynomialFunctionLagrangeForm object.</p>
+     *
+     * @param x the interpolating points array
+     * @param y the interpolating values array
+     * @param z the point at which the function value is to be computed
+     * @return the function value
+     * @throws DuplicateSampleAbscissaException if the sample has duplicate abscissas
+     * @throws IllegalArgumentException if inputs are not valid
+     */
+    public static double evaluate(double x[], double y[], double z) throws
+        DuplicateSampleAbscissaException, IllegalArgumentException {
+
+        verifyInterpolationArray(x, y);
+
+        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];
+                if (divider == 0.0) {
+                    // This happens only when two abscissas are identical.
+                    throw new DuplicateSampleAbscissaException(x[i], i, 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.
+     * <p>
+     * Note this computation can be ill-conditioned. Use with caution
+     * and only when it is necessary.</p>
+     *
+     * @throws ArithmeticException if any abscissas coincide
+     */
+    protected void computeCoefficients() throws ArithmeticException {
+
+        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];
+                }
+            }
+            if (d == 0.0) {
+                // This happens only when two abscissas are identical.
+                for (int k = 0; k < n; ++k) {
+                    if ((i != k) && (x[i] == x[k])) {
+                        throw MathRuntimeException.createArithmeticException(
+                              LocalizedFormats.IDENTICAL_ABSCISSAS_DIVISION_BY_ZERO,
+                              i, k, x[i]);
+                    }
+                }
+            }
+            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;
+    }
+
+    /**
+     * Verifies that the interpolation arrays are valid.
+     * <p>
+     * The arrays features checked by this method are that both arrays have the
+     * same length and this length is at least 2.
+     * </p>
+     * <p>
+     * The interpolating points must be distinct. However it is not
+     * verified here, it is checked in evaluate() and computeCoefficients().
+     * </p>
+     *
+     * @param x the interpolating points array
+     * @param y the interpolating values array
+     * @throws IllegalArgumentException if not valid
+     * @see #evaluate(double[], double[], double)
+     * @see #computeCoefficients()
+     */
+    public static void verifyInterpolationArray(double x[], double y[])
+        throws IllegalArgumentException {
+
+        if (x.length != y.length) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, x.length, y.length);
+        }
+
+        if (x.length < 2) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.WRONG_NUMBER_OF_POINTS, 2, x.length);
+        }
+
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/polynomials/PolynomialFunctionNewtonForm.java b/src/main/java/org/apache/commons/math/analysis/polynomials/PolynomialFunctionNewtonForm.java
new file mode 100644
index 0000000..5ee3224
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/polynomials/PolynomialFunctionNewtonForm.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.math.analysis.polynomials;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * 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>
+ *
+ * @version $Revision: 1073498 $ $Date: 2011-02-22 21:57:26 +0100 (mar. 22 févr. 2011) $
+ * @since 1.2
+ */
+public class PolynomialFunctionNewtonForm implements UnivariateRealFunction {
+
+    /**
+     * 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 the coefficients in Newton form formula
+     * @param c the centers
+     * @throws IllegalArgumentException if input arrays are not valid
+     */
+    public PolynomialFunctionNewtonForm(double a[], double c[])
+        throws IllegalArgumentException {
+
+        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 the point at which the function value is to be computed
+     * @return the function value
+     * @throws FunctionEvaluationException if a runtime error occurs
+     * @see UnivariateRealFunction#value(double)
+     */
+    public double value(double z) throws FunctionEvaluationException {
+       return evaluate(a, c, z);
+    }
+
+    /**
+     * 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 the coefficients in Newton form formula
+     * @param c the centers
+     * @param z the point at which the function value is to be computed
+     * @return the function value
+     * @throws FunctionEvaluationException if a runtime error occurs
+     * @throws IllegalArgumentException if inputs are not valid
+     */
+    public static double evaluate(double a[], double c[], double z)
+        throws FunctionEvaluationException, IllegalArgumentException {
+
+        verifyInputArray(a, c);
+
+        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 IllegalArgumentException if not valid
+     * @see org.apache.commons.math.analysis.interpolation.DividedDifferenceInterpolator#computeDividedDifference(double[],
+     * double[])
+     */
+    protected static void verifyInputArray(double a[], double c[]) throws
+        IllegalArgumentException {
+
+        if (a.length < 1 || c.length < 1) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.EMPTY_POLYNOMIALS_COEFFICIENTS_ARRAY);
+        }
+        if (a.length != c.length + 1) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.ARRAY_SIZES_SHOULD_HAVE_DIFFERENCE_1,
+                  a.length, c.length);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/polynomials/PolynomialSplineFunction.java b/src/main/java/org/apache/commons/math/analysis/polynomials/PolynomialSplineFunction.java
new file mode 100644
index 0000000..a0e1e01
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/polynomials/PolynomialSplineFunction.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.math.analysis.polynomials;
+
+import java.util.Arrays;
+
+import org.apache.commons.math.ArgumentOutsideDomainException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.analysis.DifferentiableUnivariateRealFunction;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * 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 <br>
+ * <code>polynomials[j](x - knot[j])</code></li></ol></p>
+ *
+ * @version $Revision: 1037327 $ $Date: 2010-11-20 21:57:37 +0100 (sam. 20 nov. 2010) $
+ */
+public class PolynomialSplineFunction
+    implements DifferentiableUnivariateRealFunction {
+
+    /** 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])</code> where i is the
+     * knot segment to which x belongs.
+     */
+    private final PolynomialFunction polynomials[];
+
+    /**
+     * Number of spline segments = number of polynomials
+     *  = number of partition points - 1
+     */
+    private final int n;
+
+
+    /**
+     * Construct a polynomial spline function with the given segment delimiters
+     * and interpolating polynomials.
+     * <p>
+     * The constructor copies both arrays and assigns the copies to the knots
+     * and polynomials properties, respectively.</p>
+     *
+     * @param knots spline segment interval delimiters
+     * @param polynomials polynomial functions that make up the spline
+     * @throws NullPointerException if either of the input arrays is null
+     * @throws IllegalArgumentException if knots has length less than 2,
+     * <code>polynomials.length != knots.length - 1 </code>, or the knots array
+     * is not strictly increasing.
+     *
+     */
+    public PolynomialSplineFunction(double knots[], PolynomialFunction polynomials[]) {
+        if (knots.length < 2) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.NOT_ENOUGH_POINTS_IN_SPLINE_PARTITION,
+                  2, knots.length);
+        }
+        if (knots.length - 1 != polynomials.length) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.POLYNOMIAL_INTERPOLANTS_MISMATCH_SEGMENTS,
+                  polynomials.length, knots.length);
+        }
+        if (!isStrictlyIncreasing(knots)) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.NOT_STRICTLY_INCREASING_KNOT_VALUES);
+        }
+
+        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.</p>
+     *
+     * @param v the point for which the function value should be computed
+     * @return the value
+     * @throws ArgumentOutsideDomainException if v is outside of the domain of
+     * of the spline function (less than the smallest knot point or greater
+     * than the largest knot point)
+     */
+    public double value(double v) throws ArgumentOutsideDomainException {
+        if (v < knots[0] || v > knots[n]) {
+            throw new ArgumentOutsideDomainException(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]);
+    }
+
+    /**
+     * Returns the derivative of the polynomial spline function as a UnivariateRealFunction
+     * @return  the derivative function
+     */
+    public UnivariateRealFunction derivative() {
+        return polynomialSplineDerivative();
+    }
+
+    /**
+     * Returns the derivative of the polynomial spline function as a PolynomialSplineFunction
+     *
+     * @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);
+    }
+
+    /**
+     * Returns the number of spline segments = the number of polynomials
+     * = the number of knot points - 1.
+     *
+     * @return the number of spline segments
+     */
+    public int getN() {
+        return n;
+    }
+
+    /**
+     * Returns a copy of the interpolating polynomials array.
+     * <p>
+     * Returns a fresh copy of the array. Changes made to the copy will
+     * not affect the polynomials property.</p>
+     *
+     * @return the interpolating polynomials
+     */
+    public PolynomialFunction[] getPolynomials() {
+        PolynomialFunction p[] = new PolynomialFunction[n];
+        System.arraycopy(polynomials, 0, p, 0, n);
+        return p;
+    }
+
+    /**
+     * Returns an array copy of the knot points.
+     * <p>
+     * Returns a fresh copy of the array. Changes made to the copy
+     * will not affect the knots property.</p>
+     *
+     * @return the knot points
+     */
+    public double[] getKnots() {
+        double out[] = new double[n + 1];
+        System.arraycopy(knots, 0, out, 0, n + 1);
+        return out;
+    }
+
+    /**
+     * Determines if the given array is ordered in a strictly increasing
+     * fashion.
+     *
+     * @param x the array to examine.
+     * @return <code>true</code> if the elements in <code>x</code> are ordered
+     * in a stricly increasing manner.  <code>false</code>, otherwise.
+     */
+    private static boolean isStrictlyIncreasing(double[] x) {
+        for (int i = 1; i < x.length; ++i) {
+            if (x[i - 1] >= x[i]) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/polynomials/PolynomialsUtils.java b/src/main/java/org/apache/commons/math/analysis/polynomials/PolynomialsUtils.java
new file mode 100644
index 0000000..5e939c7
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/polynomials/PolynomialsUtils.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.math.analysis.polynomials;
+
+import java.util.ArrayList;
+
+import org.apache.commons.math.fraction.BigFraction;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * A collection of static methods that operate on or return polynomials.
+ *
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ * @since 2.0
+ */
+public class PolynomialsUtils {
+
+    /** Coefficients for Chebyshev polynomials. */
+    private static final ArrayList<BigFraction> CHEBYSHEV_COEFFICIENTS;
+
+    /** Coefficients for Hermite polynomials. */
+    private static final ArrayList<BigFraction> HERMITE_COEFFICIENTS;
+
+    /** Coefficients for Laguerre polynomials. */
+    private static final ArrayList<BigFraction> LAGUERRE_COEFFICIENTS;
+
+    /** Coefficients for Legendre polynomials. */
+    private static final ArrayList<BigFraction> LEGENDRE_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);
+
+    }
+
+    /**
+     * Private constructor, to prevent instantiation.
+     */
+    private PolynomialsUtils() {
+    }
+
+    /**
+     * Create a Chebyshev polynomial of the first kind.
+     * <p><a href="http://mathworld.wolfram.com/ChebyshevPolynomialoftheFirstKind.html">Chebyshev
+     * polynomials of the first kind</a> are orthogonal polynomials.
+     * They can be defined by the following recurrence relations:
+     * <pre>
+     *  T<sub>0</sub>(X)   = 1
+     *  T<sub>1</sub>(X)   = X
+     *  T<sub>k+1</sub>(X) = 2X T<sub>k</sub>(X) - T<sub>k-1</sub>(X)
+     * </pre></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() {
+            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:
+     * <pre>
+     *  H<sub>0</sub>(X)   = 1
+     *  H<sub>1</sub>(X)   = 2X
+     *  H<sub>k+1</sub>(X) = 2X H<sub>k</sub>(X) - 2k H<sub>k-1</sub>(X)
+     * </pre></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:
+     * <pre>
+     *        L<sub>0</sub>(X)   = 1
+     *        L<sub>1</sub>(X)   = 1 - X
+     *  (k+1) L<sub>k+1</sub>(X) = (2k + 1 - X) L<sub>k</sub>(X) - k L<sub>k-1</sub>(X)
+     * </pre></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:
+     * <pre>
+     *        P<sub>0</sub>(X)   = 1
+     *        P<sub>1</sub>(X)   = X
+     *  (k+1) P<sub>k+1</sub>(X) = (2k+1) X P<sub>k</sub>(X) - k P<sub>k-1</sub>(X)
+     * </pre></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)};
+            }
+        });
+    }
+
+    /** 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 ArrayList<BigFraction> coefficients,
+                                                      final RecurrenceCoefficientsGenerator generator) {
+
+        final int maxDegree = (int) FastMath.floor(FastMath.sqrt(2 * coefficients.size())) - 1;
+        synchronized (PolynomialsUtils.class) {
+            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 ArrayList<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 static 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<sub>k+1</sub>(X) = (a[0] + a[1] X) P<sub>k</sub>(X) - a[2] P<sub>k-1</sub>(X)
+         */
+        BigFraction[] generate(int k);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/polynomials/package.html b/src/main/java/org/apache/commons/math/analysis/polynomials/package.html
new file mode 100644
index 0000000..63ca96a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/polynomials/package.html
@@ -0,0 +1,23 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $ -->
+    <body>
+     Univariate real polynomials implementations, seen as differentiable
+     univariate real functions.
+    </body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/analysis/solvers/BisectionSolver.java b/src/main/java/org/apache/commons/math/analysis/solvers/BisectionSolver.java
new file mode 100644
index 0000000..c9d3a63
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/solvers/BisectionSolver.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.analysis.solvers;
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.MaxIterationsExceededException;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * 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>
+ *
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ */
+public class BisectionSolver extends UnivariateRealSolverImpl {
+
+    /**
+     * Construct a solver for the given function.
+     *
+     * @param f function to solve.
+     * @deprecated as of 2.0 the function to solve is passed as an argument
+     * to the {@link #solve(UnivariateRealFunction, double, double)} or
+     * {@link UnivariateRealSolverImpl#solve(UnivariateRealFunction, double, double, double)}
+     * method.
+     */
+    @Deprecated
+    public BisectionSolver(UnivariateRealFunction f) {
+        super(f, 100, 1E-6);
+    }
+
+    /**
+     * Construct a solver.
+     *
+     */
+    public BisectionSolver() {
+        super(100, 1E-6);
+    }
+
+    /** {@inheritDoc} */
+    @Deprecated
+    public double solve(double min, double max, double initial)
+        throws MaxIterationsExceededException, FunctionEvaluationException {
+        return solve(f, min, max);
+    }
+
+    /** {@inheritDoc} */
+    @Deprecated
+    public double solve(double min, double max)
+        throws MaxIterationsExceededException, FunctionEvaluationException {
+        return solve(f, min, max);
+    }
+
+    /**
+     * {@inheritDoc}
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    public double solve(final UnivariateRealFunction f, double min, double max, double initial)
+        throws MaxIterationsExceededException, FunctionEvaluationException {
+        return solve(f, min, max);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double solve(int maxEval, final UnivariateRealFunction f, double min, double max, double initial)
+        throws MaxIterationsExceededException, FunctionEvaluationException {
+        return solve(maxEval, f, min, max);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double solve(int maxEval, final UnivariateRealFunction f, double min, double max)
+        throws MaxIterationsExceededException, FunctionEvaluationException {
+        setMaximalIterationCount(maxEval);
+        return solve(f, min, max);
+    }
+
+    /**
+     * {@inheritDoc}
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    public double solve(final UnivariateRealFunction f, double min, double max)
+        throws MaxIterationsExceededException, FunctionEvaluationException {
+
+        clearResult();
+        verifyInterval(min,max);
+        double m;
+        double fm;
+        double fmin;
+
+        int i = 0;
+        while (i < maximalIterationCount) {
+            m = UnivariateRealSolverUtils.midpoint(min, max);
+           fmin = f.value(min);
+           fm = f.value(m);
+
+            if (fm * fmin > 0.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 = UnivariateRealSolverUtils.midpoint(min, max);
+                setResult(m, i);
+                return m;
+            }
+            ++i;
+        }
+
+        throw new MaxIterationsExceededException(maximalIterationCount);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/solvers/BrentSolver.java b/src/main/java/org/apache/commons/math/analysis/solvers/BrentSolver.java
new file mode 100644
index 0000000..5aa2447
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/solvers/BrentSolver.java
@@ -0,0 +1,399 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.analysis.solvers;
+
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.MaxIterationsExceededException;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Implements the <a href="http://mathworld.wolfram.com/BrentsMethod.html">
+ * Brent algorithm</a> for  finding zeros of real univariate functions.
+ * <p>
+ * The function should be continuous but not necessarily smooth.</p>
+ *
+ * @version $Revision:670469 $ $Date:2008-06-23 10:01:38 +0200 (lun., 23 juin 2008) $
+ */
+public class BrentSolver extends UnivariateRealSolverImpl {
+
+    /**
+     * Default absolute accuracy
+     * @since 2.1
+     */
+    public static final double DEFAULT_ABSOLUTE_ACCURACY = 1E-6;
+
+    /** Default maximum number of iterations
+     * @since 2.1
+     */
+    public static final int DEFAULT_MAXIMUM_ITERATIONS = 100;
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 7694577816772532779L;
+
+    /**
+     * Construct a solver for the given function.
+     *
+     * @param f function to solve.
+     * @deprecated as of 2.0 the function to solve is passed as an argument
+     * to the {@link #solve(UnivariateRealFunction, double, double)} or
+     * {@link UnivariateRealSolverImpl#solve(UnivariateRealFunction, double, double, double)}
+     * method.
+     */
+    @Deprecated
+    public BrentSolver(UnivariateRealFunction f) {
+        super(f, DEFAULT_MAXIMUM_ITERATIONS, DEFAULT_ABSOLUTE_ACCURACY);
+    }
+
+    /**
+     * Construct a solver with default properties.
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    public BrentSolver() {
+        super(DEFAULT_MAXIMUM_ITERATIONS, DEFAULT_ABSOLUTE_ACCURACY);
+    }
+
+    /**
+     * Construct a solver with the given absolute accuracy.
+     *
+     * @param absoluteAccuracy lower bound for absolute accuracy of solutions returned by the solver
+     * @since 2.1
+     */
+    public BrentSolver(double absoluteAccuracy) {
+        super(DEFAULT_MAXIMUM_ITERATIONS, absoluteAccuracy);
+    }
+
+    /**
+     * Contstruct a solver with the given maximum iterations and absolute accuracy.
+     *
+     * @param maximumIterations maximum number of iterations
+     * @param absoluteAccuracy lower bound for absolute accuracy of solutions returned by the solver
+     * @since 2.1
+     */
+    public BrentSolver(int maximumIterations, double absoluteAccuracy) {
+        super(maximumIterations, absoluteAccuracy);
+    }
+
+    /** {@inheritDoc} */
+    @Deprecated
+    public double solve(double min, double max)
+        throws MaxIterationsExceededException, FunctionEvaluationException {
+        return solve(f, min, max);
+    }
+
+    /** {@inheritDoc} */
+    @Deprecated
+    public double solve(double min, double max, double initial)
+        throws MaxIterationsExceededException, FunctionEvaluationException {
+        return solve(f, min, max, initial);
+    }
+
+    /**
+     * Find a zero in the given interval with an initial guess.
+     * <p>Throws <code>IllegalArgumentException</code> if the values of the
+     * function at the three points have the same sign (note that it is
+     * allowed to have endpoints with the same sign if the initial point has
+     * opposite sign function-wise).</p>
+     *
+     * @param f function to solve.
+     * @param min the lower bound for the interval.
+     * @param max the upper bound for the interval.
+     * @param initial the start value to use (must be set to min if no
+     * initial point is known).
+     * @return the value where the function is zero
+     * @throws MaxIterationsExceededException the maximum iteration count is exceeded
+     * @throws FunctionEvaluationException if an error occurs evaluating  the function
+     * @throws IllegalArgumentException if initial is not between min and max
+     * (even if it <em>is</em> a root)
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    public double solve(final UnivariateRealFunction f,
+                        final double min, final double max, final double initial)
+        throws MaxIterationsExceededException, FunctionEvaluationException {
+
+        clearResult();
+        if ((initial < min) || (initial > max)) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.INVALID_INTERVAL_INITIAL_VALUE_PARAMETERS,
+                  min, initial, max);
+        }
+
+        // return the initial guess if it is good enough
+        double yInitial = f.value(initial);
+        if (FastMath.abs(yInitial) <= functionValueAccuracy) {
+            setResult(initial, 0);
+            return result;
+        }
+
+        // return the first endpoint if it is good enough
+        double yMin = f.value(min);
+        if (FastMath.abs(yMin) <= functionValueAccuracy) {
+            setResult(min, 0);
+            return result;
+        }
+
+        // reduce interval if min and initial bracket the root
+        if (yInitial * yMin < 0) {
+            return solve(f, min, yMin, initial, yInitial, min, yMin);
+        }
+
+        // return the second endpoint if it is good enough
+        double yMax = f.value(max);
+        if (FastMath.abs(yMax) <= functionValueAccuracy) {
+            setResult(max, 0);
+            return result;
+        }
+
+        // reduce interval if initial and max bracket the root
+        if (yInitial * yMax < 0) {
+            return solve(f, initial, yInitial, max, yMax, initial, yInitial);
+        }
+
+        throw MathRuntimeException.createIllegalArgumentException(
+              LocalizedFormats.SAME_SIGN_AT_ENDPOINTS, min, max, yMin, yMax);
+
+    }
+
+    /**
+     * Find a zero in the given interval with an initial guess.
+     * <p>Throws <code>IllegalArgumentException</code> if the values of the
+     * function at the three points have the same sign (note that it is
+     * allowed to have endpoints with the same sign if the initial point has
+     * opposite sign function-wise).</p>
+     *
+     * @param f function to solve.
+     * @param min the lower bound for the interval.
+     * @param max the upper bound for the interval.
+     * @param initial the start value to use (must be set to min if no
+     * initial point is known).
+     * @param maxEval Maximum number of evaluations.
+     * @return the value where the function is zero
+     * @throws MaxIterationsExceededException the maximum iteration count is exceeded
+     * @throws FunctionEvaluationException if an error occurs evaluating  the function
+     * @throws IllegalArgumentException if initial is not between min and max
+     * (even if it <em>is</em> a root)
+     */
+    @Override
+    public double solve(int maxEval, final UnivariateRealFunction f,
+                        final double min, final double max, final double initial)
+        throws MaxIterationsExceededException, FunctionEvaluationException {
+        setMaximalIterationCount(maxEval);
+        return solve(f, min, max, initial);
+    }
+
+    /**
+     * Find a zero in the given interval.
+     * <p>
+     * Requires that the values of the function at the endpoints have opposite
+     * signs. An <code>IllegalArgumentException</code> is thrown if this is not
+     * the case.</p>
+     *
+     * @param f the function to solve
+     * @param min the lower bound for the interval.
+     * @param max the upper bound for the interval.
+     * @return the value where the function is zero
+     * @throws MaxIterationsExceededException if the maximum iteration count is exceeded
+     * @throws FunctionEvaluationException if an error occurs evaluating the function
+     * @throws IllegalArgumentException if min is not less than max or the
+     * signs of the values of the function at the endpoints are not opposites
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    public double solve(final UnivariateRealFunction f,
+                        final double min, final double max)
+        throws MaxIterationsExceededException, FunctionEvaluationException {
+
+        clearResult();
+        verifyInterval(min, max);
+
+        double ret = Double.NaN;
+
+        double yMin = f.value(min);
+        double yMax = f.value(max);
+
+        // Verify bracketing
+        double sign = yMin * yMax;
+        if (sign > 0) {
+            // check if either value is close to a zero
+            if (FastMath.abs(yMin) <= functionValueAccuracy) {
+                setResult(min, 0);
+                ret = min;
+            } else if (FastMath.abs(yMax) <= functionValueAccuracy) {
+                setResult(max, 0);
+                ret = max;
+            } else {
+                // neither value is close to zero and min and max do not bracket root.
+                throw MathRuntimeException.createIllegalArgumentException(
+                        LocalizedFormats.SAME_SIGN_AT_ENDPOINTS, min, max, yMin, yMax);
+            }
+        } else if (sign < 0){
+            // solve using only the first endpoint as initial guess
+            ret = solve(f, min, yMin, max, yMax, min, yMin);
+        } else {
+            // either min or max is a root
+            if (yMin == 0.0) {
+                ret = min;
+            } else {
+                ret = max;
+            }
+        }
+
+        return ret;
+    }
+
+    /**
+     * Find a zero in the given interval.
+     * <p>
+     * Requires that the values of the function at the endpoints have opposite
+     * signs. An <code>IllegalArgumentException</code> is thrown if this is not
+     * the case.</p>
+     *
+     * @param f the function to solve
+     * @param min the lower bound for the interval.
+     * @param max the upper bound for the interval.
+     * @param maxEval Maximum number of evaluations.
+     * @return the value where the function is zero
+     * @throws MaxIterationsExceededException if the maximum iteration count is exceeded
+     * @throws FunctionEvaluationException if an error occurs evaluating the function
+     * @throws IllegalArgumentException if min is not less than max or the
+     * signs of the values of the function at the endpoints are not opposites
+     */
+    @Override
+    public double solve(int maxEval, final UnivariateRealFunction f,
+                        final double min, final double max)
+        throws MaxIterationsExceededException, FunctionEvaluationException {
+        setMaximalIterationCount(maxEval);
+        return solve(f, min, max);
+    }
+
+    /**
+     * Find a zero starting search according to the three provided points.
+     * @param f the function to solve
+     * @param x0 old approximation for the root
+     * @param y0 function value at the approximation for the root
+     * @param x1 last calculated approximation for the root
+     * @param y1 function value at the last calculated approximation
+     * for the root
+     * @param x2 bracket point (must be set to x0 if no bracket point is
+     * known, this will force starting with linear interpolation)
+     * @param y2 function value at the bracket point.
+     * @return the value where the function is zero
+     * @throws MaxIterationsExceededException if the maximum iteration count is exceeded
+     * @throws FunctionEvaluationException if an error occurs evaluating the function
+     */
+    private double solve(final UnivariateRealFunction f,
+                         double x0, double y0,
+                         double x1, double y1,
+                         double x2, double y2)
+    throws MaxIterationsExceededException, FunctionEvaluationException {
+
+        double delta = x1 - x0;
+        double oldDelta = delta;
+
+        int i = 0;
+        while (i < maximalIterationCount) {
+            if (FastMath.abs(y2) < FastMath.abs(y1)) {
+                // use the bracket point if is better than last approximation
+                x0 = x1;
+                x1 = x2;
+                x2 = x0;
+                y0 = y1;
+                y1 = y2;
+                y2 = y0;
+            }
+            if (FastMath.abs(y1) <= functionValueAccuracy) {
+                // Avoid division by very small values. Assume
+                // the iteration has converged (the problem may
+                // still be ill conditioned)
+                setResult(x1, i);
+                return result;
+            }
+            double dx = x2 - x1;
+            double tolerance =
+                FastMath.max(relativeAccuracy * FastMath.abs(x1), absoluteAccuracy);
+            if (FastMath.abs(dx) <= tolerance) {
+                setResult(x1, i);
+                return result;
+            }
+            if ((FastMath.abs(oldDelta) < tolerance) ||
+                    (FastMath.abs(y0) <= FastMath.abs(y1))) {
+                // Force bisection.
+                delta = 0.5 * dx;
+                oldDelta = delta;
+            } else {
+                double r3 = y1 / y0;
+                double p;
+                double p1;
+                // the equality test (x0 == x2) is intentional,
+                // it is part of the original Brent's method,
+                // it should NOT be replaced by proximity test
+                if (x0 == x2) {
+                    // Linear interpolation.
+                    p = dx * r3;
+                    p1 = 1.0 - r3;
+                } else {
+                    // Inverse quadratic interpolation.
+                    double r1 = y0 / y2;
+                    double r2 = y1 / y2;
+                    p = r3 * (dx * r1 * (r1 - r2) - (x1 - x0) * (r2 - 1.0));
+                    p1 = (r1 - 1.0) * (r2 - 1.0) * (r3 - 1.0);
+                }
+                if (p > 0.0) {
+                    p1 = -p1;
+                } else {
+                    p = -p;
+                }
+                if (2.0 * p >= 1.5 * dx * p1 - FastMath.abs(tolerance * p1) ||
+                        p >= FastMath.abs(0.5 * oldDelta * p1)) {
+                    // Inverse quadratic interpolation gives a value
+                    // in the wrong direction, or progress is slow.
+                    // Fall back to bisection.
+                    delta = 0.5 * dx;
+                    oldDelta = delta;
+                } else {
+                    oldDelta = delta;
+                    delta = p / p1;
+                }
+            }
+            // Save old X1, Y1
+            x0 = x1;
+            y0 = y1;
+            // Compute new X1, Y1
+            if (FastMath.abs(delta) > tolerance) {
+                x1 = x1 + delta;
+            } else if (dx > 0.0) {
+                x1 = x1 + 0.5 * tolerance;
+            } else if (dx <= 0.0) {
+                x1 = x1 - 0.5 * tolerance;
+            }
+            y1 = f.value(x1);
+            if ((y1 > 0) == (y2 > 0)) {
+                x2 = x0;
+                y2 = y0;
+                delta = x1 - x0;
+                oldDelta = delta;
+            }
+            i++;
+        }
+        throw new MaxIterationsExceededException(maximalIterationCount);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/solvers/LaguerreSolver.java b/src/main/java/org/apache/commons/math/analysis/solvers/LaguerreSolver.java
new file mode 100644
index 0000000..e6d8bd8
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/solvers/LaguerreSolver.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.math.analysis.solvers;
+
+import org.apache.commons.math.ConvergenceException;
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.MaxIterationsExceededException;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.analysis.polynomials.PolynomialFunction;
+import org.apache.commons.math.complex.Complex;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.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 <b>A First Course in Numerical Analysis</b>,
+ * ISBN 048641454X, chapter 8.
+ * <p>
+ * 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.</p>
+ *
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ * @since 1.2
+ */
+public class LaguerreSolver extends UnivariateRealSolverImpl {
+
+    /** polynomial function to solve.
+     * @deprecated as of 2.0 the function is not stored anymore in the instance
+     */
+    @Deprecated
+    private final PolynomialFunction p;
+
+    /**
+     * Construct a solver for the given function.
+     *
+     * @param f function to solve
+     * @throws IllegalArgumentException if function is not polynomial
+     * @deprecated as of 2.0 the function to solve is passed as an argument
+     * to the {@link #solve(UnivariateRealFunction, double, double)} or
+     * {@link UnivariateRealSolverImpl#solve(UnivariateRealFunction, double, double, double)}
+     * method.
+     */
+    @Deprecated
+    public LaguerreSolver(UnivariateRealFunction f) throws IllegalArgumentException {
+        super(f, 100, 1E-6);
+        if (f instanceof PolynomialFunction) {
+            p = (PolynomialFunction) f;
+        } else {
+            throw MathRuntimeException.createIllegalArgumentException(LocalizedFormats.FUNCTION_NOT_POLYNOMIAL);
+        }
+    }
+
+    /**
+     * Construct a solver.
+     * @deprecated in 2.2 (to be removed in 3.0)
+     */
+    @Deprecated
+    public LaguerreSolver() {
+        super(100, 1E-6);
+        p = null;
+    }
+
+    /**
+     * Returns a copy of the polynomial function.
+     *
+     * @return a fresh copy of the polynomial function
+     * @deprecated as of 2.0 the function is not stored anymore within the instance.
+     */
+    @Deprecated
+    public PolynomialFunction getPolynomialFunction() {
+        return new PolynomialFunction(p.getCoefficients());
+    }
+
+    /** {@inheritDoc} */
+    @Deprecated
+    public double solve(final double min, final double max)
+        throws ConvergenceException, FunctionEvaluationException {
+        return solve(p, min, max);
+    }
+
+    /** {@inheritDoc} */
+    @Deprecated
+    public double solve(final double min, final double max, final double initial)
+        throws ConvergenceException, FunctionEvaluationException {
+        return solve(p, min, max, initial);
+    }
+
+    /**
+     * Find a real root in the given interval with initial value.
+     * <p>
+     * Requires bracketing condition.</p>
+     *
+     * @param f function to solve (must be polynomial)
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @param initial the start value to use
+     * @param maxEval Maximum number of evaluations.
+     * @return the point at which the function value is zero
+     * @throws ConvergenceException if the maximum iteration count is exceeded
+     * or the solver detects convergence problems otherwise
+     * @throws FunctionEvaluationException if an error occurs evaluating the function
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    @Override
+    public double solve(int maxEval, final UnivariateRealFunction f,
+                        final double min, final double max, final double initial)
+        throws ConvergenceException, FunctionEvaluationException {
+        setMaximalIterationCount(maxEval);
+        return solve(f, min, max, initial);
+    }
+
+    /**
+     * Find a real root in the given interval with initial value.
+     * <p>
+     * Requires bracketing condition.</p>
+     *
+     * @param f function to solve (must be polynomial)
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @param initial the start value to use
+     * @return the point at which the function value is zero
+     * @throws ConvergenceException if the maximum iteration count is exceeded
+     * or the solver detects convergence problems otherwise
+     * @throws FunctionEvaluationException if an error occurs evaluating the function
+     * @throws IllegalArgumentException if any parameters are invalid
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    public double solve(final UnivariateRealFunction f,
+                        final double min, final double max, final double initial)
+        throws ConvergenceException, FunctionEvaluationException {
+
+        // check for zeros before verifying bracketing
+        if (f.value(min) == 0.0) {
+            return min;
+        }
+        if (f.value(max) == 0.0) {
+            return max;
+        }
+        if (f.value(initial) == 0.0) {
+            return initial;
+        }
+
+        verifyBracketing(min, max, f);
+        verifySequence(min, initial, max);
+        if (isBracketing(min, initial, f)) {
+            return solve(f, min, initial);
+        } else {
+            return solve(f, initial, max);
+        }
+
+    }
+
+    /**
+     * Find a real root in the given interval.
+     * <p>
+     * Despite the bracketing condition, the root returned by solve(Complex[],
+     * Complex) may not be a real zero inside [min, max]. For example,
+     * p(x) = x^3 + 1, min = -2, max = 2, initial = 0. We can either try
+     * another initial value, or, as we did here, call solveAll() to obtain
+     * all roots and pick up the one that we're looking for.</p>
+     *
+     * @param f the function to solve
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @param maxEval Maximum number of evaluations.
+     * @return the point at which the function value is zero
+     * @throws ConvergenceException if the maximum iteration count is exceeded
+     * or the solver detects convergence problems otherwise
+     * @throws FunctionEvaluationException if an error occurs evaluating the function
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    @Override
+    public double solve(int maxEval, final UnivariateRealFunction f,
+                        final double min, final double max)
+        throws ConvergenceException, FunctionEvaluationException {
+        setMaximalIterationCount(maxEval);
+        return solve(f, min, max);
+    }
+
+    /**
+     * Find a real root in the given interval.
+     * <p>
+     * Despite the bracketing condition, the root returned by solve(Complex[],
+     * Complex) may not be a real zero inside [min, max]. For example,
+     * p(x) = x^3 + 1, min = -2, max = 2, initial = 0. We can either try
+     * another initial value, or, as we did here, call solveAll() to obtain
+     * all roots and pick up the one that we're looking for.</p>
+     *
+     * @param f the function to solve
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @return the point at which the function value is zero
+     * @throws ConvergenceException if the maximum iteration count is exceeded
+     * or the solver detects convergence problems otherwise
+     * @throws FunctionEvaluationException if an error occurs evaluating the function
+     * @throws IllegalArgumentException if any parameters are invalid
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    public double solve(final UnivariateRealFunction f,
+                        final double min, final double max)
+        throws ConvergenceException, FunctionEvaluationException {
+
+        // check function type
+        if (!(f instanceof PolynomialFunction)) {
+            throw MathRuntimeException.createIllegalArgumentException(LocalizedFormats.FUNCTION_NOT_POLYNOMIAL);
+        }
+
+        // check for zeros before verifying bracketing
+        if (f.value(min) == 0.0) { return min; }
+        if (f.value(max) == 0.0) { return max; }
+        verifyBracketing(min, max, f);
+
+        double coefficients[] = ((PolynomialFunction) f).getCoefficients();
+        Complex c[] = new Complex[coefficients.length];
+        for (int i = 0; i < coefficients.length; i++) {
+            c[i] = new Complex(coefficients[i], 0.0);
+        }
+        Complex initial = new Complex(0.5 * (min + max), 0.0);
+        Complex z = solve(c, initial);
+        if (isRootOK(min, max, z)) {
+            setResult(z.getReal(), iterationCount);
+            return result;
+        }
+
+        // solve all roots and select the one we're seeking
+        Complex[] root = solveAll(c, initial);
+        for (int i = 0; i < root.length; i++) {
+            if (isRootOK(min, max, root[i])) {
+                setResult(root[i].getReal(), iterationCount);
+                return result;
+            }
+        }
+
+        // should never happen
+        throw new ConvergenceException();
+    }
+
+    /**
+     * Returns true iff the given complex root is actually a real zero
+     * in the given interval, within the solver tolerance level.
+     *
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @param z the complex root
+     * @return true iff z is the sought-after real zero
+     */
+    protected boolean isRootOK(double min, double max, Complex z) {
+        double tolerance = FastMath.max(relativeAccuracy * z.abs(), absoluteAccuracy);
+        return (isSequence(min, z.getReal(), max)) &&
+               (FastMath.abs(z.getImaginary()) <= tolerance ||
+                z.abs() <= functionValueAccuracy);
+    }
+
+    /**
+     * Find all complex roots for the polynomial with the given coefficients,
+     * starting from the given initial value.
+     *
+     * @param coefficients the polynomial coefficients array
+     * @param initial the start value to use
+     * @return the point at which the function value is zero
+     * @throws ConvergenceException if the maximum iteration count is exceeded
+     * or the solver detects convergence problems otherwise
+     * @throws FunctionEvaluationException if an error occurs evaluating the function
+     * @throws IllegalArgumentException if any parameters are invalid
+     * @deprecated in 2.2.
+     */
+    @Deprecated
+    public Complex[] solveAll(double coefficients[], double initial) throws
+        ConvergenceException, FunctionEvaluationException {
+
+        Complex c[] = new Complex[coefficients.length];
+        Complex z = new Complex(initial, 0.0);
+        for (int i = 0; i < c.length; i++) {
+            c[i] = new Complex(coefficients[i], 0.0);
+        }
+        return solveAll(c, z);
+    }
+
+    /**
+     * Find all complex roots for the polynomial with the given coefficients,
+     * starting from the given initial value.
+     *
+     * @param coefficients the polynomial coefficients array
+     * @param initial the start value to use
+     * @return the point at which the function value is zero
+     * @throws MaxIterationsExceededException if the maximum iteration count is exceeded
+     * or the solver detects convergence problems otherwise
+     * @throws FunctionEvaluationException if an error occurs evaluating the function
+     * @throws IllegalArgumentException if any parameters are invalid
+     * @deprecated in 2.2.
+     */
+    @Deprecated
+    public Complex[] solveAll(Complex coefficients[], Complex initial) throws
+        MaxIterationsExceededException, FunctionEvaluationException {
+
+        int n = coefficients.length - 1;
+        int iterationCount = 0;
+        if (n < 1) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.NON_POSITIVE_POLYNOMIAL_DEGREE, n);
+        }
+        Complex c[] = new Complex[n+1];    // coefficients for deflated polynomial
+        for (int i = 0; i <= n; i++) {
+            c[i] = coefficients[i];
+        }
+
+        // solve individual root successively
+        Complex root[] = new Complex[n];
+        for (int i = 0; i < n; i++) {
+            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]));
+            }
+            iterationCount += this.iterationCount;
+        }
+
+        resultComputed = true;
+        this.iterationCount = iterationCount;
+        return root;
+    }
+
+    /**
+     * Find a complex root for the polynomial with the given coefficients,
+     * starting from the given initial value.
+     *
+     * @param coefficients the polynomial coefficients array
+     * @param initial the start value to use
+     * @return the point at which the function value is zero
+     * @throws MaxIterationsExceededException if the maximum iteration count is exceeded
+     * or the solver detects convergence problems otherwise
+     * @throws FunctionEvaluationException if an error occurs evaluating the function
+     * @throws IllegalArgumentException if any parameters are invalid
+     * @deprecated in 2.2.
+     */
+    @Deprecated
+    public Complex solve(Complex coefficients[], Complex initial) throws
+        MaxIterationsExceededException, FunctionEvaluationException {
+
+        int n = coefficients.length - 1;
+        if (n < 1) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.NON_POSITIVE_POLYNOMIAL_DEGREE, n);
+        }
+        Complex N  = new Complex(n,     0.0);
+        Complex N1 = new Complex(n - 1, 0.0);
+
+        int i = 1;
+        Complex pv = null;
+        Complex dv = null;
+        Complex d2v = null;
+        Complex G = null;
+        Complex G2 = null;
+        Complex H = null;
+        Complex delta = null;
+        Complex denominator = null;
+        Complex z = initial;
+        Complex oldz = new Complex(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
+        while (i <= maximalIterationCount) {
+            // Compute pv (polynomial value), dv (derivative value), and
+            // d2v (second derivative value) simultaneously.
+            pv = coefficients[n];
+            dv = Complex.ZERO;
+            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
+            double tolerance = FastMath.max(relativeAccuracy * z.abs(),
+                                        absoluteAccuracy);
+            if ((z.subtract(oldz)).abs() <= tolerance) {
+                resultComputed = true;
+                iterationCount = i;
+                return z;
+            }
+            if (pv.abs() <= functionValueAccuracy) {
+                resultComputed = true;
+                iterationCount = i;
+                return z;
+            }
+
+            // now pv != 0, calculate the new approximation
+            G = dv.divide(pv);
+            G2 = G.multiply(G);
+            H = G2.subtract(d2v.divide(pv));
+            delta = N1.multiply((N.multiply(H)).subtract(G2));
+            // choose a denominator larger in magnitude
+            Complex deltaSqrt = delta.sqrt();
+            Complex dplus = G.add(deltaSqrt);
+            Complex dminus = G.subtract(deltaSqrt);
+            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(N.divide(denominator));
+            }
+            i++;
+        }
+        throw new MaxIterationsExceededException(maximalIterationCount);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/solvers/MullerSolver.java b/src/main/java/org/apache/commons/math/analysis/solvers/MullerSolver.java
new file mode 100644
index 0000000..a6d03bc
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/solvers/MullerSolver.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.math.analysis.solvers;
+
+import org.apache.commons.math.ConvergenceException;
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.MaxIterationsExceededException;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.util.FastMath;
+import org.apache.commons.math.util.MathUtils;
+
+/**
+ * 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. Methods solve() and solve2() find
+ * real zeros, using different ways to bypass complex arithmetics.</p>
+ *
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ * @since 1.2
+ */
+public class MullerSolver extends UnivariateRealSolverImpl {
+
+    /**
+     * Construct a solver for the given function.
+     *
+     * @param f function to solve
+     * @deprecated as of 2.0 the function to solve is passed as an argument
+     * to the {@link #solve(UnivariateRealFunction, double, double)} or
+     * {@link UnivariateRealSolverImpl#solve(UnivariateRealFunction, double, double, double)}
+     * method.
+     */
+    @Deprecated
+    public MullerSolver(UnivariateRealFunction f) {
+        super(f, 100, 1E-6);
+    }
+
+    /**
+     * Construct a solver.
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    public MullerSolver() {
+        super(100, 1E-6);
+    }
+
+    /** {@inheritDoc} */
+    @Deprecated
+    public double solve(final double min, final double max)
+        throws ConvergenceException, FunctionEvaluationException {
+        return solve(f, min, max);
+    }
+
+    /** {@inheritDoc} */
+    @Deprecated
+    public double solve(final double min, final double max, final double initial)
+        throws ConvergenceException, FunctionEvaluationException {
+        return solve(f, min, max, initial);
+    }
+
+    /**
+     * Find a real root in the given interval with initial value.
+     * <p>
+     * Requires bracketing condition.</p>
+     *
+     * @param f the function to solve
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @param initial the start value to use
+     * @param maxEval Maximum number of evaluations.
+     * @return the point at which the function value is zero
+     * @throws MaxIterationsExceededException if the maximum iteration count is exceeded
+     * or the solver detects convergence problems otherwise
+     * @throws FunctionEvaluationException if an error occurs evaluating the function
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    @Override
+    public double solve(int maxEval, final UnivariateRealFunction f,
+                        final double min, final double max, final double initial)
+        throws MaxIterationsExceededException, FunctionEvaluationException {
+        setMaximalIterationCount(maxEval);
+        return solve(f, min, max, initial);
+    }
+
+    /**
+     * Find a real root in the given interval with initial value.
+     * <p>
+     * Requires bracketing condition.</p>
+     *
+     * @param f the function to solve
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @param initial the start value to use
+     * @return the point at which the function value is zero
+     * @throws MaxIterationsExceededException if the maximum iteration count is exceeded
+     * or the solver detects convergence problems otherwise
+     * @throws FunctionEvaluationException if an error occurs evaluating the function
+     * @throws IllegalArgumentException if any parameters are invalid
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    public double solve(final UnivariateRealFunction f,
+                        final double min, final double max, final double initial)
+        throws MaxIterationsExceededException, FunctionEvaluationException {
+
+        // check for zeros before verifying bracketing
+        if (f.value(min) == 0.0) { return min; }
+        if (f.value(max) == 0.0) { return max; }
+        if (f.value(initial) == 0.0) { return initial; }
+
+        verifyBracketing(min, max, f);
+        verifySequence(min, initial, max);
+        if (isBracketing(min, initial, f)) {
+            return solve(f, min, initial);
+        } else {
+            return solve(f, initial, max);
+        }
+    }
+
+    /**
+     * Find a real root in the given interval.
+     * <p>
+     * Original Muller's 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>
+     *
+     * @param f the function to solve
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @param maxEval Maximum number of evaluations.
+     * @return the point at which the function value is zero
+     * @throws MaxIterationsExceededException if the maximum iteration count is exceeded
+     * or the solver detects convergence problems otherwise
+     * @throws FunctionEvaluationException if an error occurs evaluating the function
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    @Override
+    public double solve(int maxEval, final UnivariateRealFunction f,
+                        final double min, final double max)
+        throws MaxIterationsExceededException, FunctionEvaluationException {
+        setMaximalIterationCount(maxEval);
+        return solve(f, min, max);
+    }
+
+    /**
+     * Find a real root in the given interval.
+     * <p>
+     * Original Muller's 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>
+     *
+     * @param f the function to solve
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @return the point at which the function value is zero
+     * @throws MaxIterationsExceededException if the maximum iteration count is exceeded
+     * or the solver detects convergence problems otherwise
+     * @throws FunctionEvaluationException if an error occurs evaluating the function
+     * @throws IllegalArgumentException if any parameters are invalid
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    public double solve(final UnivariateRealFunction f,
+                        final double min, final double max)
+        throws MaxIterationsExceededException, FunctionEvaluationException {
+
+        // [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 = f.value(x0);
+        double x2 = max;
+        double y2 = f.value(x2);
+        double x1 = 0.5 * (x0 + x2);
+        double y1 = f.value(x1);
+
+        // check for zeros before verifying bracketing
+        if (y0 == 0.0) {
+            return min;
+        }
+        if (y2 == 0.0) {
+            return max;
+        }
+        verifyBracketing(min, max, f);
+
+        double oldx = Double.POSITIVE_INFINITY;
+        for (int i = 1; i <= maximalIterationCount; ++i) {
+            // 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 = f.value(x);
+
+            // check for convergence
+            final double tolerance = FastMath.max(relativeAccuracy * FastMath.abs(x), absoluteAccuracy);
+            if (FastMath.abs(x - oldx) <= tolerance) {
+                setResult(x, i);
+                return result;
+            }
+            if (FastMath.abs(y) <= functionValueAccuracy) {
+                setResult(x, i);
+                return result;
+            }
+
+            // 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 = f.value(xm);
+                if (MathUtils.sign(y0) + MathUtils.sign(ym) == 0.0) {
+                    x2 = xm; y2 = ym;
+                } else {
+                    x0 = xm; y0 = ym;
+                }
+                x1 = 0.5 * (x0 + x2);
+                y1 = f.value(x1);
+                oldx = Double.POSITIVE_INFINITY;
+            }
+        }
+        throw new MaxIterationsExceededException(maximalIterationCount);
+    }
+
+    /**
+     * Find a real root in the given interval.
+     * <p>
+     * solve2() differs from solve() in the way it avoids complex operations.
+     * Except for the initial [min, max], solve2() does not require bracketing
+     * condition, e.g. f(x0), f(x1), f(x2) can have the same sign. If complex
+     * number arises in the computation, we simply use its modulus as real
+     * approximation.</p>
+     * <p>
+     * Because the interval may not be bracketing, bisection alternative is
+     * not applicable here. However in practice our treatment usually works
+     * well, especially near real zeros where the imaginary part of complex
+     * approximation is often negligible.</p>
+     * <p>
+     * The formulas here do not use divided differences directly.</p>
+     *
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @return the point at which the function value is zero
+     * @throws MaxIterationsExceededException if the maximum iteration count is exceeded
+     * or the solver detects convergence problems otherwise
+     * @throws FunctionEvaluationException if an error occurs evaluating the function
+     * @throws IllegalArgumentException if any parameters are invalid
+     * @deprecated replaced by {@link #solve2(UnivariateRealFunction, double, double)}
+     * since 2.0
+     */
+    @Deprecated
+    public double solve2(final double min, final double max)
+        throws MaxIterationsExceededException, FunctionEvaluationException {
+        return solve2(f, min, max);
+    }
+
+    /**
+     * Find a real root in the given interval.
+     * <p>
+     * solve2() differs from solve() in the way it avoids complex operations.
+     * Except for the initial [min, max], solve2() does not require bracketing
+     * condition, e.g. f(x0), f(x1), f(x2) can have the same sign. If complex
+     * number arises in the computation, we simply use its modulus as real
+     * approximation.</p>
+     * <p>
+     * Because the interval may not be bracketing, bisection alternative is
+     * not applicable here. However in practice our treatment usually works
+     * well, especially near real zeros where the imaginary part of complex
+     * approximation is often negligible.</p>
+     * <p>
+     * The formulas here do not use divided differences directly.</p>
+     *
+     * @param f the function to solve
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @return the point at which the function value is zero
+     * @throws MaxIterationsExceededException if the maximum iteration count is exceeded
+     * or the solver detects convergence problems otherwise
+     * @throws FunctionEvaluationException if an error occurs evaluating the function
+     * @throws IllegalArgumentException if any parameters are invalid
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    public double solve2(final UnivariateRealFunction f,
+                         final double min, final double max)
+        throws MaxIterationsExceededException, FunctionEvaluationException {
+
+        // 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 = f.value(x0);
+        double x1 = max;
+        double y1 = f.value(x1);
+        double x2 = 0.5 * (x0 + x1);
+        double y2 = f.value(x2);
+
+        // check for zeros before verifying bracketing
+        if (y0 == 0.0) { return min; }
+        if (y1 == 0.0) { return max; }
+        verifyBracketing(min, max, f);
+
+        double oldx = Double.POSITIVE_INFINITY;
+        for (int i = 1; i <= maximalIterationCount; ++i) {
+            // 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 = f.value(x);
+
+            // check for convergence
+            final double tolerance = FastMath.max(relativeAccuracy * FastMath.abs(x), absoluteAccuracy);
+            if (FastMath.abs(x - oldx) <= tolerance) {
+                setResult(x, i);
+                return result;
+            }
+            if (FastMath.abs(y) <= functionValueAccuracy) {
+                setResult(x, i);
+                return result;
+            }
+
+            // prepare the next iteration
+            x0 = x1;
+            y0 = y1;
+            x1 = x2;
+            y1 = y2;
+            x2 = x;
+            y2 = y;
+            oldx = x;
+        }
+        throw new MaxIterationsExceededException(maximalIterationCount);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/solvers/NewtonSolver.java b/src/main/java/org/apache/commons/math/analysis/solvers/NewtonSolver.java
new file mode 100644
index 0000000..a2cffff
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/solvers/NewtonSolver.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.math.analysis.solvers;
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.MaxIterationsExceededException;
+import org.apache.commons.math.analysis.DifferentiableUnivariateRealFunction;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * 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>
+ *
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ */
+public class NewtonSolver extends UnivariateRealSolverImpl {
+
+    /**
+     * Construct a solver for the given function.
+     * @param f function to solve.
+     * @deprecated as of 2.0 the function to solve is passed as an argument
+     * to the {@link #solve(UnivariateRealFunction, double, double)} or
+     * {@link UnivariateRealSolverImpl#solve(UnivariateRealFunction, double, double, double)}
+     * method.
+     */
+    @Deprecated
+    public NewtonSolver(DifferentiableUnivariateRealFunction f) {
+        super(f, 100, 1E-6);
+    }
+
+    /**
+     * Construct a solver.
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    public NewtonSolver() {
+        super(100, 1E-6);
+    }
+
+    /** {@inheritDoc} */
+    @Deprecated
+    public double solve(final double min, final double max)
+        throws MaxIterationsExceededException, FunctionEvaluationException  {
+        return solve(f, min, max);
+    }
+
+    /** {@inheritDoc} */
+    @Deprecated
+    public double solve(final double min, final double max, final double startValue)
+        throws MaxIterationsExceededException, FunctionEvaluationException  {
+        return solve(f, min, max, startValue);
+    }
+
+    /**
+     * Find a zero near the midpoint of <code>min</code> and <code>max</code>.
+     *
+     * @param f the function to solve
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @param maxEval Maximum number of evaluations.
+     * @return the value where the function is zero
+     * @throws MaxIterationsExceededException if the maximum iteration count is exceeded
+     * @throws FunctionEvaluationException if an error occurs evaluating the function or derivative
+     * @throws IllegalArgumentException if min is not less than max
+     */
+    @Override
+    public double solve(int maxEval, final UnivariateRealFunction f,
+                        final double min, final double max)
+        throws MaxIterationsExceededException, FunctionEvaluationException  {
+        setMaximalIterationCount(maxEval);
+        return solve(f, min, max);
+    }
+
+    /**
+     * Find a zero near the midpoint of <code>min</code> and <code>max</code>.
+     *
+     * @param f the function to solve
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @return the value where the function is zero
+     * @throws MaxIterationsExceededException if the maximum iteration count is exceeded
+     * @throws FunctionEvaluationException if an error occurs evaluating the function or derivative
+     * @throws IllegalArgumentException if min is not less than max
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    public double solve(final UnivariateRealFunction f,
+                        final double min, final double max)
+        throws MaxIterationsExceededException, FunctionEvaluationException  {
+        return solve(f, min, max, UnivariateRealSolverUtils.midpoint(min, max));
+    }
+
+    /**
+     * Find a zero near the value <code>startValue</code>.
+     *
+     * @param f the function to solve
+     * @param min the lower bound for the interval (ignored).
+     * @param max the upper bound for the interval (ignored).
+     * @param startValue the start value to use.
+     * @param maxEval Maximum number of evaluations.
+     * @return the value where the function is zero
+     * @throws MaxIterationsExceededException if the maximum iteration count is exceeded
+     * @throws FunctionEvaluationException if an error occurs evaluating the function or derivative
+     * @throws IllegalArgumentException if startValue is not between min and max or
+     * if function is not a {@link DifferentiableUnivariateRealFunction} instance
+     */
+    @Override
+    public double solve(int maxEval, final UnivariateRealFunction f,
+                        final double min, final double max, final double startValue)
+        throws MaxIterationsExceededException, FunctionEvaluationException {
+        setMaximalIterationCount(maxEval);
+        return solve(f, min, max, startValue);
+    }
+
+    /**
+     * Find a zero near the value <code>startValue</code>.
+     *
+     * @param f the function to solve
+     * @param min the lower bound for the interval (ignored).
+     * @param max the upper bound for the interval (ignored).
+     * @param startValue the start value to use.
+     * @return the value where the function is zero
+     * @throws MaxIterationsExceededException if the maximum iteration count is exceeded
+     * @throws FunctionEvaluationException if an error occurs evaluating the function or derivative
+     * @throws IllegalArgumentException if startValue is not between min and max or
+     * if function is not a {@link DifferentiableUnivariateRealFunction} instance
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    public double solve(final UnivariateRealFunction f,
+                        final double min, final double max, final double startValue)
+        throws MaxIterationsExceededException, FunctionEvaluationException {
+
+        try {
+
+            final UnivariateRealFunction derivative =
+                ((DifferentiableUnivariateRealFunction) f).derivative();
+            clearResult();
+            verifySequence(min, startValue, max);
+
+            double x0 = startValue;
+            double x1;
+
+            int i = 0;
+            while (i < maximalIterationCount) {
+
+                x1 = x0 - (f.value(x0) / derivative.value(x0));
+                if (FastMath.abs(x1 - x0) <= absoluteAccuracy) {
+                    setResult(x1, i);
+                    return x1;
+                }
+
+                x0 = x1;
+                ++i;
+            }
+
+            throw new MaxIterationsExceededException(maximalIterationCount);
+        } catch (ClassCastException cce) {
+            throw MathRuntimeException.createIllegalArgumentException(LocalizedFormats.FUNCTION_NOT_DIFFERENTIABLE);
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/solvers/RiddersSolver.java b/src/main/java/org/apache/commons/math/analysis/solvers/RiddersSolver.java
new file mode 100644
index 0000000..4f316c7
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/solvers/RiddersSolver.java
@@ -0,0 +1,247 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.analysis.solvers;
+
+import org.apache.commons.math.ConvergenceException;
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.MaxIterationsExceededException;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.util.FastMath;
+import org.apache.commons.math.util.MathUtils;
+
+/**
+ * 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>
+ *
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ * @since 1.2
+ */
+public class RiddersSolver extends UnivariateRealSolverImpl {
+
+    /**
+     * Construct a solver for the given function.
+     *
+     * @param f function to solve
+     * @deprecated as of 2.0 the function to solve is passed as an argument
+     * to the {@link #solve(UnivariateRealFunction, double, double)} or
+     * {@link UnivariateRealSolverImpl#solve(UnivariateRealFunction, double, double, double)}
+     * method.
+     */
+    @Deprecated
+    public RiddersSolver(UnivariateRealFunction f) {
+        super(f, 100, 1E-6);
+    }
+
+    /**
+     * Construct a solver.
+     * @deprecated in 2.2
+     */
+    @Deprecated
+    public RiddersSolver() {
+        super(100, 1E-6);
+    }
+
+    /** {@inheritDoc} */
+    @Deprecated
+    public double solve(final double min, final double max)
+        throws ConvergenceException, FunctionEvaluationException {
+        return solve(f, min, max);
+    }
+
+    /** {@inheritDoc} */
+    @Deprecated
+    public double solve(final double min, final double max, final double initial)
+        throws ConvergenceException, FunctionEvaluationException {
+        return solve(f, min, max, initial);
+    }
+
+    /**
+     * Find a root in the given interval with initial value.
+     * <p>
+     * Requires bracketing condition.</p>
+     *
+     * @param f the function to solve
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @param initial the start value to use
+     * @param maxEval Maximum number of evaluations.
+     * @return the point at which the function value is zero
+     * @throws MaxIterationsExceededException if the maximum iteration count is exceeded
+     * @throws FunctionEvaluationException if an error occurs evaluating the function
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    @Override
+    public double solve(int maxEval, final UnivariateRealFunction f,
+                        final double min, final double max, final double initial)
+        throws MaxIterationsExceededException, FunctionEvaluationException {
+        setMaximalIterationCount(maxEval);
+        return solve(f, min, max, initial);
+    }
+
+    /**
+     * Find a root in the given interval with initial value.
+     * <p>
+     * Requires bracketing condition.</p>
+     *
+     * @param f the function to solve
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @param initial the start value to use
+     * @return the point at which the function value is zero
+     * @throws MaxIterationsExceededException if the maximum iteration count is exceeded
+     * @throws FunctionEvaluationException if an error occurs evaluating the function
+     * @throws IllegalArgumentException if any parameters are invalid
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    public double solve(final UnivariateRealFunction f,
+                        final double min, final double max, final double initial)
+        throws MaxIterationsExceededException, FunctionEvaluationException {
+
+        // check for zeros before verifying bracketing
+        if (f.value(min) == 0.0) { return min; }
+        if (f.value(max) == 0.0) { return max; }
+        if (f.value(initial) == 0.0) { return initial; }
+
+        verifyBracketing(min, max, f);
+        verifySequence(min, initial, max);
+        if (isBracketing(min, initial, f)) {
+            return solve(f, min, initial);
+        } else {
+            return solve(f, initial, max);
+        }
+    }
+
+    /**
+     * Find a root in the given interval.
+     * <p>
+     * Requires bracketing condition.</p>
+     *
+     * @param f the function to solve
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @param maxEval Maximum number of evaluations.
+     * @return the point at which the function value is zero
+     * @throws MaxIterationsExceededException if the maximum iteration count is exceeded
+     * @throws FunctionEvaluationException if an error occurs evaluating the function
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    @Override
+    public double solve(int maxEval, final UnivariateRealFunction f,
+                        final double min, final double max)
+        throws MaxIterationsExceededException, FunctionEvaluationException {
+        setMaximalIterationCount(maxEval);
+        return solve(f, min, max);
+    }
+
+    /**
+     * Find a root in the given interval.
+     * <p>
+     * Requires bracketing condition.</p>
+     *
+     * @param f the function to solve
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @return the point at which the function value is zero
+     * @throws MaxIterationsExceededException if the maximum iteration count is exceeded
+     * @throws FunctionEvaluationException if an error occurs evaluating the function
+     * @throws IllegalArgumentException if any parameters are invalid
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    public double solve(final UnivariateRealFunction f,
+                        final double min, final double max)
+        throws MaxIterationsExceededException, FunctionEvaluationException {
+
+        // [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 = f.value(x1);
+        double x2 = max;
+        double y2 = f.value(x2);
+
+        // check for zeros before verifying bracketing
+        if (y1 == 0.0) {
+            return min;
+        }
+        if (y2 == 0.0) {
+            return max;
+        }
+        verifyBracketing(min, max, f);
+
+        int i = 1;
+        double oldx = Double.POSITIVE_INFINITY;
+        while (i <= maximalIterationCount) {
+            // calculate the new root approximation
+            final double x3 = 0.5 * (x1 + x2);
+            final double y3 = f.value(x3);
+            if (FastMath.abs(y3) <= functionValueAccuracy) {
+                setResult(x3, i);
+                return result;
+            }
+            final double delta = 1 - (y1 * y2) / (y3 * y3);  // delta > 1 due to bracketing
+            final double correction = (MathUtils.sign(y2) * MathUtils.sign(y3)) *
+                                      (x3 - x1) / FastMath.sqrt(delta);
+            final double x = x3 - correction;                // correction != 0
+            final double y = f.value(x);
+
+            // check for convergence
+            final double tolerance = FastMath.max(relativeAccuracy * FastMath.abs(x), absoluteAccuracy);
+            if (FastMath.abs(x - oldx) <= tolerance) {
+                setResult(x, i);
+                return result;
+            }
+            if (FastMath.abs(y) <= functionValueAccuracy) {
+                setResult(x, i);
+                return result;
+            }
+
+            // prepare the new interval for next iteration
+            // Ridders' method guarantees x1 < x < x2
+            if (correction > 0.0) {             // x1 < x < x3
+                if (MathUtils.sign(y1) + MathUtils.sign(y) == 0.0) {
+                    x2 = x;
+                    y2 = y;
+                } else {
+                    x1 = x;
+                    x2 = x3;
+                    y1 = y;
+                    y2 = y3;
+                }
+            } else {                            // x3 < x < x2
+                if (MathUtils.sign(y2) + MathUtils.sign(y) == 0.0) {
+                    x1 = x;
+                    y1 = y;
+                } else {
+                    x1 = x3;
+                    x2 = x;
+                    y1 = y3;
+                    y2 = y;
+                }
+            }
+            oldx = x;
+            i++;
+        }
+        throw new MaxIterationsExceededException(maximalIterationCount);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/solvers/SecantSolver.java b/src/main/java/org/apache/commons/math/analysis/solvers/SecantSolver.java
new file mode 100644
index 0000000..325b662
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/solvers/SecantSolver.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.math.analysis.solvers;
+
+import org.apache.commons.math.ConvergenceException;
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.MaxIterationsExceededException;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.util.FastMath;
+
+
+/**
+ * Implements a modified version of the
+ * <a href="http://mathworld.wolfram.com/SecantMethod.html">secant method</a>
+ * for approximating a zero of a real univariate function.
+ * <p>
+ * The algorithm is modified to maintain bracketing of a root by successive
+ * approximations. Because of forced bracketing, convergence may be slower than
+ * the unrestricted secant algorithm. However, this implementation should in
+ * general outperform the
+ * <a href="http://mathworld.wolfram.com/MethodofFalsePosition.html">
+ * regula falsi method.</a></p>
+ * <p>
+ * The function is assumed to be continuous but not necessarily smooth.</p>
+ *
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ */
+public class SecantSolver extends UnivariateRealSolverImpl {
+
+    /**
+     * Construct a solver for the given function.
+     * @param f function to solve.
+     * @deprecated as of 2.0 the function to solve is passed as an argument
+     * to the {@link #solve(UnivariateRealFunction, double, double)} or
+     * {@link UnivariateRealSolverImpl#solve(UnivariateRealFunction, double, double, double)}
+     * method.
+     */
+    @Deprecated
+    public SecantSolver(UnivariateRealFunction f) {
+        super(f, 100, 1E-6);
+    }
+
+    /**
+     * Construct a solver.
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    public SecantSolver() {
+        super(100, 1E-6);
+    }
+
+    /** {@inheritDoc} */
+    @Deprecated
+    public double solve(final double min, final double max)
+        throws ConvergenceException, FunctionEvaluationException {
+        return solve(f, min, max);
+    }
+
+    /** {@inheritDoc} */
+    @Deprecated
+    public double solve(final double min, final double max, final double initial)
+        throws ConvergenceException, FunctionEvaluationException {
+        return solve(f, min, max, initial);
+    }
+
+    /**
+     * Find a zero in the given interval.
+     *
+     * @param f the function to solve
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @param initial the start value to use (ignored)
+     * @param maxEval Maximum number of evaluations.
+     * @return the value where the function is zero
+     * @throws MaxIterationsExceededException if the maximum iteration count is exceeded
+     * @throws FunctionEvaluationException if an error occurs evaluating the function
+     * @throws IllegalArgumentException if min is not less than max or the
+     * signs of the values of the function at the endpoints are not opposites
+     */
+    @Override
+    public double solve(int maxEval, final UnivariateRealFunction f,
+                        final double min, final double max, final double initial)
+        throws MaxIterationsExceededException, FunctionEvaluationException {
+        setMaximalIterationCount(maxEval);
+        return solve(f, min, max, initial);
+    }
+
+    /**
+     * Find a zero in the given interval.
+     *
+     * @param f the function to solve
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @param initial the start value to use (ignored)
+     * @return the value where the function is zero
+     * @throws MaxIterationsExceededException if the maximum iteration count is exceeded
+     * @throws FunctionEvaluationException if an error occurs evaluating the function
+     * @throws IllegalArgumentException if min is not less than max or the
+     * signs of the values of the function at the endpoints are not opposites
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    public double solve(final UnivariateRealFunction f,
+                        final double min, final double max, final double initial)
+        throws MaxIterationsExceededException, FunctionEvaluationException {
+        return solve(f, min, max);
+    }
+
+    /**
+     * Find a zero in the given interval.
+     * @param f the function to solve
+     * @param min the lower bound for the interval.
+     * @param max the upper bound for the interval.
+     * @param maxEval Maximum number of evaluations.
+     * @return the value where the function is zero
+     * @throws MaxIterationsExceededException  if the maximum iteration count is exceeded
+     * @throws FunctionEvaluationException if an error occurs evaluating the function
+     * @throws IllegalArgumentException if min is not less than max or the
+     * signs of the values of the function at the endpoints are not opposites
+     */
+    @Override
+    public double solve(int maxEval, final UnivariateRealFunction f,
+                        final double min, final double max)
+        throws MaxIterationsExceededException, FunctionEvaluationException {
+        setMaximalIterationCount(maxEval);
+        return solve(f, min, max);
+    }
+
+    /**
+     * Find a zero in the given interval.
+     * @param f the function to solve
+     * @param min the lower bound for the interval.
+     * @param max the upper bound for the interval.
+     * @return the value where the function is zero
+     * @throws MaxIterationsExceededException  if the maximum iteration count is exceeded
+     * @throws FunctionEvaluationException if an error occurs evaluating the function
+     * @throws IllegalArgumentException if min is not less than max or the
+     * signs of the values of the function at the endpoints are not opposites
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    public double solve(final UnivariateRealFunction f,
+                        final double min, final double max)
+        throws MaxIterationsExceededException, FunctionEvaluationException {
+
+        clearResult();
+        verifyInterval(min, max);
+
+        // Index 0 is the old approximation for the root.
+        // Index 1 is the last calculated approximation  for the root.
+        // Index 2 is a bracket for the root with respect to x0.
+        // OldDelta is the length of the bracketing interval of the last
+        // iteration.
+        double x0 = min;
+        double x1 = max;
+        double y0 = f.value(x0);
+        double y1 = f.value(x1);
+
+        // Verify bracketing
+        if (y0 * y1 >= 0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.SAME_SIGN_AT_ENDPOINTS, min, max, y0, y1);
+        }
+
+        double x2 = x0;
+        double y2 = y0;
+        double oldDelta = x2 - x1;
+        int i = 0;
+        while (i < maximalIterationCount) {
+            if (FastMath.abs(y2) < FastMath.abs(y1)) {
+                x0 = x1;
+                x1 = x2;
+                x2 = x0;
+                y0 = y1;
+                y1 = y2;
+                y2 = y0;
+            }
+            if (FastMath.abs(y1) <= functionValueAccuracy) {
+                setResult(x1, i);
+                return result;
+            }
+            if (FastMath.abs(oldDelta) <
+                FastMath.max(relativeAccuracy * FastMath.abs(x1), absoluteAccuracy)) {
+                setResult(x1, i);
+                return result;
+            }
+            double delta;
+            if (FastMath.abs(y1) > FastMath.abs(y0)) {
+                // Function value increased in last iteration. Force bisection.
+                delta = 0.5 * oldDelta;
+            } else {
+                delta = (x0 - x1) / (1 - y0 / y1);
+                if (delta / oldDelta > 1) {
+                    // New approximation falls outside bracket.
+                    // Fall back to bisection.
+                    delta = 0.5 * oldDelta;
+                }
+            }
+            x0 = x1;
+            y0 = y1;
+            x1 = x1 + delta;
+            y1 = f.value(x1);
+            if ((y1 > 0) == (y2 > 0)) {
+                // New bracket is (x0,x1).
+                x2 = x0;
+                y2 = y0;
+            }
+            oldDelta = x2 - x1;
+            i++;
+        }
+        throw new MaxIterationsExceededException(maximalIterationCount);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/solvers/UnivariateRealSolver.java b/src/main/java/org/apache/commons/math/analysis/solvers/UnivariateRealSolver.java
new file mode 100644
index 0000000..6540f67
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/solvers/UnivariateRealSolver.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.math.analysis.solvers;
+
+import org.apache.commons.math.ConvergenceException;
+import org.apache.commons.math.ConvergingAlgorithm;
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+
+
+/**
+ * Interface for (univariate real) rootfinding algorithms.
+ * <p>
+ * Implementations will search for only one zero in the given interval.</p>
+ *
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ */
+public interface UnivariateRealSolver extends ConvergingAlgorithm {
+
+    /**
+     * Set the function value accuracy.
+     * <p>
+     * This is used to determine when an evaluated function value or some other
+     * value which is used as divisor is zero.</p>
+     * <p>
+     * This is a safety guard and it shouldn't be necessary to change this in
+     * general.</p>
+     *
+     * @param accuracy the accuracy.
+     * @throws IllegalArgumentException if the accuracy can't be achieved by
+     * the solver or is otherwise deemed unreasonable.
+     */
+    void setFunctionValueAccuracy(double accuracy);
+
+    /**
+     * Get the actual function value accuracy.
+     * @return the accuracy
+     */
+    double getFunctionValueAccuracy();
+
+    /**
+     * Reset the actual function accuracy to the default.
+     * The default value is provided by the solver implementation.
+     */
+    void resetFunctionValueAccuracy();
+
+    /**
+     * Solve for a zero root in the given interval.
+     * <p>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.</p>
+     *
+     * @param min the lower bound for the interval.
+     * @param max the upper bound for the interval.
+     * @return a value where the function is zero
+     * @throws ConvergenceException if the maximum iteration count is exceeded
+     * or the solver detects convergence problems otherwise.
+     * @throws FunctionEvaluationException if an error occurs evaluating the function
+     * @throws IllegalArgumentException if min > max or the endpoints do not
+     * satisfy the requirements specified by the solver
+     * @deprecated replaced by {@link #solve(UnivariateRealFunction, double, double)}
+     * since 2.0
+     */
+    @Deprecated
+    double solve(double min, double max) throws ConvergenceException, FunctionEvaluationException;
+
+    /**
+     * Solve for a zero root in the given interval.
+     * <p>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.</p>
+     *
+     * @param f the function to solve.
+     * @param min the lower bound for the interval.
+     * @param max the upper bound for the interval.
+     * @return a value where the function is zero
+     * @throws ConvergenceException if the maximum iteration count is exceeded
+     * or the solver detects convergence problems otherwise.
+     * @throws FunctionEvaluationException if an error occurs evaluating the function
+     * @throws IllegalArgumentException if min > max or the endpoints do not
+     * satisfy the requirements specified by the solver
+     * @since 2.0
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    double solve(UnivariateRealFunction f, double min, double max)
+        throws ConvergenceException, FunctionEvaluationException;
+
+    /**
+     * Solve for a zero in the given interval, start at startValue.
+     * <p>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.</p>
+     *
+     * @param min the lower bound for the interval.
+     * @param max the upper bound for the interval.
+     * @param startValue the start value to use
+     * @return a value where the function is zero
+     * @throws ConvergenceException if the maximum iteration count is exceeded
+     * or the solver detects convergence problems otherwise.
+     * @throws FunctionEvaluationException if an error occurs evaluating the function
+     * @throws IllegalArgumentException if min > max or the arguments do not
+     * satisfy the requirements specified by the solver
+     * @deprecated replaced by {@link #solve(UnivariateRealFunction, double, double, double)}
+     * since 2.0
+     */
+    @Deprecated
+    double solve(double min, double max, double startValue)
+        throws ConvergenceException, FunctionEvaluationException, IllegalArgumentException;
+
+    /**
+     * Solve for a zero in the given interval, start at startValue.
+     * <p>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.</p>
+     *
+     * @param f the function to solve.
+     * @param min the lower bound for the interval.
+     * @param max the upper bound for the interval.
+     * @param startValue the start value to use
+     * @return a value where the function is zero
+     * @throws ConvergenceException if the maximum iteration count is exceeded
+     * or the solver detects convergence problems otherwise.
+     * @throws FunctionEvaluationException if an error occurs evaluating the function
+     * @throws IllegalArgumentException if min > max or the arguments do not
+     * satisfy the requirements specified by the solver
+     * @since 2.0
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    double solve(UnivariateRealFunction f, double min, double max, double startValue)
+        throws ConvergenceException, FunctionEvaluationException, IllegalArgumentException;
+
+    /**
+     * Get the result of the last run of the solver.
+     *
+     * @return the last result.
+     * @throws IllegalStateException if there is no result available, either
+     * because no result was yet computed or the last attempt failed.
+     */
+    double getResult();
+
+    /**
+     * Get the result of the last run of the solver.
+     *
+     * @return the value of the function at the last result.
+     * @throws IllegalStateException if there is no result available, either
+     * because no result was yet computed or the last attempt failed.
+     */
+    double getFunctionValue();
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/solvers/UnivariateRealSolverFactory.java b/src/main/java/org/apache/commons/math/analysis/solvers/UnivariateRealSolverFactory.java
new file mode 100644
index 0000000..46b7234
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/solvers/UnivariateRealSolverFactory.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.math.analysis.solvers;
+
+/**
+ * Abstract factory class used to create {@link UnivariateRealSolver} instances.
+ * <p>
+ * Solvers implementing the following algorithms are supported:
+ * <ul>
+ * <li>Bisection</li>
+ * <li>Brent's method</li>
+ * <li>Secant method</li>
+ * </ul>
+ * Concrete factories extending this class also specify a default solver, instances of which
+ * are returned by <code>newDefaultSolver()</code>.</p>
+ * <p>
+ * Common usage:<pre>
+ * SolverFactory factory = UnivariateRealSolverFactory.newInstance();</p>
+ *
+ * // create a Brent solver to use
+ * BrentSolver solver = factory.newBrentSolver();
+ * </pre>
+ *
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+public abstract class UnivariateRealSolverFactory {
+    /**
+     * Default constructor.
+     */
+    protected UnivariateRealSolverFactory() {
+    }
+
+    /**
+     * Create a new factory.
+     * @return a new factory.
+     */
+    public static UnivariateRealSolverFactory newInstance() {
+        return new UnivariateRealSolverFactoryImpl();
+    }
+
+    /**
+     * Create a new {@link UnivariateRealSolver}.  The
+     * actual solver returned is determined by the underlying factory.
+     * @return the new solver.
+     */
+    public abstract UnivariateRealSolver newDefaultSolver();
+
+    /**
+     * Create a new {@link UnivariateRealSolver}.  The
+     * solver is an implementation of the bisection method.
+     * @return the new solver.
+     */
+    public abstract UnivariateRealSolver newBisectionSolver();
+
+    /**
+     * Create a new {@link UnivariateRealSolver}.  The
+     * solver is an implementation of the Brent method.
+     * @return the new solver.
+     */
+    public abstract UnivariateRealSolver newBrentSolver();
+
+    /**
+     * Create a new {@link UnivariateRealSolver}.  The
+     * solver is an implementation of Newton's Method.
+     * @return the new solver.
+     */
+    public abstract UnivariateRealSolver newNewtonSolver();
+
+    /**
+     * Create a new {@link UnivariateRealSolver}.  The
+     * solver is an implementation of the secant method.
+     * @return the new solver.
+     */
+    public abstract UnivariateRealSolver newSecantSolver();
+
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/solvers/UnivariateRealSolverFactoryImpl.java b/src/main/java/org/apache/commons/math/analysis/solvers/UnivariateRealSolverFactoryImpl.java
new file mode 100644
index 0000000..cb4c6b2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/solvers/UnivariateRealSolverFactoryImpl.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.math.analysis.solvers;
+
+/**
+ * A concrete {@link  UnivariateRealSolverFactory}.  This is the default solver factory
+ * used by commons-math.
+ * <p>
+ * The default solver returned by this factory is a {@link BrentSolver}.</p>
+ *
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+public class UnivariateRealSolverFactoryImpl extends UnivariateRealSolverFactory {
+
+    /**
+     * Default constructor.
+     */
+    public UnivariateRealSolverFactoryImpl() {
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public UnivariateRealSolver newDefaultSolver() {
+        return newBrentSolver();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public UnivariateRealSolver newBisectionSolver() {
+        return new BisectionSolver();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public UnivariateRealSolver newBrentSolver() {
+        return new BrentSolver();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public UnivariateRealSolver newNewtonSolver() {
+        return new NewtonSolver();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public UnivariateRealSolver newSecantSolver() {
+        return new SecantSolver();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/solvers/UnivariateRealSolverImpl.java b/src/main/java/org/apache/commons/math/analysis/solvers/UnivariateRealSolverImpl.java
new file mode 100644
index 0000000..557c767
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/solvers/UnivariateRealSolverImpl.java
@@ -0,0 +1,304 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.analysis.solvers;
+
+import org.apache.commons.math.ConvergingAlgorithmImpl;
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.ConvergenceException;
+import org.apache.commons.math.exception.NullArgumentException;
+
+/**
+ * Provide a default implementation for several functions useful to generic
+ * solvers.
+ *
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ * @deprecated in 2.2 (to be removed in 3.0).
+ */
+@Deprecated
+public abstract class UnivariateRealSolverImpl
+    extends ConvergingAlgorithmImpl implements UnivariateRealSolver {
+
+    /** Maximum error of function. */
+    protected double functionValueAccuracy;
+
+    /** Default maximum error of function. */
+    protected double defaultFunctionValueAccuracy;
+
+    /** Indicates where a root has been computed. */
+    protected boolean resultComputed = false;
+
+    /** The last computed root. */
+    protected double result;
+
+    /** Value of the function at the last computed result. */
+    protected double functionValue;
+
+    /** The function to solve.
+     * @deprecated as of 2.0 the function to solve is passed as an argument
+     * to the {@link #solve(UnivariateRealFunction, double, double)} or
+     * {@link UnivariateRealSolverImpl#solve(UnivariateRealFunction, double, double, double)}
+     * method. */
+    @Deprecated
+    protected UnivariateRealFunction f;
+
+    /**
+     * Construct a solver with given iteration count and accuracy.
+     *
+     * @param f the function to solve.
+     * @param defaultAbsoluteAccuracy maximum absolute error
+     * @param defaultMaximalIterationCount maximum number of iterations
+     * @throws IllegalArgumentException if f is null or the
+     * defaultAbsoluteAccuracy is not valid
+     * @deprecated as of 2.0 the function to solve is passed as an argument
+     * to the {@link #solve(UnivariateRealFunction, double, double)} or
+     * {@link UnivariateRealSolverImpl#solve(UnivariateRealFunction, double, double, double)}
+     * method.
+     */
+    @Deprecated
+    protected UnivariateRealSolverImpl(final UnivariateRealFunction f,
+                                       final int defaultMaximalIterationCount,
+                                       final double defaultAbsoluteAccuracy) {
+        super(defaultMaximalIterationCount, defaultAbsoluteAccuracy);
+        if (f == null) {
+            throw new NullArgumentException(LocalizedFormats.FUNCTION);
+        }
+        this.f = f;
+        this.defaultFunctionValueAccuracy = 1.0e-15;
+        this.functionValueAccuracy = defaultFunctionValueAccuracy;
+    }
+
+    /**
+     * Construct a solver with given iteration count and accuracy.
+     *
+     * @param defaultAbsoluteAccuracy maximum absolute error
+     * @param defaultMaximalIterationCount maximum number of iterations
+     * @throws IllegalArgumentException if f is null or the
+     * defaultAbsoluteAccuracy is not valid
+     */
+    protected UnivariateRealSolverImpl(final int defaultMaximalIterationCount,
+                                       final double defaultAbsoluteAccuracy) {
+        super(defaultMaximalIterationCount, defaultAbsoluteAccuracy);
+        this.defaultFunctionValueAccuracy = 1.0e-15;
+        this.functionValueAccuracy = defaultFunctionValueAccuracy;
+    }
+
+    /** Check if a result has been computed.
+     * @exception IllegalStateException if no result has been computed
+     */
+    protected void checkResultComputed() throws IllegalStateException {
+        if (!resultComputed) {
+            throw MathRuntimeException.createIllegalStateException(LocalizedFormats.NO_RESULT_AVAILABLE);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public double getResult() {
+        checkResultComputed();
+        return result;
+    }
+
+    /** {@inheritDoc} */
+    public double getFunctionValue() {
+        checkResultComputed();
+        return functionValue;
+    }
+
+    /** {@inheritDoc} */
+    public void setFunctionValueAccuracy(final double accuracy) {
+        functionValueAccuracy = accuracy;
+    }
+
+    /** {@inheritDoc} */
+    public double getFunctionValueAccuracy() {
+        return functionValueAccuracy;
+    }
+
+    /** {@inheritDoc} */
+    public void resetFunctionValueAccuracy() {
+        functionValueAccuracy = defaultFunctionValueAccuracy;
+    }
+
+    /**
+     * Solve for a zero root in the given interval.
+     * <p>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.</p>
+     *
+     * @param function the function to solve.
+     * @param min the lower bound for the interval.
+     * @param max the upper bound for the interval.
+     * @param maxEval Maximum number of evaluations.
+     * @return a value where the function is zero
+     * @throws ConvergenceException if the maximum iteration count is exceeded
+     * or the solver detects convergence problems otherwise.
+     * @throws FunctionEvaluationException if an error occurs evaluating the function
+     * @throws IllegalArgumentException if min > max or the endpoints do not
+     * satisfy the requirements specified by the solver
+     * @since 2.2
+     */
+    public double solve(int maxEval, UnivariateRealFunction function, double min, double max)
+        throws ConvergenceException, FunctionEvaluationException {
+        throw MathRuntimeException.createUnsupportedOperationException(LocalizedFormats.NOT_OVERRIDEN);
+    }
+
+    /**
+     * Solve for a zero in the given interval, start at startValue.
+     * <p>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.</p>
+     *
+     * @param function the function to solve.
+     * @param min the lower bound for the interval.
+     * @param max the upper bound for the interval.
+     * @param startValue the start value to use
+     * @param maxEval Maximum number of evaluations.
+     * @return a value where the function is zero
+     * @throws ConvergenceException if the maximum iteration count is exceeded
+     * or the solver detects convergence problems otherwise.
+     * @throws FunctionEvaluationException if an error occurs evaluating the function
+     * @throws IllegalArgumentException if min > max or the arguments do not
+     * satisfy the requirements specified by the solver
+     * @since 2.2
+     */
+    public double solve(int maxEval, UnivariateRealFunction function, double min, double max, double startValue)
+        throws ConvergenceException, FunctionEvaluationException, IllegalArgumentException {
+        throw MathRuntimeException.createUnsupportedOperationException(LocalizedFormats.NOT_OVERRIDEN);
+    }
+
+    /**
+     * Convenience function for implementations.
+     *
+     * @param newResult the result to set
+     * @param iterationCount the iteration count to set
+     */
+    protected final void setResult(final double newResult, final int iterationCount) {
+        this.result         = newResult;
+        this.iterationCount = iterationCount;
+        this.resultComputed = true;
+    }
+
+    /**
+     * Convenience function for implementations.
+     *
+     * @param x the result to set
+     * @param fx the result to set
+     * @param iterationCount the iteration count to set
+     */
+    protected final void setResult(final double x, final double fx,
+                                   final int iterationCount) {
+        this.result         = x;
+        this.functionValue  = fx;
+        this.iterationCount = iterationCount;
+        this.resultComputed = true;
+    }
+
+    /**
+     * Convenience function for implementations.
+     */
+    protected final void clearResult() {
+        this.iterationCount = 0;
+        this.resultComputed = false;
+    }
+
+    /**
+     * Returns true iff the function takes opposite signs at the endpoints.
+     *
+     * @param lower  the lower endpoint
+     * @param upper  the upper endpoint
+     * @param function the function
+     * @return true if f(lower) * f(upper) < 0
+     * @throws FunctionEvaluationException if an error occurs evaluating the function at the endpoints
+     */
+    protected boolean isBracketing(final double lower, final double upper,
+                                   final UnivariateRealFunction function)
+        throws FunctionEvaluationException {
+        final double f1 = function.value(lower);
+        final double f2 = function.value(upper);
+        return (f1 > 0 && f2 < 0) || (f1 < 0 && f2 > 0);
+    }
+
+    /**
+     * Returns true if the arguments form a (strictly) increasing sequence
+     *
+     * @param start  first number
+     * @param mid   second number
+     * @param end  third number
+     * @return true if the arguments form an increasing sequence
+     */
+    protected boolean isSequence(final double start, final double mid, final double end) {
+        return (start < mid) && (mid < end);
+    }
+
+    /**
+     * Verifies that the endpoints specify an interval,
+     * throws IllegalArgumentException if not
+     *
+     * @param lower  lower endpoint
+     * @param upper upper endpoint
+     * @throws IllegalArgumentException
+     */
+    protected void verifyInterval(final double lower, final double upper) {
+        if (lower >= upper) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.ENDPOINTS_NOT_AN_INTERVAL,
+                    lower, upper);
+        }
+    }
+
+    /**
+     * Verifies that <code>lower < initial < upper</code>
+     * throws IllegalArgumentException if not
+     *
+     * @param lower  lower endpoint
+     * @param initial initial value
+     * @param upper upper endpoint
+     * @throws IllegalArgumentException
+     */
+    protected void verifySequence(final double lower, final double initial, final double upper) {
+        if (!isSequence(lower, initial, upper)) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.INVALID_INTERVAL_INITIAL_VALUE_PARAMETERS,
+                    lower, initial, upper);
+        }
+    }
+
+    /**
+     * Verifies that the endpoints specify an interval and the function takes
+     * opposite signs at the endpoints, throws IllegalArgumentException if not
+     *
+     * @param lower  lower endpoint
+     * @param upper upper endpoint
+     * @param function function
+     * @throws IllegalArgumentException
+     * @throws FunctionEvaluationException if an error occurs evaluating the function at the endpoints
+     */
+    protected void verifyBracketing(final double lower, final double upper,
+                                    final UnivariateRealFunction function)
+        throws FunctionEvaluationException {
+
+        verifyInterval(lower, upper);
+        if (!isBracketing(lower, upper, function)) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.SAME_SIGN_AT_ENDPOINTS,
+                    lower, upper, function.value(lower), function.value(upper));
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/solvers/UnivariateRealSolverUtils.java b/src/main/java/org/apache/commons/math/analysis/solvers/UnivariateRealSolverUtils.java
new file mode 100644
index 0000000..3186d6a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/solvers/UnivariateRealSolverUtils.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.math.analysis.solvers;
+
+import org.apache.commons.math.ConvergenceException;
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.exception.NullArgumentException;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Utility routines for {@link UnivariateRealSolver} objects.
+ *
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ */
+public class UnivariateRealSolverUtils {
+
+    /**
+     * Default constructor.
+     */
+    private UnivariateRealSolverUtils() {
+        super();
+    }
+
+    /**
+     * Convenience method to find a zero of a univariate real function.  A default
+     * solver is used.
+     *
+     * @param f the function.
+     * @param x0 the lower bound for the interval.
+     * @param x1 the upper bound for the interval.
+     * @return a value where the function is zero.
+     * @throws ConvergenceException if the iteration count was exceeded
+     * @throws FunctionEvaluationException if an error occurs evaluating the function
+     * @throws IllegalArgumentException if f is null or the endpoints do not
+     * specify a valid interval
+     */
+    public static double solve(UnivariateRealFunction f, double x0, double x1)
+    throws ConvergenceException, FunctionEvaluationException {
+        setup(f);
+        return LazyHolder.FACTORY.newDefaultSolver().solve(f, x0, x1);
+    }
+
+    /**
+     * Convenience method to find a zero of a univariate real function.  A default
+     * solver is used.
+     *
+     * @param f the function
+     * @param x0 the lower bound for the interval
+     * @param x1 the upper bound for the interval
+     * @param absoluteAccuracy the accuracy to be used by the solver
+     * @return a value where the function is zero
+     * @throws ConvergenceException if the iteration count is exceeded
+     * @throws FunctionEvaluationException if an error occurs evaluating the function
+     * @throws IllegalArgumentException if f is null, the endpoints do not
+     * specify a valid interval, or the absoluteAccuracy is not valid for the
+     * default solver
+     */
+    public static double solve(UnivariateRealFunction f, double x0, double x1,
+            double absoluteAccuracy) throws ConvergenceException,
+            FunctionEvaluationException {
+
+        setup(f);
+        UnivariateRealSolver solver = LazyHolder.FACTORY.newDefaultSolver();
+        solver.setAbsoluteAccuracy(absoluteAccuracy);
+        return solver.solve(f, x0, x1);
+    }
+
+    /**
+     * This method attempts to find two values a and b satisfying <ul>
+    * <li> <code> lowerBound <= a < initial < b <= upperBound</code> </li>
+     * <li> <code> f(a) * f(b) < 0 </code></li>
+     * </ul>
+     * If f is continuous on <code>[a,b],</code> this means that <code>a</code>
+     * and <code>b</code> bracket a root of f.
+     * <p>
+     * The algorithm starts by setting
+     * <code>a := initial -1; b := initial +1,</code> examines the value of the
+     * function at <code>a</code> and <code>b</code> and keeps moving
+     * the endpoints out by one unit each time through a loop that terminates
+     * when one of the following happens: <ul>
+     * <li> <code> f(a) * f(b) < 0 </code> --  success!</li>
+     * <li> <code> a = lower </code> and <code> b = upper</code>
+     * -- ConvergenceException </li>
+     * <li> <code> Integer.MAX_VALUE</code> iterations elapse
+     * -- ConvergenceException </li>
+     * </ul></p>
+     * <p>
+     * <strong>Note: </strong> this method can take
+     * <code>Integer.MAX_VALUE</code> iterations to throw a
+     * <code>ConvergenceException.</code>  Unless you are confident that there
+     * is a root between <code>lowerBound</code> and <code>upperBound</code>
+     * near <code>initial,</code> it is better to use
+     * {@link #bracket(UnivariateRealFunction, double, double, double, int)},
+     * explicitly specifying the maximum number of iterations.</p>
+     *
+     * @param function the 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, b}
+     * @throws ConvergenceException if a root can not be bracketted
+     * @throws FunctionEvaluationException if an error occurs evaluating the function
+     * @throws IllegalArgumentException if function is null, maximumIterations
+     * is not positive, or initial is not between lowerBound and upperBound
+     */
+    public static double[] bracket(UnivariateRealFunction function,
+            double initial, double lowerBound, double upperBound)
+    throws ConvergenceException, FunctionEvaluationException {
+        return bracket( function, initial, lowerBound, upperBound,
+            Integer.MAX_VALUE ) ;
+    }
+
+     /**
+     * This method attempts to find two values a and b satisfying <ul>
+     * <li> <code> lowerBound <= a < initial < b <= upperBound</code> </li>
+     * <li> <code> f(a) * f(b) <= 0 </code> </li>
+     * </ul>
+     * If f is continuous on <code>[a,b],</code> this means that <code>a</code>
+     * and <code>b</code> bracket a root of f.
+     * <p>
+     * The algorithm starts by setting
+     * <code>a := initial -1; b := initial +1,</code> examines the value of the
+     * function at <code>a</code> and <code>b</code> and keeps moving
+     * the endpoints out by one unit each time through a loop that terminates
+     * when one of the following happens: <ul>
+     * <li> <code> f(a) * f(b) <= 0 </code> --  success!</li>
+     * <li> <code> a = lower </code> and <code> b = upper</code>
+     * -- ConvergenceException </li>
+     * <li> <code> maximumIterations</code> iterations elapse
+     * -- ConvergenceException </li></ul></p>
+     *
+     * @param function the 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, b}.
+     * @throws ConvergenceException if the algorithm fails to find a and b
+     * satisfying the desired conditions
+     * @throws FunctionEvaluationException if an error occurs evaluating the function
+     * @throws IllegalArgumentException if function is null, maximumIterations
+     * is not positive, or initial is not between lowerBound and upperBound
+     */
+    public static double[] bracket(UnivariateRealFunction function,
+            double initial, double lowerBound, double upperBound,
+            int maximumIterations) throws ConvergenceException,
+            FunctionEvaluationException {
+
+        if (function == null) {
+            throw new NullArgumentException(LocalizedFormats.FUNCTION);
+        }
+        if (maximumIterations <= 0)  {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.INVALID_MAX_ITERATIONS, maximumIterations);
+        }
+        if (initial < lowerBound || initial > upperBound || lowerBound >= upperBound) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.INVALID_BRACKETING_PARAMETERS,
+                  lowerBound, initial, upperBound);
+        }
+        double a = initial;
+        double b = initial;
+        double fa;
+        double fb;
+        int numIterations = 0 ;
+
+        do {
+            a = FastMath.max(a - 1.0, lowerBound);
+            b = FastMath.min(b + 1.0, upperBound);
+            fa = function.value(a);
+
+            fb = function.value(b);
+            numIterations++ ;
+        } while ((fa * fb > 0.0) && (numIterations < maximumIterations) &&
+                ((a > lowerBound) || (b < upperBound)));
+
+        if (fa * fb > 0.0 ) {
+            throw new ConvergenceException(
+                      LocalizedFormats.FAILED_BRACKETING,
+                      numIterations, maximumIterations, initial,
+                      lowerBound, upperBound, a, b, fa, fb);
+        }
+
+        return new double[]{a, b};
+    }
+
+    /**
+     * 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) * .5;
+    }
+
+    /**
+     * Checks to see if f is null, throwing IllegalArgumentException if so.
+     * @param f  input function
+     * @throws IllegalArgumentException if f is null
+     */
+    private static void setup(UnivariateRealFunction f) {
+        if (f == null) {
+            throw new NullArgumentException(LocalizedFormats.FUNCTION);
+        }
+    }
+
+    // CHECKSTYLE: stop HideUtilityClassConstructor
+    /** Holder for the factory.
+     * <p>We use here the Initialization On Demand Holder Idiom.</p>
+     */
+    private static class LazyHolder {
+        /** Cached solver factory */
+        private static final UnivariateRealSolverFactory FACTORY = UnivariateRealSolverFactory.newInstance();
+    }
+    // CHECKSTYLE: resume HideUtilityClassConstructor
+
+}
diff --git a/src/main/java/org/apache/commons/math/analysis/solvers/package.html b/src/main/java/org/apache/commons/math/analysis/solvers/package.html
new file mode 100644
index 0000000..bbb49d1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/analysis/solvers/package.html
@@ -0,0 +1,22 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $ -->
+    <body>
+     Root finding algorithms, for univariate real functions.
+    </body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/complex/Complex.java b/src/main/java/org/apache/commons/math/complex/Complex.java
new file mode 100644
index 0000000..ad2bc96
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/complex/Complex.java
@@ -0,0 +1,1007 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.complex;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.math.FieldElement;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.util.MathUtils;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Representation of a Complex number - a number which has both a
+ * real and imaginary part.
+ * <p>
+ * Implementations of arithmetic operations handle <code>NaN</code> and
+ * infinite values according to the rules for {@link java.lang.Double}
+ * arithmetic, applying definitional formulas and returning <code>NaN</code> or
+ * infinite values in real or imaginary parts as these arise in computation.
+ * See individual method javadocs for details.</p>
+ * <p>
+ * {@link #equals} identifies all values with <code>NaN</code> in either real
+ * or imaginary part - e.g., <pre>
+ * <code>1 + NaNi  == NaN + i == NaN + NaNi.</code></pre></p>
+ *
+ * implements Serializable since 2.0
+ *
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ */
+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 the real and imaginary parts.
+     *
+     * @param real the real part
+     * @param imaginary the imaginary part
+     */
+    public Complex(double real, double imaginary) {
+        super();
+        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.
+     * <p>
+     * Returns <code>NaN</code> if either real or imaginary part is
+     * <code>NaN</code> and <code>Double.POSITIVE_INFINITY</code> if
+     * neither part is <code>NaN</code>, but at least one part takes an infinite
+     * value.</p>
+     *
+     * @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);
+        }
+    }
+
+    /**
+     * Return the sum of this complex number and the given complex number.
+     * <p>
+     * Uses the definitional formula
+     * <pre>
+     * (a + bi) + (c + di) = (a+c) + (b+d)i
+     * </pre></p>
+     * <p>
+     * If either this or <code>rhs</code> has a NaN value in either part,
+     * {@link #NaN} is returned; otherwise Inifinite and NaN values are
+     * returned in the parts of the result according to the rules for
+     * {@link java.lang.Double} arithmetic.</p>
+     *
+     * @param rhs the other complex number
+     * @return the complex number sum
+     * @throws NullPointerException if <code>rhs</code> is null
+     */
+    public Complex add(Complex rhs) {
+        return createComplex(real + rhs.getReal(),
+            imaginary + rhs.getImaginary());
+    }
+
+    /**
+     * Return the conjugate of this complex number. The conjugate of
+     * "A + Bi" is "A - Bi".
+     * <p>
+     * {@link #NaN} is returned if either the real or imaginary
+     * part of this Complex number equals <code>Double.NaN</code>.</p>
+     * <p>
+     * If the imaginary part is infinite, and the real part is not NaN,
+     * the returned value has infinite imaginary part of the opposite
+     * sign - e.g. the conjugate of <code>1 + POSITIVE_INFINITY i</code>
+     * is <code>1 - NEGATIVE_INFINITY i</code></p>
+     *
+     * @return the conjugate of this Complex object
+     */
+    public Complex conjugate() {
+        if (isNaN()) {
+            return NaN;
+        }
+        return createComplex(real, -imaginary);
+    }
+
+    /**
+     * Return the quotient of this complex number and the given complex number.
+     * <p>
+     * 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>
+     * <p>
+     * Infinite and NaN values are handled / returned according to the
+     * following rules, applied in the order presented:
+     * <ul>
+     * <li>If either this or <code>rhs</code> has a NaN value in either part,
+     *  {@link #NaN} is returned.</li>
+     * <li>If <code>rhs</code> equals {@link #ZERO}, {@link #NaN} is returned.
+     * </li>
+     * <li>If this and <code>rhs</code> are both infinite,
+     * {@link #NaN} is returned.</li>
+     * <li>If this is finite (i.e., has no infinite or NaN parts) and
+     *  <code>rhs</code> is infinite (one or both parts infinite),
+     * {@link #ZERO} is returned.</li>
+     * <li>If this is infinite and <code>rhs</code> is finite, NaN values are
+     * returned in the parts of the result if the {@link java.lang.Double}
+     * rules applied to the definitional formula force NaN results.</li>
+     * </ul></p>
+     *
+     * @param rhs the other complex number
+     * @return the complex number quotient
+     * @throws NullPointerException if <code>rhs</code> is null
+     */
+    public Complex divide(Complex rhs) {
+        if (isNaN() || rhs.isNaN()) {
+            return NaN;
+        }
+
+        double c = rhs.getReal();
+        double d = rhs.getImaginary();
+        if (c == 0.0 && d == 0.0) {
+            return NaN;
+        }
+
+        if (rhs.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);
+        }
+    }
+
+    /**
+     * Test for the equality of two Complex objects.
+     * <p>
+     * If both the real and imaginary parts of two Complex numbers
+     * are exactly the same, and neither is <code>Double.NaN</code>, the two
+     * Complex objects are considered to be equal.</p>
+     * <p>
+     * All <code>NaN</code> 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</code>, the complex number is equal to
+     * <code>Complex.NaN</code>.</p>
+     *
+     * @param other Object to test for equality to this
+     * @return true if two Complex objects are equal, false if
+     *         object is null, not an instance of Complex, or
+     *         not equal to this Complex instance
+     *
+     */
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+        if (other instanceof Complex){
+            Complex rhs = (Complex)other;
+            if (rhs.isNaN()) {
+                return this.isNaN();
+            } else {
+                return (real == rhs.real) && (imaginary == rhs.imaginary);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Get a hashCode for the complex number.
+     * <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 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;
+    }
+
+    /**
+     * Returns true if either or both parts of this complex number is NaN;
+     * false otherwise
+     *
+     * @return  true if either or both parts of this complex number is NaN;
+     * false otherwise
+     */
+    public boolean isNaN() {
+        return isNaN;
+    }
+
+    /**
+     * Returns true if either the real or imaginary part of this complex number
+     * takes an infinite value (either <code>Double.POSITIVE_INFINITY</code> or
+     * <code>Double.NEGATIVE_INFINITY</code>) and neither part
+     * is <code>NaN</code>.
+     *
+     * @return true if one or both parts of this complex number are infinite
+     * and neither part is <code>NaN</code>
+     */
+    public boolean isInfinite() {
+        return isInfinite;
+    }
+
+    /**
+     * Return the product of this complex number and the given complex number.
+     * <p>
+     * Implements preliminary checks for NaN and infinity followed by
+     * the definitional formula:
+     * <pre><code>
+     * (a + bi)(c + di) = (ac - bd) + (ad + bc)i
+     * </code></pre>
+     * </p>
+     * <p>
+     * Returns {@link #NaN} if either this or <code>rhs</code> has one or more
+     * NaN parts.
+     * </p>
+     * Returns {@link #INF} if neither this nor <code>rhs</code> has one or more
+     * NaN parts and if either this or <code>rhs</code> has one or more
+     * infinite parts (same result is returned regardless of the sign of the
+     * components).
+     * </p>
+     * <p>
+     * Returns finite values in components of the result per the
+     * definitional formula in all remaining cases.
+     *  </p>
+     *
+     * @param rhs the other complex number
+     * @return the complex number product
+     * @throws NullPointerException if <code>rhs</code> is null
+     */
+    public Complex multiply(Complex rhs) {
+        if (isNaN() || rhs.isNaN()) {
+            return NaN;
+        }
+        if (Double.isInfinite(real) || Double.isInfinite(imaginary) ||
+            Double.isInfinite(rhs.real)|| Double.isInfinite(rhs.imaginary)) {
+            // we don't use Complex.isInfinite() to avoid testing for NaN again
+            return INF;
+        }
+        return createComplex(real * rhs.real - imaginary * rhs.imaginary,
+                real * rhs.imaginary + imaginary * rhs.real);
+    }
+
+    /**
+     * Return the product of this complex number and the given scalar number.
+     * <p>
+     * Implements preliminary checks for NaN and infinity followed by
+     * the definitional formula:
+     * <pre><code>
+     * c(a + bi) = (ca) + (cb)i
+     * </code></pre>
+     * </p>
+     * <p>
+     * Returns {@link #NaN} if either this or <code>rhs</code> has one or more
+     * NaN parts.
+     * </p>
+     * Returns {@link #INF} if neither this nor <code>rhs</code> has one or more
+     * NaN parts and if either this or <code>rhs</code> has one or more
+     * infinite parts (same result is returned regardless of the sign of the
+     * components).
+     * </p>
+     * <p>
+     * Returns finite values in components of the result per the
+     * definitional formula in all remaining cases.
+     *  </p>
+     *
+     * @param rhs the scalar number
+     * @return the complex number product
+     */
+    public Complex multiply(double rhs) {
+        if (isNaN() || Double.isNaN(rhs)) {
+            return NaN;
+        }
+        if (Double.isInfinite(real) || Double.isInfinite(imaginary) ||
+            Double.isInfinite(rhs)) {
+            // we don't use Complex.isInfinite() to avoid testing for NaN again
+            return INF;
+        }
+        return createComplex(real * rhs, imaginary * rhs);
+    }
+
+    /**
+     * Return the additive inverse of this complex number.
+     * <p>
+     * Returns <code>Complex.NaN</code> if either real or imaginary
+     * part of this Complex number equals <code>Double.NaN</code>.</p>
+     *
+     * @return the negation of this complex number
+     */
+    public Complex negate() {
+        if (isNaN()) {
+            return NaN;
+        }
+
+        return createComplex(-real, -imaginary);
+    }
+
+    /**
+     * Return the difference between this complex number and the given complex
+     * number.
+      * <p>
+     * Uses the definitional formula
+     * <pre>
+     * (a + bi) - (c + di) = (a-c) + (b-d)i
+     * </pre></p>
+     * <p>
+     * If either this or <code>rhs</code> has a NaN value in either part,
+     * {@link #NaN} is returned; otherwise inifinite and NaN values are
+     * returned in the parts of the result according to the rules for
+     * {@link java.lang.Double} arithmetic. </p>
+     *
+     * @param rhs the other complex number
+     * @return the complex number difference
+     * @throws NullPointerException if <code>rhs</code> is null
+     */
+    public Complex subtract(Complex rhs) {
+        if (isNaN() || rhs.isNaN()) {
+            return NaN;
+        }
+
+        return createComplex(real - rhs.getReal(),
+            imaginary - rhs.getImaginary());
+    }
+
+    /**
+     * Compute the
+     * <a href="http://mathworld.wolfram.com/InverseCosine.html" TARGET="_top">
+     * inverse cosine</a> of this complex number.
+     * <p>
+     * Implements the formula: <pre>
+     * <code> acos(z) = -i (log(z + i (sqrt(1 - z<sup>2</sup>))))</code></pre></p>
+     * <p>
+     * Returns {@link Complex#NaN} if either real or imaginary part of the
+     * input argument is <code>NaN</code> or infinite.</p>
+     *
+     * @return the inverse cosine of this complex number
+     * @since 1.2
+     */
+    public Complex acos() {
+        if (isNaN()) {
+            return Complex.NaN;
+        }
+
+        return this.add(this.sqrt1z().multiply(Complex.I)).log()
+              .multiply(Complex.I.negate());
+    }
+
+    /**
+     * Compute the
+     * <a href="http://mathworld.wolfram.com/InverseSine.html" TARGET="_top">
+     * inverse sine</a> of this complex number.
+     * <p>
+     * Implements the formula: <pre>
+     * <code> asin(z) = -i (log(sqrt(1 - z<sup>2</sup>) + iz)) </code></pre></p>
+     * <p>
+     * Returns {@link Complex#NaN} if either real or imaginary part of the
+     * input argument is <code>NaN</code> or infinite.</p>
+     *
+     * @return the inverse sine of this complex number.
+     * @since 1.2
+     */
+    public Complex asin() {
+        if (isNaN()) {
+            return Complex.NaN;
+        }
+
+        return sqrt1z().add(this.multiply(Complex.I)).log()
+              .multiply(Complex.I.negate());
+    }
+
+    /**
+     * Compute the
+     * <a href="http://mathworld.wolfram.com/InverseTangent.html" TARGET="_top">
+     * inverse tangent</a> of this complex number.
+     * <p>
+     * Implements the formula: <pre>
+     * <code> atan(z) = (i/2) log((i + z)/(i - z)) </code></pre></p>
+     * <p>
+     * Returns {@link Complex#NaN} if either real or imaginary part of the
+     * input argument is <code>NaN</code> or infinite.</p>
+     *
+     * @return the inverse tangent of this complex number
+     * @since 1.2
+     */
+    public Complex atan() {
+        if (isNaN()) {
+            return Complex.NaN;
+        }
+
+        return this.add(Complex.I).divide(Complex.I.subtract(this)).log()
+            .multiply(Complex.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.
+     * <p>
+     * Implements the formula: <pre>
+     * <code> cos(a + bi) = cos(a)cosh(b) - sin(a)sinh(b)i</code></pre>
+     * where the (real) functions on the right-hand side are
+     * {@link java.lang.Math#sin}, {@link java.lang.Math#cos},
+     * {@link MathUtils#cosh} and {@link MathUtils#sinh}.</p>
+     * <p>
+     * Returns {@link Complex#NaN} if either real or imaginary part of the
+     * input argument is <code>NaN</code>.</p>
+     * <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 &#x2213; INFINITY i
+     * cos(&plusmn;INFINITY + i) = NaN + NaN i
+     * cos(&plusmn;INFINITY &plusmn; INFINITY i) = NaN + NaN i</code></pre></p>
+     *
+     * @return the cosine of this complex number
+     * @since 1.2
+     */
+    public Complex cos() {
+        if (isNaN()) {
+            return Complex.NaN;
+        }
+
+        return createComplex(FastMath.cos(real) * MathUtils.cosh(imaginary),
+            -FastMath.sin(real) * MathUtils.sinh(imaginary));
+    }
+
+    /**
+     * Compute the
+     * <a href="http://mathworld.wolfram.com/HyperbolicCosine.html" TARGET="_top">
+     * hyperbolic cosine</a> of this complex number.
+     * <p>
+     * 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 java.lang.Math#sin}, {@link java.lang.Math#cos},
+     * {@link MathUtils#cosh} and {@link MathUtils#sinh}.</p>
+     * <p>
+     * Returns {@link Complex#NaN} if either real or imaginary part of the
+     * input argument is <code>NaN</code>.</p>
+     * <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>
+     * 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></p>
+     *
+     * @return the hyperbolic cosine of this complex number.
+     * @since 1.2
+     */
+    public Complex cosh() {
+        if (isNaN()) {
+            return Complex.NaN;
+        }
+
+        return createComplex(MathUtils.cosh(real) * FastMath.cos(imaginary),
+            MathUtils.sinh(real) * FastMath.sin(imaginary));
+    }
+
+    /**
+     * Compute the
+     * <a href="http://mathworld.wolfram.com/ExponentialFunction.html" TARGET="_top">
+     * exponential function</a> of this complex number.
+     * <p>
+     * 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 java.lang.Math#exp}, {@link java.lang.Math#cos}, and
+     * {@link java.lang.Math#sin}.</p>
+     * <p>
+     * Returns {@link Complex#NaN} if either real or imaginary part of the
+     * input argument is <code>NaN</code>.</p>
+     * <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>
+     * 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></p>
+     *
+     * @return <i>e</i><sup><code>this</code></sup>
+     * @since 1.2
+     */
+    public Complex exp() {
+        if (isNaN()) {
+            return Complex.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.
+     * <p>
+     * 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 java.lang.Math#log},
+     * <code>|a + bi|</code> is the modulus, {@link Complex#abs},  and
+     * <code>arg(a + bi) = {@link java.lang.Math#atan2}(b, a)</code></p>
+     * <p>
+     * Returns {@link Complex#NaN} if either real or imaginary part of the
+     * input argument is <code>NaN</code>.</p>
+     * <p>
+     * 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></p>
+     *
+     * @return ln of this complex number.
+     * @since 1.2
+     */
+    public Complex log() {
+        if (isNaN()) {
+            return Complex.NaN;
+        }
+
+        return createComplex(FastMath.log(abs()),
+            FastMath.atan2(imaginary, real));
+    }
+
+    /**
+     * Returns of value of this complex number raised to the power of <code>x</code>.
+     * <p>
+     * Implements the formula: <pre>
+     * <code> y<sup>x</sup> = exp(x&middot;log(y))</code></pre>
+     * where <code>exp</code> and <code>log</code> are {@link #exp} and
+     * {@link #log}, respectively.</p>
+     * <p>
+     * Returns {@link Complex#NaN} if either real or imaginary part of the
+     * input argument is <code>NaN</code> or infinite, or if <code>y</code>
+     * equals {@link Complex#ZERO}.</p>
+     *
+     * @param x the exponent.
+     * @return <code>this</code><sup><code>x</code></sup>
+     * @throws NullPointerException if x is null
+     * @since 1.2
+     */
+    public Complex pow(Complex x) {
+        if (x == null) {
+            throw new NullPointerException();
+        }
+        return this.log().multiply(x).exp();
+    }
+
+    /**
+     * Compute the
+     * <a href="http://mathworld.wolfram.com/Sine.html" TARGET="_top">
+     * sine</a>
+     * of this complex number.
+     * <p>
+     * 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 java.lang.Math#sin}, {@link java.lang.Math#cos},
+     * {@link MathUtils#cosh} and {@link MathUtils#sinh}.</p>
+     * <p>
+     * Returns {@link Complex#NaN} if either real or imaginary part of the
+     * input argument is <code>NaN</code>.</p>
+     * <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>
+     * 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></p>
+     *
+     * @return the sine of this complex number.
+     * @since 1.2
+     */
+    public Complex sin() {
+        if (isNaN()) {
+            return Complex.NaN;
+        }
+
+        return createComplex(FastMath.sin(real) * MathUtils.cosh(imaginary),
+            FastMath.cos(real) * MathUtils.sinh(imaginary));
+    }
+
+    /**
+     * Compute the
+     * <a href="http://mathworld.wolfram.com/HyperbolicSine.html" TARGET="_top">
+     * hyperbolic sine</a> of this complex number.
+     * <p>
+     * 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 java.lang.Math#sin}, {@link java.lang.Math#cos},
+     * {@link MathUtils#cosh} and {@link MathUtils#sinh}.</p>
+     * <p>
+     * Returns {@link Complex#NaN} if either real or imaginary part of the
+     * input argument is <code>NaN</code>.</p>
+     * <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></p>
+     *
+     * @return the hyperbolic sine of this complex number
+     * @since 1.2
+     */
+    public Complex sinh() {
+        if (isNaN()) {
+            return Complex.NaN;
+        }
+
+        return createComplex(MathUtils.sinh(real) * FastMath.cos(imaginary),
+            MathUtils.cosh(real) * FastMath.sin(imaginary));
+    }
+
+    /**
+     * Compute the
+     * <a href="http://mathworld.wolfram.com/SquareRoot.html" TARGET="_top">
+     * square root</a> of this complex number.
+     * <p>
+     * Implements the following algorithm to compute <code>sqrt(a + bi)</code>:
+     * <ol><li>Let <code>t = sqrt((|a| + |a + bi|) / 2)</code></li>
+     * <li><pre>if <code> a &#8805; 0</code> return <code>t + (b/2t)i</code>
+     *  else return <code>|b|/2t + sign(b)t i </code></pre></li>
+     * </ol>
+     * where <ul>
+     * <li><code>|a| = {@link Math#abs}(a)</code></li>
+     * <li><code>|a + bi| = {@link Complex#abs}(a + bi) </code></li>
+     * <li><code>sign(b) =  {@link MathUtils#indicator}(b) </code>
+     * </ul></p>
+     * <p>
+     * Returns {@link Complex#NaN} if either real or imaginary part of the
+     * input argument is <code>NaN</code>.</p>
+     * <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>
+     * 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></p>
+     *
+     * @return the square root of this complex number
+     * @since 1.2
+     */
+    public Complex sqrt() {
+        if (isNaN()) {
+            return Complex.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),
+                MathUtils.indicator(imaginary) * t);
+        }
+    }
+
+    /**
+     * Compute the
+     * <a href="http://mathworld.wolfram.com/SquareRoot.html" TARGET="_top">
+     * square root</a> of 1 - <code>this</code><sup>2</sup> for this complex
+     * number.
+     * <p>
+     * Computes the result directly as
+     * <code>sqrt(Complex.ONE.subtract(z.multiply(z)))</code>.</p>
+     * <p>
+     * Returns {@link Complex#NaN} if either real or imaginary part of the
+     * input argument is <code>NaN</code>.</p>
+     * <p>
+     * Infinite values in real or imaginary parts of the input may result in
+     * infinite or NaN values returned in parts of the result.</p>
+     *
+     * @return the square root of 1 - <code>this</code><sup>2</sup>
+     * @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.
+     * <p>
+     * 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 java.lang.Math#sin}, {@link java.lang.Math#cos},
+     * {@link MathUtils#cosh} and {@link MathUtils#sinh}.</p>
+     * <p>
+     * Returns {@link Complex#NaN} if either real or imaginary part of the
+     * input argument is <code>NaN</code>.</p>
+     * <p>
+     * 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(1 &plusmn; INFINITY i) = 0 + NaN i
+     * tan(&plusmn;INFINITY + i) = NaN + NaN i
+     * tan(&plusmn;INFINITY &plusmn; INFINITY i) = NaN + NaN i
+     * tan(&plusmn;&pi;/2 + 0 i) = &plusmn;INFINITY + NaN i</code></pre></p>
+     *
+     * @return the tangent of this complex number
+     * @since 1.2
+     */
+    public Complex tan() {
+        if (isNaN()) {
+            return Complex.NaN;
+        }
+
+        double real2 = 2.0 * real;
+        double imaginary2 = 2.0 * imaginary;
+        double d = FastMath.cos(real2) + MathUtils.cosh(imaginary2);
+
+        return createComplex(FastMath.sin(real2) / d, MathUtils.sinh(imaginary2) / d);
+    }
+
+    /**
+     * Compute the
+     * <a href="http://mathworld.wolfram.com/HyperbolicTangent.html" TARGET="_top">
+     * hyperbolic tangent</a> of this complex number.
+     * <p>
+     * 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 java.lang.Math#sin}, {@link java.lang.Math#cos},
+     * {@link MathUtils#cosh} and {@link MathUtils#sinh}.</p>
+     * <p>
+     * Returns {@link Complex#NaN} if either real or imaginary part of the
+     * input argument is <code>NaN</code>.</p>
+     * <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>
+     * tanh(1 &plusmn; INFINITY i) = NaN + NaN i
+     * tanh(&plusmn;INFINITY + i) = NaN + 0 i
+     * tanh(&plusmn;INFINITY &plusmn; INFINITY i) = NaN + NaN i
+     * tanh(0 + (&pi;/2)i) = NaN + INFINITY i</code></pre></p>
+     *
+     * @return the hyperbolic tangent of this complex number
+     * @since 1.2
+     */
+    public Complex tanh() {
+        if (isNaN()) {
+            return Complex.NaN;
+        }
+
+        double real2 = 2.0 * real;
+        double imaginary2 = 2.0 * imaginary;
+        double d = MathUtils.cosh(real2) + FastMath.cos(imaginary2);
+
+        return createComplex(MathUtils.sinh(real2) / d, FastMath.sin(imaginary2) / d);
+    }
+
+
+
+    /**
+     * <p>Compute the argument of this complex number.
+     * </p>
+     * <p>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>
+     * <p>If either real or imaginary part (or both) is NaN, NaN is returned.  Infinite parts are handled
+     * as java.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 java.Math.atan2 for full details.</p>
+     *
+     * @return the argument of this complex number
+     */
+    public double getArgument() {
+        return FastMath.atan2(getImaginary(), getReal());
+    }
+
+    /**
+     * <p>Computes the n-th roots of this complex number.
+     * </p>
+     * <p>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</code></i>, where <code>abs</code> and <code>phi</code> are
+     * respectively the {@link #abs() modulus} and {@link #getArgument() argument} of this complex number.
+     * </p>
+     * <p>If one or both parts of this complex number is NaN, a list with just one element,
+     *  {@link #NaN} is returned.</p>
+     * <p>if neither part is NaN, but at least one part is infinite, the result is a one-element
+     * list containing {@link #INF}.</p>
+     *
+     * @param n degree of root
+     * @return List<Complex> all nth roots of this complex number
+     * @throws IllegalArgumentException if parameter n is less than or equal to 0
+     * @since 2.0
+     */
+    public List<Complex> nthRoot(int n) throws IllegalArgumentException {
+
+        if (n <= 0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.CANNOT_COMPUTE_NTH_ROOT_FOR_NEGATIVE_N,
+                    n);
+        }
+
+        List<Complex> result = new ArrayList<Complex>();
+
+        if (isNaN()) {
+            result.add(Complex.NaN);
+            return result;
+        }
+
+        if (isInfinite()) {
+            result.add(Complex.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 the real part
+     * @param imaginaryPart the imaginary part
+     * @return a new complex number instance
+     * @since 1.2
+     */
+    protected Complex createComplex(double realPart, double imaginaryPart) {
+        return new Complex(realPart, imaginaryPart);
+    }
+
+    /**
+     * <p>Resolve the transient fields in a deserialized Complex Object.</p>
+     * <p>Subclasses will need to override {@link #createComplex} to deserialize properly</p>
+     * @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();
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/complex/ComplexField.java b/src/main/java/org/apache/commons/math/complex/ComplexField.java
new file mode 100644
index 0000000..5bce1d5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/complex/ComplexField.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.math.complex;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.Field;
+
+/**
+ * Representation of the complex numbers field.
+ * <p>
+ * This class is a singleton.
+ * </p>
+ * @see Complex
+ * @version $Revision: 811827 $ $Date: 2009-09-06 17:32:50 +0200 (dim. 06 sept. 2009) $
+ * @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;
+    }
+
+    // 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 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/math/complex/ComplexFormat.java b/src/main/java/org/apache/commons/math/complex/ComplexFormat.java
new file mode 100644
index 0000000..288d6de
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/complex/ComplexFormat.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.math.complex;
+
+import java.text.FieldPosition;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.util.Locale;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.util.CompositeFormat;
+import org.apache.commons.math.exception.NullArgumentException;
+
+/**
+ * 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.
+ *
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ */
+public class ComplexFormat extends CompositeFormat {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -3343698360149467646L;
+
+     /** 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 String imaginaryCharacter;
+
+    /** The format used for the imaginary part. */
+    private NumberFormat imaginaryFormat;
+
+    /** The format used for the real part. */
+    private 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(DEFAULT_IMAGINARY_CHARACTER, getDefaultNumberFormat());
+    }
+
+    /**
+     * 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.
+     */
+    public ComplexFormat(NumberFormat format) {
+        this(DEFAULT_IMAGINARY_CHARACTER, 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.
+     */
+    public ComplexFormat(NumberFormat realFormat, NumberFormat imaginaryFormat) {
+        this(DEFAULT_IMAGINARY_CHARACTER, realFormat, imaginaryFormat);
+    }
+
+    /**
+     * 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.
+     */
+    public ComplexFormat(String imaginaryCharacter) {
+        this(imaginaryCharacter, 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.
+     */
+    public ComplexFormat(String imaginaryCharacter, NumberFormat format) {
+        this(imaginaryCharacter, format, (NumberFormat)format.clone());
+    }
+
+    /**
+     * 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.
+     */
+    public ComplexFormat(String imaginaryCharacter, NumberFormat realFormat,
+            NumberFormat imaginaryFormat) {
+        super();
+        setImaginaryCharacter(imaginaryCharacter);
+        setImaginaryFormat(imaginaryFormat);
+        setRealFormat(realFormat);
+    }
+
+    /**
+     * Get the set of locales for which complex formats are available.
+     * <p>This is the same set as the {@link NumberFormat} set.</p>
+     * @return available complex format locales.
+     */
+    public static Locale[] getAvailableLocales() {
+        return NumberFormat.getAvailableLocales();
+    }
+
+    /**
+     * This static method calls {@link #format(Object)} on a default instance of
+     * ComplexFormat.
+     *
+     * @param c Complex object to format
+     * @return A formatted number in the form "Re(c) + Im(c)i"
+     */
+    public static String formatComplex(Complex c) {
+        return getInstance().format(c);
+    }
+
+    /**
+     * 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();
+        formatDouble(re, getRealFormat(), toAppendTo, pos);
+
+        // format sign and imaginary
+        double im = complex.getImaginary();
+        if (im < 0.0) {
+            toAppendTo.append(" - ");
+            formatDouble(-im, getImaginaryFormat(), toAppendTo, pos);
+            toAppendTo.append(getImaginaryCharacter());
+        } else if (im > 0.0 || Double.isNaN(im)) {
+            toAppendTo.append(" + ");
+            formatDouble(im, getImaginaryFormat(), toAppendTo, pos);
+            toAppendTo.append(getImaginaryCharacter());
+        }
+
+        return toAppendTo;
+    }
+
+    /**
+     * Formats a object to produce a string.  <code>obj</code> 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 IllegalArgumentException is <code>obj</code> is not a valid type.
+     */
+    @Override
+    public StringBuffer format(Object obj, StringBuffer toAppendTo,
+            FieldPosition pos) {
+
+        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 MathRuntimeException.createIllegalArgumentException(
+                  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 = getDefaultNumberFormat(locale);
+        return new ComplexFormat(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.
+     * @exception ParseException if the beginning of the specified string
+     *            cannot be parsed.
+     */
+    public Complex parse(String source) throws ParseException {
+        ParsePosition parsePosition = new ParsePosition(0);
+        Complex result = parse(source, parsePosition);
+        if (parsePosition.getIndex() == 0) {
+            throw MathRuntimeException.createParseException(
+                    parsePosition.getErrorIndex(),
+                    LocalizedFormats.UNPARSEABLE_COMPLEX_NUMBER, source);
+        }
+        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
+        parseAndIgnoreWhitespace(source, pos);
+
+        // parse real
+        Number re = 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 = 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
+        parseAndIgnoreWhitespace(source, pos);
+
+        // parse imaginary
+        Number im = 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 (!parseFixedstring(source, getImaginaryCharacter(), pos)) {
+            return null;
+        }
+
+        return new Complex(re.doubleValue(), im.doubleValue() * sign);
+
+    }
+
+    /**
+     * Parses a string to produce a object.
+     *
+     * @param source the string to parse
+     * @param pos input/ouput parsing parameter.
+     * @return the parsed object.
+     * @see java.text.Format#parseObject(java.lang.String, java.text.ParsePosition)
+     */
+    @Override
+    public Object parseObject(String source, ParsePosition pos) {
+        return parse(source, pos);
+    }
+
+    /**
+     * Modify the imaginaryCharacter.
+     * @param imaginaryCharacter The new imaginaryCharacter value.
+     * @throws IllegalArgumentException if <code>imaginaryCharacter</code> is
+     *         <code>null</code> or an empty string.
+     */
+    public void setImaginaryCharacter(String imaginaryCharacter) {
+        if (imaginaryCharacter == null || imaginaryCharacter.length() == 0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.EMPTY_STRING_FOR_IMAGINARY_CHARACTER);
+        }
+        this.imaginaryCharacter = imaginaryCharacter;
+    }
+
+    /**
+     * Modify the imaginaryFormat.
+     * @param imaginaryFormat The new imaginaryFormat value.
+     * @throws NullArgumentException if {@code imaginaryFormat} is {@code null}.
+     */
+    public void setImaginaryFormat(NumberFormat imaginaryFormat) {
+        if (imaginaryFormat == null) {
+            throw new NullArgumentException(LocalizedFormats.IMAGINARY_FORMAT);
+        }
+        this.imaginaryFormat = imaginaryFormat;
+    }
+
+    /**
+     * Modify the realFormat.
+     * @param realFormat The new realFormat value.
+     * @throws NullArgumentException if {@code realFormat} is {@code null}.
+     */
+    public void setRealFormat(NumberFormat realFormat) {
+        if (realFormat == null) {
+            throw new NullArgumentException(LocalizedFormats.REAL_FORMAT);
+        }
+        this.realFormat = realFormat;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/complex/ComplexUtils.java b/src/main/java/org/apache/commons/math/complex/ComplexUtils.java
new file mode 100644
index 0000000..f7fb392
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/complex/ComplexUtils.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.math.complex;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Static implementations of common
+ * {@link org.apache.commons.math.complex.Complex} utilities functions.
+ *
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ */
+public class ComplexUtils {
+
+    /**
+     * Default constructor.
+     */
+    private ComplexUtils() {
+        super();
+    }
+
+    /**
+     * 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>
+     * <p>
+     * If either <code>r</code> or <code>theta</code> is NaN, or
+     * <code>theta</code> is infinite, {@link Complex#NaN} is returned.</p>
+     * <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></p>
+     *
+     * @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 IllegalArgumentException  if r is negative
+     * @since 1.1
+     */
+    public static Complex polar2Complex(double r, double theta) {
+        if (r < 0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.NEGATIVE_COMPLEX_MODULE, r);
+        }
+        return new Complex(r * FastMath.cos(theta), r * FastMath.sin(theta));
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/complex/package.html b/src/main/java/org/apache/commons/math/complex/package.html
new file mode 100644
index 0000000..755dba0
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/complex/package.html
@@ -0,0 +1,23 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $ -->
+    <body>
+     Complex number type and implementations of complex transcendental
+     functions.
+    </body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/dfp/Dfp.java b/src/main/java/org/apache/commons/math/dfp/Dfp.java
new file mode 100644
index 0000000..7ce338c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/dfp/Dfp.java
@@ -0,0 +1,2399 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.dfp;
+
+import java.util.Arrays;
+
+import org.apache.commons.math.FieldElement;
+
+/**
+ *  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>
+ *
+ *  <p>The design goals here are:
+ *  <ol>
+ *    <li>Decimal math, or close to it</li>
+ *    <li>Settable precision (but no mix between numbers using different settings)</li>
+ *    <li>Portability.  Code should be keep as portable as possible.</li>
+ *    <li>Performance</li>
+ *    <li>Accuracy  - Results should always be +/- 1 ULP for basic
+ *         algebraic operation</li>
+ *    <li>Comply with IEEE 854-1987 as much as possible.
+ *         (See IEEE 854-1987 notes below)</li>
+ *  </ol></p>
+ *
+ *  <p>Trade offs:
+ *  <ol>
+ *    <li>Memory foot print.  I'm using more memory than necessary to
+ *         represent numbers to get better performance.</li>
+ *    <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.</li>
+ *  </ol></p>
+ *
+ *  <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>
+ *
+ *  <p>IEEE 854-1987  Notes and differences</p>
+ *
+ *  <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>
+ *
+ *  <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>
+ *
+ *  <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>
+ *
+ *  <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>
+ *
+ *  <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.</p>
+ * @see DfpField
+ * @version $Revision: 1003889 $ $Date: 2010-10-02 23:11:55 +0200 (sam. 02 oct. 2010) $
+ * @since 2.2
+ */
+public class Dfp implements FieldElement<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: & 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) {
+                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;
+
+        int trailingZeros = 0;
+        while (p > q) {
+            if (striped[p] != '0') {
+                break;
+            }
+            trailingZeros++;
+            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.math.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.
+     * </p>
+     * @return {@link org.apache.commons.math.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 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 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 + (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
+     */
+    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
+     */
+    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
+     */
+    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
+     */
+    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
+     */
+    public int log10()  {
+        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 = 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 = 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 0&lt;=x&lt;radix.
+     * There are speed advantages in this special case
+     * @param x multiplicand
+     * @return product of this and x
+     */
+    public Dfp multiply(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 = 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;
+
+    }
+
+    /** Compute the square root.
+     * @return square root of the instance
+     */
+    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 = ae % p;
+            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 = result.exp + ERR_SCALE;
+                break;
+
+            case DfpField.FLAG_OVERFLOW:
+                result.exp = 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;
+        if (lessThan(getZero())) {
+            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.log10() * 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;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/dfp/DfpDec.java b/src/main/java/org/apache/commons/math/dfp/DfpDec.java
new file mode 100644
index 0000000..d2c3b56
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/dfp/DfpDec.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.math.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.
+ * @version $Revision: 1003892 $ $Date: 2010-10-02 23:28:56 +0200 (sam. 02 oct. 2010) $
+ * @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(log10() - 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(log10());
+            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/math/dfp/DfpField.java b/src/main/java/org/apache/commons/math/dfp/DfpField.java
new file mode 100644
index 0000000..65a25f4
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/dfp/DfpField.java
@@ -0,0 +1,750 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.dfp;
+
+import org.apache.commons.math.Field;
+
+/** Field for Decimal floating point instances.
+ * @version $Revision: 995987 $ $Date: 2010-09-10 23:24:15 +0200 (ven. 10 sept. 2010) $
+ * @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;
+
+    /** 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.
+     * </p>
+     * @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.
+     * </p>
+     * @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)}
+     * </p>
+     * @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;
+    }
+
+    /** 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).
+     *
+     *  Let f(x) = ln(x),
+     *
+     *  We know that f'(x) = 1/x, thus from Taylor's theorem we have:
+     *
+     *           -----          n+1         n
+     *  f(x) =   \           (-1)    (x - 1)
+     *           /          ----------------    for 1 <= n <= infinity
+     *           -----             n
+     *
+     *  or
+     *                       2        3       4
+     *                   (x-1)   (x-1)    (x-1)
+     *  ln(x) =  (x-1) - ----- + ------ - ------ + ...
+     *                     2       3        4
+     *
+     *  alternatively,
+     *
+     *                  2    3   4
+     *                 x    x   x
+     *  ln(x+1) =  x - -  + - - - + ...
+     *                 2    3   4
+     *
+     *  This series can be used to compute ln(x), but it converges too slowly.
+     *
+     *  If we substitute -x for x above, we get
+     *
+     *                   2    3    4
+     *                  x    x    x
+     *  ln(1-x) =  -x - -  - -  - - + ...
+     *                  2    3    4
+     *
+     *  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
+     *
+     *                             3     5      7
+     *                           2x    2x     2x
+     *  ln(x+1) - ln(x-1) = 2x + --- + --- + ---- + ...
+     *                            3     5      7
+     *
+     *  By the property of logarithms that ln(a) - ln(b) = ln (a/b) we have:
+     *
+     *                                3        5        7
+     *      x+1           /          x        x        x          \
+     *  ln ----- =   2 *  |  x  +   ----  +  ----  +  ---- + ...  |
+     *      x-1           \          3        5        7          /
+     *
+     *  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 = 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/math/dfp/DfpMath.java b/src/main/java/org/apache/commons/math/dfp/DfpMath.java
new file mode 100644
index 0000000..56af1d2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/dfp/DfpMath.java
@@ -0,0 +1,969 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.dfp;
+
+/** Mathematical routines for use with {@link Dfp}.
+ * The constants are defined in {@link DfpField}
+ * @version $Revision: 1042376 $ $Date: 2010-12-05 16:54:55 +0100 (dim. 05 déc. 2010) $
+ * @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 = 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 = trial * 2;
+            } while (a>trial);
+
+            r = prevr;
+            trial = prevtrial;
+
+            a = 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),
+     *
+     *  We know that f'(x) = 1/x, thus from Taylor's theorum we have:
+     *
+     *           -----          n+1         n
+     *  f(x) =   \           (-1)    (x - 1)
+     *           /          ----------------    for 1 <= n <= infinity
+     *           -----             n
+     *
+     *  or
+     *                       2        3       4
+     *                   (x-1)   (x-1)    (x-1)
+     *  ln(x) =  (x-1) - ----- + ------ - ------ + ...
+     *                     2       3        4
+     *
+     *  alternatively,
+     *
+     *                  2    3   4
+     *                 x    x   x
+     *  ln(x+1) =  x - -  + - - - + ...
+     *                 2    3   4
+     *
+     *  This series can be used to compute ln(x), but it converges too slowly.
+     *
+     *  If we substitute -x for x above, we get
+     *
+     *                   2    3    4
+     *                  x    x    x
+     *  ln(1-x) =  -x - -  - -  - - + ...
+     *                  2    3    4
+     *
+     *  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
+     *
+     *                             3     5      7
+     *                           2x    2x     2x
+     *  ln(x+1) - ln(x-1) = 2x + --- + --- + ---- + ...
+     *                            3     5      7
+     *
+     *  By the property of logarithms that ln(a) - ln(b) = ln (a/b) we have:
+     *
+     *                                3        5        7
+     *      x+1           /          x        x        x          \
+     *  ln ----- =   2 *  |  x  +   ----  +  ----  +  ---- + ...  |
+     *      x-1           \          3        5        7          /
+     *
+     *  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 = 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) {
+            // if y is odd integer
+            if (y.rint().equals(y) && !y.remainder(two).equals(zero)) {
+                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))) {
+            Dfp c[] = new Dfp[2];
+            c[0] = x;
+            c[1] = zero;
+
+            //y = sinInternal(c);
+            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
+     *
+     *  Uses the typical taylor series
+     *
+     *  but may reduce arguments using the following identity
+     * tan(x+y) = (tan(x) + tan(y)) / (1 - tan(x)*tan(y))
+     *
+     * since tan(PI/8) = sqrt(2)-1,
+     *
+     * 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/math/dfp/package.html b/src/main/java/org/apache/commons/math/dfp/package.html
new file mode 100644
index 0000000..f63dd6e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/dfp/package.html
@@ -0,0 +1,88 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 992696 $ $Date: 2010-09-05 00:57:31 +0200 (dim. 05 sept. 2010) $ -->
+    <body>
+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>
+
+<p>The design goals here are:
+<ol>
+  <li>Decimal math, or close to it</li>
+  <li>Settable precision (but no mix between numbers using different settings)</li>
+  <li>Portability.  Code should be keep as portable as possible.</li>
+  <li>Performance</li>
+  <li>Accuracy  - Results should always be +/- 1 ULP for basic
+       algebraic operation</li>
+  <li>Comply with IEEE 854-1987 as much as possible.
+       (See IEEE 854-1987 notes below)</li>
+</ol></p>
+
+<p>Trade offs:
+<ol>
+  <li>Memory foot print.  I'm using more memory than necessary to
+       represent numbers to get better performance.</li>
+  <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.</li>
+</ol></p>
+
+<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>
+
+<p>IEEE 854-1987  Notes and differences</p>
+
+<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>
+
+<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>
+
+<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>
+
+<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>
+
+<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.</p>
+    </body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/distribution/AbstractContinuousDistribution.java b/src/main/java/org/apache/commons/math/distribution/AbstractContinuousDistribution.java
new file mode 100644
index 0000000..aaa2efd
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/AbstractContinuousDistribution.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.math.distribution;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.ConvergenceException;
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.analysis.solvers.BrentSolver;
+import org.apache.commons.math.analysis.solvers.UnivariateRealSolverUtils;
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.random.RandomDataImpl;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Base class for continuous distributions.  Default implementations are
+ * provided for some of the methods that do not vary from distribution to
+ * distribution.
+ *
+ * @version $Revision: 1073498 $ $Date: 2011-02-22 21:57:26 +0100 (mar. 22 févr. 2011) $
+ */
+public abstract class AbstractContinuousDistribution
+    extends AbstractDistribution
+    implements ContinuousDistribution, Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -38038050983108802L;
+
+    /**
+     * RandomData instance used to generate samples from the distribution
+     * @since 2.2
+     */
+    protected final RandomDataImpl randomData = new RandomDataImpl();
+
+    /**
+     * Solver absolute accuracy for inverse cumulative computation
+     * @since 2.1
+     */
+    private double solverAbsoluteAccuracy = BrentSolver.DEFAULT_ABSOLUTE_ACCURACY;
+
+    /**
+     * Default constructor.
+     */
+    protected AbstractContinuousDistribution() {
+        super();
+    }
+
+    /**
+     * Return the probability density for a particular point.
+     * @param x  The point at which the density should be computed.
+     * @return  The pdf at point x.
+     * @throws MathRuntimeException if the specialized class hasn't implemented this function
+     * @since 2.1
+     */
+    public double density(double x) throws MathRuntimeException {
+        throw new MathRuntimeException(new UnsupportedOperationException(),
+                LocalizedFormats.NO_DENSITY_FOR_THIS_DISTRIBUTION);
+    }
+
+    /**
+     * For this distribution, X, this method returns the critical point x, such
+     * that P(X &lt; x) = <code>p</code>.
+     *
+     * @param p the desired probability
+     * @return x, such that P(X &lt; x) = <code>p</code>
+     * @throws MathException if the inverse cumulative probability can not be
+     *         computed due to convergence or other numerical errors.
+     * @throws IllegalArgumentException if <code>p</code> is not a valid
+     *         probability.
+     */
+    public double inverseCumulativeProbability(final double p)
+        throws MathException {
+        if (p < 0.0 || p > 1.0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.OUT_OF_RANGE_SIMPLE, p, 0.0, 1.0);
+        }
+
+        // by default, do simple root finding using bracketing and default solver.
+        // subclasses can override if there is a better method.
+        UnivariateRealFunction rootFindingFunction =
+            new UnivariateRealFunction() {
+            public double value(double x) throws FunctionEvaluationException {
+                double ret = Double.NaN;
+                try {
+                    ret = cumulativeProbability(x) - p;
+                } catch (MathException ex) {
+                    throw new FunctionEvaluationException(x, ex.getSpecificPattern(), ex.getGeneralPattern(), ex.getArguments());
+                }
+                if (Double.isNaN(ret)) {
+                    throw new FunctionEvaluationException(x, LocalizedFormats.CUMULATIVE_PROBABILITY_RETURNED_NAN, x, p);
+                }
+                return ret;
+            }
+        };
+
+        // Try to bracket root, test domain endpoints if this fails
+        double lowerBound = getDomainLowerBound(p);
+        double upperBound = getDomainUpperBound(p);
+        double[] bracket = null;
+        try {
+            bracket = UnivariateRealSolverUtils.bracket(
+                    rootFindingFunction, getInitialDomain(p),
+                    lowerBound, upperBound);
+        }  catch (ConvergenceException ex) {
+            /*
+             * Check domain endpoints to see if one gives value that is within
+             * the default solver's defaultAbsoluteAccuracy of 0 (will be the
+             * case if density has bounded support and p is 0 or 1).
+             */
+            if (FastMath.abs(rootFindingFunction.value(lowerBound)) < getSolverAbsoluteAccuracy()) {
+                return lowerBound;
+            }
+            if (FastMath.abs(rootFindingFunction.value(upperBound)) < getSolverAbsoluteAccuracy()) {
+                return upperBound;
+            }
+            // Failed bracket convergence was not because of corner solution
+            throw new MathException(ex);
+        }
+
+        // find root
+        double root = UnivariateRealSolverUtils.solve(rootFindingFunction,
+                // override getSolverAbsoluteAccuracy() to use a Brent solver with
+                // absolute accuracy different from BrentSolver default
+                bracket[0],bracket[1], getSolverAbsoluteAccuracy());
+        return root;
+    }
+
+    /**
+     * Reseeds the random generator used to generate samples.
+     *
+     * @param seed the new seed
+     * @since 2.2
+     */
+    public void reseedRandomGenerator(long seed) {
+        randomData.reSeed(seed);
+    }
+
+    /**
+     * Generates a random value sampled from this distribution. The default
+     * implementation uses the
+     * <a href="http://en.wikipedia.org/wiki/Inverse_transform_sampling"> inversion method.</a>
+     *
+     * @return random value
+     * @since 2.2
+     * @throws MathException if an error occurs generating the random value
+     */
+    public double sample() throws MathException {
+        return randomData.nextInversionDeviate(this);
+    }
+
+    /**
+     * Generates a random sample from the distribution.  The default implementation
+     * generates the sample by calling {@link #sample()} in a loop.
+     *
+     * @param sampleSize number of random values to generate
+     * @since 2.2
+     * @return an array representing the random sample
+     * @throws MathException if an error occurs generating the sample
+     * @throws IllegalArgumentException if sampleSize is not positive
+     */
+    public double[] sample(int sampleSize) throws MathException {
+        if (sampleSize <= 0) {
+            MathRuntimeException.createIllegalArgumentException(LocalizedFormats.NOT_POSITIVE_SAMPLE_SIZE, sampleSize);
+        }
+        double[] out = new double[sampleSize];
+        for (int i = 0; i < sampleSize; i++) {
+            out[i] = sample();
+        }
+        return out;
+    }
+
+    /**
+     * Access the initial domain value, based on <code>p</code>, used to
+     * bracket a CDF root.  This method is used by
+     * {@link #inverseCumulativeProbability(double)} to find critical values.
+     *
+     * @param p the desired probability for the critical value
+     * @return initial domain value
+     */
+    protected abstract double getInitialDomain(double p);
+
+    /**
+     * Access the domain value lower bound, based on <code>p</code>, used to
+     * bracket a CDF root.  This method is used by
+     * {@link #inverseCumulativeProbability(double)} to find critical values.
+     *
+     * @param p the desired probability for the critical value
+     * @return domain value lower bound, i.e.
+     *         P(X &lt; <i>lower bound</i>) &lt; <code>p</code>
+     */
+    protected abstract double getDomainLowerBound(double p);
+
+    /**
+     * Access the domain value upper bound, based on <code>p</code>, used to
+     * bracket a CDF root.  This method is used by
+     * {@link #inverseCumulativeProbability(double)} to find critical values.
+     *
+     * @param p the desired probability for the critical value
+     * @return domain value upper bound, i.e.
+     *         P(X &lt; <i>upper bound</i>) &gt; <code>p</code>
+     */
+    protected abstract double getDomainUpperBound(double p);
+
+    /**
+     * Returns the solver absolute accuracy for inverse cumulative computation.
+     *
+     * @return the maximum absolute error in inverse cumulative probability estimates
+     * @since 2.1
+     */
+    protected double getSolverAbsoluteAccuracy() {
+        return solverAbsoluteAccuracy;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/distribution/AbstractDistribution.java b/src/main/java/org/apache/commons/math/distribution/AbstractDistribution.java
new file mode 100644
index 0000000..c32ba29
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/AbstractDistribution.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.math.distribution;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * Base class for probability distributions.
+ *
+ * @version $Revision: 1054524 $ $Date: 2011-01-03 05:59:18 +0100 (lun. 03 janv. 2011) $
+ */
+public abstract class AbstractDistribution
+    implements Distribution, Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -38038050983108802L;
+
+    /**
+     * Default constructor.
+     */
+    protected AbstractDistribution() {
+        super();
+    }
+
+    /**
+     * For a random variable X whose values are distributed according
+     * to this distribution, this method returns P(x0 &le; X &le; x1).
+     * <p>
+     * The default implementation uses the identity</p>
+     * <p>
+     * P(x0 &le; X &le; x1) = P(X &le; x1) - P(X &le; x0) </p>
+     *
+     * @param x0 the (inclusive) 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</code> and <code>x1</code>,
+     * including the endpoints.
+     * @throws MathException if the cumulative probability can not be
+     * computed due to convergence or other numerical errors.
+     * @throws IllegalArgumentException if <code>x0 > x1</code>
+     */
+    public double cumulativeProbability(double x0, double x1)
+        throws MathException {
+        if (x0 > x1) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.LOWER_ENDPOINT_ABOVE_UPPER_ENDPOINT,
+                  x0, x1);
+        }
+        return cumulativeProbability(x1) - cumulativeProbability(x0);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/distribution/AbstractIntegerDistribution.java b/src/main/java/org/apache/commons/math/distribution/AbstractIntegerDistribution.java
new file mode 100644
index 0000000..96cfe5d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/AbstractIntegerDistribution.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.math.distribution;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.random.RandomDataImpl;
+import org.apache.commons.math.util.FastMath;
+
+
+/**
+ * Base class for integer-valued discrete distributions.  Default
+ * implementations are provided for some of the methods that do not vary
+ * from distribution to distribution.
+ *
+ * @version $Revision: 1067494 $ $Date: 2011-02-05 20:49:07 +0100 (sam. 05 févr. 2011) $
+ */
+public abstract class AbstractIntegerDistribution extends AbstractDistribution
+    implements IntegerDistribution, Serializable {
+
+   /** Serializable version identifier */
+    private static final long serialVersionUID = -1146319659338487221L;
+
+    /**
+     * RandomData instance used to generate samples from the distribution
+     * @since 2.2
+     */
+    protected final RandomDataImpl randomData = new RandomDataImpl();
+
+    /**
+     * Default constructor.
+     */
+    protected AbstractIntegerDistribution() {
+        super();
+    }
+
+    /**
+     * For a random variable X whose values are distributed according
+     * to this distribution, this method returns P(X &le; x).  In other words,
+     * this method represents the  (cumulative) distribution function, or
+     * CDF, for this distribution.
+     * <p>
+     * If <code>x</code> does not represent an integer value, the CDF is
+     * evaluated at the greatest integer less than x.
+     *
+     * @param x the value at which the distribution function is evaluated.
+     * @return cumulative probability that a random variable with this
+     * distribution takes a value less than or equal to <code>x</code>
+     * @throws MathException if the cumulative probability can not be
+     * computed due to convergence or other numerical errors.
+     */
+    public double cumulativeProbability(double x) throws MathException {
+        return cumulativeProbability((int) FastMath.floor(x));
+    }
+
+    /**
+     * For a random variable X whose values are distributed according
+     * to this distribution, this method returns P(x0 &le; X &le; x1).
+     *
+     * @param x0 the (inclusive) 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</code> and <code>x1</code>,
+     * including the endpoints.
+     * @throws MathException if the cumulative probability can not be
+     * computed due to convergence or other numerical errors.
+     * @throws IllegalArgumentException if <code>x0 > x1</code>
+     */
+    @Override
+    public double cumulativeProbability(double x0, double x1)
+        throws MathException {
+        if (x0 > x1) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.LOWER_ENDPOINT_ABOVE_UPPER_ENDPOINT, x0, x1);
+        }
+        if (FastMath.floor(x0) < x0) {
+            return cumulativeProbability(((int) FastMath.floor(x0)) + 1,
+               (int) FastMath.floor(x1)); // don't want to count mass below x0
+        } else { // x0 is mathematical integer, so use as is
+            return cumulativeProbability((int) FastMath.floor(x0),
+                (int) FastMath.floor(x1));
+        }
+    }
+
+    /**
+     * For a random variable X whose values are distributed according
+     * to this distribution, this method returns P(X &le; x).  In other words,
+     * this method represents the probability distribution function, or PDF,
+     * for this distribution.
+     *
+     * @param x the value at which the PDF is evaluated.
+     * @return PDF for this distribution.
+     * @throws MathException if the cumulative probability can not be
+     *            computed due to convergence or other numerical errors.
+     */
+    public abstract double cumulativeProbability(int x) throws MathException;
+
+    /**
+     * For a random variable X whose values are distributed according
+     * to this distribution, this method returns P(X = x). In other words, this
+     * method represents the probability mass function,  or PMF, for the distribution.
+     * <p>
+     * If <code>x</code> does not represent an integer value, 0 is returned.
+     *
+     * @param x the value at which the probability density function is evaluated
+     * @return the value of the probability density function at x
+     */
+    public double probability(double x) {
+        double fl = FastMath.floor(x);
+        if (fl == x) {
+            return this.probability((int) x);
+        } else {
+            return 0;
+        }
+    }
+
+    /**
+    * For a random variable X whose values are distributed according
+     * to this distribution, this method returns P(x0 &le; X &le; x1).
+     *
+     * @param x0 the inclusive, lower bound
+     * @param x1 the inclusive, upper bound
+     * @return the cumulative probability.
+     * @throws MathException if the cumulative probability can not be
+     *            computed due to convergence or other numerical errors.
+     * @throws IllegalArgumentException if x0 > x1
+     */
+    public double cumulativeProbability(int x0, int x1) throws MathException {
+        if (x0 > x1) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.LOWER_ENDPOINT_ABOVE_UPPER_ENDPOINT, x0, x1);
+        }
+        return cumulativeProbability(x1) - cumulativeProbability(x0 - 1);
+    }
+
+    /**
+     * For a random variable X whose values are distributed according
+     * to this distribution, this method returns the largest x, such
+     * that P(X &le; x) &le; <code>p</code>.
+     *
+     * @param p the desired probability
+     * @return the largest x such that P(X &le; x) <= p
+     * @throws MathException if the inverse cumulative probability can not be
+     *            computed due to convergence or other numerical errors.
+     * @throws IllegalArgumentException if p < 0 or p > 1
+     */
+    public int inverseCumulativeProbability(final double p) throws MathException{
+        if (p < 0.0 || p > 1.0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.OUT_OF_RANGE_SIMPLE, p, 0.0, 1.0);
+        }
+
+        // by default, do simple bisection.
+        // subclasses can override if there is a better method.
+        int x0 = getDomainLowerBound(p);
+        int x1 = getDomainUpperBound(p);
+        double pm;
+        while (x0 < x1) {
+            int xm = x0 + (x1 - x0) / 2;
+            pm = checkedCumulativeProbability(xm);
+            if (pm > p) {
+                // update x1
+                if (xm == x1) {
+                    // this can happen with integer division
+                    // simply decrement x1
+                    --x1;
+                } else {
+                    // update x1 normally
+                    x1 = xm;
+                }
+            } else {
+                // update x0
+                if (xm == x0) {
+                    // this can happen with integer division
+                    // simply increment x0
+                    ++x0;
+                } else {
+                    // update x0 normally
+                    x0 = xm;
+                }
+            }
+        }
+
+        // insure x0 is the correct critical point
+        pm = checkedCumulativeProbability(x0);
+        while (pm > p) {
+            --x0;
+            pm = checkedCumulativeProbability(x0);
+        }
+
+        return x0;
+    }
+
+    /**
+     * Reseeds the random generator used to generate samples.
+     *
+     * @param seed the new seed
+     * @since 2.2
+     */
+    public void reseedRandomGenerator(long seed) {
+        randomData.reSeed(seed);
+    }
+
+    /**
+     * Generates a random value sampled from this distribution. The default
+     * implementation uses the
+     * <a href="http://en.wikipedia.org/wiki/Inverse_transform_sampling"> inversion method.</a>
+     *
+     * @return random value
+     * @since 2.2
+     * @throws MathException if an error occurs generating the random value
+     */
+    public int sample() throws MathException {
+        return randomData.nextInversionDeviate(this);
+    }
+
+    /**
+     * Generates a random sample from the distribution.  The default implementation
+     * generates the sample by calling {@link #sample()} in a loop.
+     *
+     * @param sampleSize number of random values to generate
+     * @since 2.2
+     * @return an array representing the random sample
+     * @throws MathException if an error occurs generating the sample
+     * @throws IllegalArgumentException if sampleSize is not positive
+     */
+    public int[] sample(int sampleSize) throws MathException {
+        if (sampleSize <= 0) {
+            MathRuntimeException.createIllegalArgumentException(LocalizedFormats.NOT_POSITIVE_SAMPLE_SIZE, 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 NaN values returned.
+     * Throws MathException if the value is NaN. Rethrows any MathException encountered
+     * evaluating the cumulative probability function. Throws
+     * MathException if the cumulative probability function returns NaN.
+     *
+     * @param argument input value
+     * @return cumulative probability
+     * @throws MathException if the cumulative probability is NaN
+     */
+    private double checkedCumulativeProbability(int argument) throws MathException {
+        double result = Double.NaN;
+            result = cumulativeProbability(argument);
+        if (Double.isNaN(result)) {
+            throw new MathException(LocalizedFormats.DISCRETE_CUMULATIVE_PROBABILITY_RETURNED_NAN, argument);
+        }
+        return result;
+    }
+
+    /**
+     * Access the domain value lower bound, based on <code>p</code>, used to
+     * bracket a PDF root.  This method is used by
+     * {@link #inverseCumulativeProbability(double)} to find critical values.
+     *
+     * @param p the desired probability for the critical value
+     * @return domain value lower bound, i.e.
+     *         P(X &lt; <i>lower bound</i>) &lt; <code>p</code>
+     */
+    protected abstract int getDomainLowerBound(double p);
+
+    /**
+     * Access the domain value upper bound, based on <code>p</code>, used to
+     * bracket a PDF root.  This method is used by
+     * {@link #inverseCumulativeProbability(double)} to find critical values.
+     *
+     * @param p the desired probability for the critical value
+     * @return domain value upper bound, i.e.
+     *         P(X &lt; <i>upper bound</i>) &gt; <code>p</code>
+     */
+    protected abstract int getDomainUpperBound(double p);
+
+    /**
+     * Use this method to get information about whether the lower bound
+     * of the support is inclusive or not. For discrete support,
+     * only true here is meaningful.
+     *
+     * @return true (always but at Integer.MIN_VALUE because of the nature of discrete support)
+     * @since 2.2
+     */
+    public boolean isSupportLowerBoundInclusive() {
+        return true;
+    }
+
+    /**
+     * Use this method to get information about whether the upper bound
+     * of the support is inclusive or not. For discrete support,
+     * only true here is meaningful.
+     *
+     * @return true (always but at Integer.MAX_VALUE because of the nature of discrete support)
+     * @since 2.2
+     */
+    public boolean isSupportUpperBoundInclusive() {
+        return true;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/distribution/BetaDistribution.java b/src/main/java/org/apache/commons/math/distribution/BetaDistribution.java
new file mode 100644
index 0000000..7693b04
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/BetaDistribution.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.math.distribution;
+
+import org.apache.commons.math.MathException;
+
+/**
+ * Computes the cumulative, inverse cumulative and density functions for the beta distribuiton.
+ *
+ * @see <a href="http://en.wikipedia.org/wiki/Beta_distribution">Beta_distribution</a>
+ * @version $Revision: 920852 $ $Date: 2010-03-09 13:53:44 +0100 (mar. 09 mars 2010) $
+ * @since 2.0
+ */
+public interface BetaDistribution extends ContinuousDistribution, HasDensity<Double> {
+    /**
+     * Modify the shape parameter, alpha.
+     * @param alpha the new shape parameter.
+     * @deprecated as of 2.1
+     */
+    @Deprecated
+    void setAlpha(double alpha);
+
+     /**
+      * Access the shape parameter, alpha
+      * @return alpha.
+      */
+     double getAlpha();
+
+     /**
+      * Modify the shape parameter, beta.
+      * @param beta the new scale parameter.
+      * @deprecated as of 2.1
+      */
+     @Deprecated
+     void setBeta(double beta);
+
+     /**
+      * Access the shape parameter, beta
+      * @return beta.
+      */
+     double getBeta();
+
+     /**
+      * Return the probability density for a particular point.
+      * @param x  The point at which the density should be computed.
+      * @return  The pdf at point x.
+      * @exception MathException if probability density cannot be computed
+      */
+     double density(Double x) throws MathException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/distribution/BetaDistributionImpl.java b/src/main/java/org/apache/commons/math/distribution/BetaDistributionImpl.java
new file mode 100644
index 0000000..4d96187
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/BetaDistributionImpl.java
@@ -0,0 +1,284 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.distribution;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.special.Gamma;
+import org.apache.commons.math.special.Beta;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Implements the Beta distribution.
+ * <p>
+ * References:
+ * <ul>
+ * <li><a href="http://en.wikipedia.org/wiki/Beta_distribution">
+ * Beta distribution</a></li>
+ * </ul>
+ * </p>
+ * @version $Revision: 1054524 $ $Date: 2011-01-03 05:59:18 +0100 (lun. 03 janv. 2011) $
+ * @since 2.0
+ */
+public class BetaDistributionImpl
+    extends AbstractContinuousDistribution implements BetaDistribution {
+
+    /**
+     * 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 double alpha;
+
+    /** Second shape parameter. */
+    private 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.
+     * @param alpha first shape parameter (must be positive)
+     * @param beta second shape parameter (must be positive)
+     * @param inverseCumAccuracy the maximum absolute error in inverse cumulative probability estimates
+     * (defaults to {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY})
+     * @since 2.1
+     */
+    public BetaDistributionImpl(double alpha, double beta, double inverseCumAccuracy) {
+        this.alpha = alpha;
+        this.beta = beta;
+        z = Double.NaN;
+        solverAbsoluteAccuracy = inverseCumAccuracy;
+    }
+
+    /**
+     * Build a new instance.
+     * @param alpha first shape parameter (must be positive)
+     * @param beta second shape parameter (must be positive)
+     */
+    public BetaDistributionImpl(double alpha, double beta) {
+        this(alpha, beta, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+    }
+
+    /** {@inheritDoc}
+     * @deprecated as of 2.1 (class will become immutable in 3.0)
+     */
+    @Deprecated
+    public void setAlpha(double alpha) {
+        this.alpha = alpha;
+        z = Double.NaN;
+    }
+
+    /** {@inheritDoc} */
+    public double getAlpha() {
+        return alpha;
+    }
+
+    /** {@inheritDoc}
+     * @deprecated as of 2.1 (class will become immutable in 3.0)
+     */
+    @Deprecated
+    public void setBeta(double beta) {
+        this.beta = beta;
+        z = Double.NaN;
+    }
+
+    /** {@inheritDoc} */
+    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);
+        }
+    }
+
+    /**
+     * Return the probability density for a particular point.
+     *
+     * @param x The point at which the density should be computed.
+     * @return The pdf at point x.
+     * @deprecated
+     */
+    @Deprecated
+    public double density(Double x) {
+        return density(x.doubleValue());
+    }
+
+    /**
+     * Return the probability density for a particular point.
+     *
+     * @param x The point at which the density should be computed.
+     * @return The pdf at point x.
+     * @since 2.1
+     */
+    @Override
+    public double density(double x) {
+        recomputeZ();
+        if (x < 0 || x > 1) {
+            return 0;
+        } else if (x == 0) {
+            if (alpha < 1) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                        LocalizedFormats.CANNOT_COMPUTE_BETA_DENSITY_AT_0_FOR_SOME_ALPHA, alpha);
+            }
+            return 0;
+        } else if (x == 1) {
+            if (beta < 1) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                        LocalizedFormats.CANNOT_COMPUTE_BETA_DENSITY_AT_1_FOR_SOME_BETA, beta);
+            }
+            return 0;
+        } else {
+            double logX = FastMath.log(x);
+            double log1mX = FastMath.log1p(-x);
+            return FastMath.exp((alpha - 1) * logX + (beta - 1) * log1mX - z);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double inverseCumulativeProbability(double p) throws MathException {
+        if (p == 0) {
+            return 0;
+        } else if (p == 1) {
+            return 1;
+        } else {
+            return super.inverseCumulativeProbability(p);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected double getInitialDomain(double p) {
+        return p;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected double getDomainLowerBound(double p) {
+        return 0;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected double getDomainUpperBound(double p) {
+        return 1;
+    }
+
+    /** {@inheritDoc} */
+    public double cumulativeProbability(double x) throws MathException {
+        if (x <= 0) {
+            return 0;
+        } else if (x >= 1) {
+            return 1;
+        } else {
+            return Beta.regularizedBeta(x, alpha, beta);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double cumulativeProbability(double x0, double x1) throws MathException {
+        return cumulativeProbability(x1) - cumulativeProbability(x0);
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * Returns the lower bound of the support for this distribution.
+     * The support of the Beta distribution is always [0, 1], regardless
+     * of the parameters, so this method always returns 0.
+     *
+     * @return lower bound of the support (always 0)
+     * @since 2.2
+     */
+    public double getSupportLowerBound() {
+        return 0;
+    }
+
+    /**
+     * Returns the upper bound of the support for this distribution.
+     * The support of the Beta distribution is always [0, 1], regardless
+     * of the parameters, so this method always returns 1.
+     *
+     * @return lower bound of the support (always 1)
+     * @since 2.2
+     */
+    public double getSupportUpperBound() {
+        return 1;
+    }
+
+    /**
+     * Returns the mean.
+     *
+     * For first shape parameter <code>s1</code> and
+     * second shape parameter <code>s2</code>, the mean is
+     * <code>s1 / (s1 + s2)</code>
+     *
+     * @return the mean
+     * @since 2.2
+     */
+    public double getNumericalMean() {
+        final double a = getAlpha();
+        return a / (a + getBeta());
+    }
+
+    /**
+     * Returns the variance.
+     *
+     * For first shape parameter <code>s1</code> and
+     * second shape parameter <code>s2</code>,
+     * the variance is
+     * <code>[ s1 * s2 ] / [ (s1 + s2)^2 * (s1 + s2 + 1) ]</code>
+     *
+     * @return the variance
+     * @since 2.2
+     */
+    public double getNumericalVariance() {
+        final double a = getAlpha();
+        final double b = getBeta();
+        final double alphabetasum = a + b;
+        return (a * b) / ((alphabetasum * alphabetasum) * (alphabetasum + 1));
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/distribution/BinomialDistribution.java b/src/main/java/org/apache/commons/math/distribution/BinomialDistribution.java
new file mode 100644
index 0000000..94b3236
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/BinomialDistribution.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.math.distribution;
+
+/**
+ * The Binomial Distribution.
+ *
+ * <p>
+ * References:
+ * <ul>
+ * <li><a href="http://mathworld.wolfram.com/BinomialDistribution.html">
+ * Binomial Distribution</a></li>
+ * </ul>
+ * </p>
+ *
+ * @version $Revision: 920852 $ $Date: 2010-03-09 13:53:44 +0100 (mar. 09 mars 2010) $
+ */
+public interface BinomialDistribution extends IntegerDistribution {
+    /**
+     * Access the number of trials for this distribution.
+     * @return the number of trials.
+     */
+    int getNumberOfTrials();
+
+    /**
+     * Access the probability of success for this distribution.
+     * @return the probability of success.
+     */
+    double getProbabilityOfSuccess();
+
+    /**
+     * Change the number of trials for this distribution.
+     * @param trials the new number of trials.
+     * @deprecated as of v2.1
+     */
+    @Deprecated
+    void setNumberOfTrials(int trials);
+
+    /**
+     * Change the probability of success for this distribution.
+     * @param p the new probability of success.
+     * @deprecated as of v2.1
+     */
+    @Deprecated
+    void setProbabilityOfSuccess(double p);
+}
diff --git a/src/main/java/org/apache/commons/math/distribution/BinomialDistributionImpl.java b/src/main/java/org/apache/commons/math/distribution/BinomialDistributionImpl.java
new file mode 100644
index 0000000..9ebb629
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/BinomialDistributionImpl.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.math.distribution;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.special.Beta;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * The default implementation of {@link BinomialDistribution}.
+ *
+ * @version $Revision: 1054524 $ $Date: 2011-01-03 05:59:18 +0100 (lun. 03 janv. 2011) $
+ */
+public class BinomialDistributionImpl extends AbstractIntegerDistribution
+        implements BinomialDistribution, Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 6751309484392813623L;
+
+    /** The number of trials. */
+    private int numberOfTrials;
+
+    /** The probability of success. */
+    private double probabilityOfSuccess;
+
+    /**
+     * Create a binomial distribution with the given number of trials and
+     * probability of success.
+     *
+     * @param trials the number of trials.
+     * @param p the probability of success.
+     */
+    public BinomialDistributionImpl(int trials, double p) {
+        super();
+        setNumberOfTrialsInternal(trials);
+        setProbabilityOfSuccessInternal(p);
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * Change the number of trials for this distribution.
+     *
+     * @param trials the new number of trials.
+     * @throws IllegalArgumentException if <code>trials</code> is not a valid
+     *             number of trials.
+     * @deprecated as of 2.1 (class will become immutable in 3.0)
+     */
+    @Deprecated
+    public void setNumberOfTrials(int trials) {
+        setNumberOfTrialsInternal(trials);
+    }
+
+    /**
+     * Change the number of trials for this distribution.
+     *
+     * @param trials the new number of trials.
+     * @throws IllegalArgumentException if <code>trials</code> is not a valid
+     *             number of trials.
+     */
+    private void setNumberOfTrialsInternal(int trials) {
+        if (trials < 0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.NEGATIVE_NUMBER_OF_TRIALS, trials);
+        }
+        numberOfTrials = trials;
+    }
+
+    /**
+     * Change the probability of success for this distribution.
+     *
+     * @param p the new probability of success.
+     * @throws IllegalArgumentException if <code>p</code> is not a valid
+     *             probability.
+     * @deprecated as of 2.1 (class will become immutable in 3.0)
+     */
+    @Deprecated
+    public void setProbabilityOfSuccess(double p) {
+        setProbabilityOfSuccessInternal(p);
+    }
+
+    /**
+     * Change the probability of success for this distribution.
+     *
+     * @param p the new probability of success.
+     * @throws IllegalArgumentException if <code>p</code> is not a valid
+     *             probability.
+     */
+    private void setProbabilityOfSuccessInternal(double p) {
+        if (p < 0.0 || p > 1.0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.OUT_OF_RANGE_SIMPLE, p, 0.0, 1.0);
+        }
+        probabilityOfSuccess = p;
+    }
+
+    /**
+     * Access the domain value lower bound, based on <code>p</code>, used to
+     * bracket a PDF root.
+     *
+     * @param p the desired probability for the critical value
+     * @return domain value lower bound, i.e. P(X &lt; <i>lower bound</i>) &lt;
+     *         <code>p</code>
+     */
+    @Override
+    protected int getDomainLowerBound(double p) {
+        return -1;
+    }
+
+    /**
+     * Access the domain value upper bound, based on <code>p</code>, used to
+     * bracket a PDF root.
+     *
+     * @param p the desired probability for the critical value
+     * @return domain value upper bound, i.e. P(X &lt; <i>upper bound</i>) &gt;
+     *         <code>p</code>
+     */
+    @Override
+    protected int getDomainUpperBound(double p) {
+        return numberOfTrials;
+    }
+
+    /**
+     * For this distribution, X, this method returns P(X &le; x).
+     *
+     * @param x the value at which the PDF is evaluated.
+     * @return PDF for this distribution.
+     * @throws MathException if the cumulative probability can not be computed
+     *             due to convergence or other numerical errors.
+     */
+    @Override
+    public double cumulativeProbability(int x) throws MathException {
+        double ret;
+        if (x < 0) {
+            ret = 0.0;
+        } else if (x >= numberOfTrials) {
+            ret = 1.0;
+        } else {
+            ret = 1.0 - Beta.regularizedBeta(getProbabilityOfSuccess(),
+                    x + 1.0, numberOfTrials - x);
+        }
+        return ret;
+    }
+
+    /**
+     * For this distribution, X, this method returns P(X = x).
+     *
+     * @param x the value at which the PMF is evaluated.
+     * @return PMF for this distribution.
+     */
+    public double probability(int x) {
+        double ret;
+        if (x < 0 || x > numberOfTrials) {
+            ret = 0.0;
+        } else {
+            ret = FastMath.exp(SaddlePointExpansion.logBinomialProbability(x,
+                    numberOfTrials, probabilityOfSuccess,
+                    1.0 - probabilityOfSuccess));
+        }
+        return ret;
+    }
+
+    /**
+     * For this distribution, X, this method returns the largest x, such that
+     * P(X &le; x) &le; <code>p</code>.
+     * <p>
+     * Returns <code>-1</code> for p=0 and <code>Integer.MAX_VALUE</code> for
+     * p=1.
+     * </p>
+     *
+     * @param p the desired probability
+     * @return the largest x such that P(X &le; x) <= p
+     * @throws MathException if the inverse cumulative probability can not be
+     *             computed due to convergence or other numerical errors.
+     * @throws IllegalArgumentException if p < 0 or p > 1
+     */
+    @Override
+    public int inverseCumulativeProbability(final double p)
+            throws MathException {
+        // handle extreme values explicitly
+        if (p == 0) {
+            return -1;
+        }
+        if (p == 1) {
+            return Integer.MAX_VALUE;
+        }
+
+        // use default bisection impl
+        return super.inverseCumulativeProbability(p);
+    }
+
+    /**
+     * Returns the lower bound of the support for the distribution.
+     *
+     * The lower bound of the support is always 0 no matter the number of trials
+     * and probability parameter.
+     *
+     * @return lower bound of the support (always 0)
+     * @since 2.2
+     */
+    public int getSupportLowerBound() {
+        return 0;
+    }
+
+    /**
+     * Returns the upper bound of the support for the distribution.
+     *
+     * The upper bound of the support is the number of trials.
+     *
+     * @return upper bound of the support (equal to number of trials)
+     * @since 2.2
+     */
+    public int getSupportUpperBound() {
+        return getNumberOfTrials();
+    }
+
+    /**
+     * Returns the mean.
+     *
+     * For <code>n</code> number of trials and
+     * probability parameter <code>p</code>, the mean is
+     * <code>n * p</code>
+     *
+     * @return the mean
+     * @since 2.2
+     */
+    public double getNumericalMean() {
+        return (double)getNumberOfTrials() * getProbabilityOfSuccess();
+    }
+
+    /**
+     * Returns the variance.
+     *
+     * For <code>n</code> number of trials and
+     * probability parameter <code>p</code>, the variance is
+     * <code>n * p * (1 - p)</code>
+     *
+     * @return the variance
+     * @since 2.2
+     */
+    public double getNumericalVariance() {
+        final double p = getProbabilityOfSuccess();
+        return (double)getNumberOfTrials() * p * (1 - p);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/distribution/CauchyDistribution.java b/src/main/java/org/apache/commons/math/distribution/CauchyDistribution.java
new file mode 100644
index 0000000..7a4ccbd
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/CauchyDistribution.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.math.distribution;
+
+/**
+ * Cauchy Distribution.
+ *
+ * <p>
+ * References:
+ * <ul>
+ * <li><a href="http://mathworld.wolfram.com/CauchyDistribution.html">
+ * Cauchy Distribution</a></li>
+ * </ul>
+ * </p>
+ *
+ * @since 1.1
+ * @version $Revision: 920852 $ $Date: 2010-03-09 13:53:44 +0100 (mar. 09 mars 2010) $
+ */
+public interface CauchyDistribution extends ContinuousDistribution {
+
+    /**
+     * Access the median.
+     * @return median for this distribution
+     */
+    double getMedian();
+
+    /**
+     * Access the scale parameter.
+     * @return scale parameter for this distribution
+     */
+    double getScale();
+
+    /**
+     * Modify the median.
+     * @param median for this distribution
+     * @deprecated as of v2.1
+     */
+    @Deprecated
+    void setMedian(double median);
+
+    /**
+     * Modify the scale parameter.
+     * @param s scale parameter for this distribution
+     * @deprecated as of v2.1
+     */
+    @Deprecated
+    void setScale(double s);
+}
diff --git a/src/main/java/org/apache/commons/math/distribution/CauchyDistributionImpl.java b/src/main/java/org/apache/commons/math/distribution/CauchyDistributionImpl.java
new file mode 100644
index 0000000..b076924
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/CauchyDistributionImpl.java
@@ -0,0 +1,320 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.distribution;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Default implementation of
+ * {@link org.apache.commons.math.distribution.CauchyDistribution}.
+ *
+ * @since 1.1
+ * @version $Revision: 1054524 $ $Date: 2011-01-03 05:59:18 +0100 (lun. 03 janv. 2011) $
+ */
+public class CauchyDistributionImpl extends AbstractContinuousDistribution
+        implements CauchyDistribution, Serializable {
+
+    /**
+     * 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 double median = 0;
+
+    /** The scale of this distribution. */
+    private double scale = 1;
+
+    /** Inverse cumulative probability accuracy */
+    private final double solverAbsoluteAccuracy;
+
+    /**
+     * Creates cauchy distribution with the medain equal to zero and scale
+     * equal to one.
+     */
+    public CauchyDistributionImpl(){
+        this(0.0, 1.0);
+    }
+
+    /**
+     * Create a cauchy distribution using the given median and scale.
+     * @param median median for this distribution
+     * @param s scale parameter for this distribution
+     */
+    public CauchyDistributionImpl(double median, double s){
+        this(median, s, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+    }
+
+    /**
+     * Create a cauchy distribution using the given median and scale.
+     * @param median median for this distribution
+     * @param s scale parameter for this distribution
+     * @param inverseCumAccuracy the maximum absolute error in inverse cumulative probability estimates
+     * (defaults to {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY})
+     * @since 2.1
+     */
+    public CauchyDistributionImpl(double median, double s, double inverseCumAccuracy) {
+        super();
+        setMedianInternal(median);
+        setScaleInternal(s);
+        solverAbsoluteAccuracy = inverseCumAccuracy;
+    }
+
+    /**
+     * For this distribution, X, this method returns P(X &lt; <code>x</code>).
+     * @param x the value at which the CDF is evaluated.
+     * @return CDF evaluated at <code>x</code>.
+     */
+    public double cumulativeProbability(double x) {
+        return 0.5 + (FastMath.atan((x - median) / scale) / FastMath.PI);
+    }
+
+    /**
+     * Access the median.
+     * @return median for this distribution
+     */
+    public double getMedian() {
+        return median;
+    }
+
+    /**
+     * Access the scale parameter.
+     * @return scale parameter for this distribution
+     */
+    public double getScale() {
+        return scale;
+    }
+
+    /**
+     * Returns the probability density for a particular point.
+     *
+     * @param x The point at which the density should be computed.
+     * @return The pdf at point x.
+     * @since 2.1
+     */
+    @Override
+    public double density(double x) {
+        final double dev = x - median;
+        return (1 / FastMath.PI) * (scale / (dev * dev + scale * scale));
+    }
+
+    /**
+     * For this distribution, X, this method returns the critical point x, such
+     * that P(X &lt; x) = <code>p</code>.
+     * <p>
+     * Returns <code>Double.NEGATIVE_INFINITY</code> for p=0 and
+     * <code>Double.POSITIVE_INFINITY</code> for p=1.</p>
+     *
+     * @param p the desired probability
+     * @return x, such that P(X &lt; x) = <code>p</code>
+     * @throws IllegalArgumentException if <code>p</code> is not a valid
+     *         probability.
+     */
+    @Override
+    public double inverseCumulativeProbability(double p) {
+        double ret;
+        if (p < 0.0 || p > 1.0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.OUT_OF_RANGE_SIMPLE, p, 0.0, 1.0);
+        } 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;
+    }
+
+    /**
+     * Modify the median.
+     * @param median for this distribution
+     * @deprecated as of 2.1 (class will become immutable in 3.0)
+     */
+    @Deprecated
+    public void setMedian(double median) {
+        setMedianInternal(median);
+    }
+
+    /**
+     * Modify the median.
+     * @param newMedian for this distribution
+     */
+    private void setMedianInternal(double newMedian) {
+        this.median = newMedian;
+    }
+
+    /**
+     * Modify the scale parameter.
+     * @param s scale parameter for this distribution
+     * @throws IllegalArgumentException if <code>sd</code> is not positive.
+     * @deprecated as of 2.1 (class will become immutable in 3.0)
+     */
+    @Deprecated
+    public void setScale(double s) {
+        setScaleInternal(s);
+    }
+
+    /**
+     * Modify the scale parameter.
+     * @param s scale parameter for this distribution
+     * @throws IllegalArgumentException if <code>sd</code> is not positive.
+     */
+    private void setScaleInternal(double s) {
+        if (s <= 0.0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.NOT_POSITIVE_SCALE, s);
+        }
+        scale = s;
+    }
+
+    /**
+     * Access the domain value lower bound, based on <code>p</code>, used to
+     * bracket a CDF root.  This method is used by
+     * {@link #inverseCumulativeProbability(double)} to find critical values.
+     *
+     * @param p the desired probability for the critical value
+     * @return domain value lower bound, i.e.
+     *         P(X &lt; <i>lower bound</i>) &lt; <code>p</code>
+     */
+    @Override
+    protected double getDomainLowerBound(double p) {
+        double ret;
+
+        if (p < .5) {
+            ret = -Double.MAX_VALUE;
+        } else {
+            ret = median;
+        }
+
+        return ret;
+    }
+
+    /**
+     * Access the domain value upper bound, based on <code>p</code>, used to
+     * bracket a CDF root.  This method is used by
+     * {@link #inverseCumulativeProbability(double)} to find critical values.
+     *
+     * @param p the desired probability for the critical value
+     * @return domain value upper bound, i.e.
+     *         P(X &lt; <i>upper bound</i>) &gt; <code>p</code>
+     */
+    @Override
+    protected double getDomainUpperBound(double p) {
+        double ret;
+
+        if (p < .5) {
+            ret = median;
+        } else {
+            ret = Double.MAX_VALUE;
+        }
+
+        return ret;
+    }
+
+    /**
+     * Access the initial domain value, based on <code>p</code>, used to
+     * bracket a CDF root.  This method is used by
+     * {@link #inverseCumulativeProbability(double)} to find critical values.
+     *
+     * @param p the desired probability for the critical value
+     * @return initial domain value
+     */
+    @Override
+    protected double getInitialDomain(double p) {
+        double ret;
+
+        if (p < .5) {
+            ret = median - scale;
+        } else if (p > .5) {
+            ret = median + scale;
+        } else {
+            ret = median;
+        }
+
+        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;
+    }
+
+    /**
+     * Returns the lower bound of the support for this distribution.
+     * The lower bound of the support of the Cauchy distribution is always
+     * negative infinity, regardless of the parameters.
+     *
+     * @return lower bound of the support (always Double.NEGATIVE_INFINITY)
+     * @since 2.2
+     */
+    public double getSupportLowerBound() {
+        return Double.NEGATIVE_INFINITY;
+    }
+
+    /**
+     * Returns the upper bound of the support for this distribution.
+     * The upper bound of the support of the Cauchy distribution is always
+     * positive infinity, regardless of the parameters.
+     *
+     * @return upper bound of the support (always Double.POSITIVE_INFINITY)
+     * @since 2.2
+     */
+    public double getSupportUpperBound() {
+        return Double.POSITIVE_INFINITY;
+    }
+
+    /**
+     * Returns the mean.
+     *
+     * The mean is always undefined, regardless of the parameters.
+     *
+     * @return mean (always Double.NaN)
+     * @since 2.2
+     */
+    public double getNumericalMean() {
+        return Double.NaN;
+    }
+
+    /**
+     * Returns the variance.
+     *
+     * The variance is always undefined, regardless of the parameters.
+     *
+     * @return variance (always Double.NaN)
+     * @since 2.2
+     */
+    public double getNumericalVariance() {
+        return Double.NaN;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/distribution/ChiSquaredDistribution.java b/src/main/java/org/apache/commons/math/distribution/ChiSquaredDistribution.java
new file mode 100644
index 0000000..0478db1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/ChiSquaredDistribution.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.math.distribution;
+
+/**
+ * The Chi-Squared Distribution.
+ *
+ * <p>
+ * References:
+ * <ul>
+ * <li><a href="http://mathworld.wolfram.com/Chi-SquaredDistribution.html">
+ * Chi-Squared Distribution</a></li>
+ * </ul>
+ * </p>
+ *
+ * @version $Revision: 920852 $ $Date: 2010-03-09 13:53:44 +0100 (mar. 09 mars 2010) $
+ */
+public interface ChiSquaredDistribution extends ContinuousDistribution, HasDensity<Double> {
+    /**
+     * Modify the degrees of freedom.
+     * @param degreesOfFreedom the new degrees of freedom.
+     * @deprecated as of v2.1
+     */
+    @Deprecated
+    void setDegreesOfFreedom(double degreesOfFreedom);
+
+    /**
+     * Access the degrees of freedom.
+     * @return the degrees of freedom.
+     */
+    double getDegreesOfFreedom();
+
+    /**
+     * Return the probability density for a particular point.
+     * @param x  The point at which the density should be computed.
+     * @return  The pdf at point x.
+     */
+    double density(Double x);
+}
diff --git a/src/main/java/org/apache/commons/math/distribution/ChiSquaredDistributionImpl.java b/src/main/java/org/apache/commons/math/distribution/ChiSquaredDistributionImpl.java
new file mode 100644
index 0000000..f877792
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/ChiSquaredDistributionImpl.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.math.distribution;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.MathException;
+
+/**
+ * The default implementation of {@link ChiSquaredDistribution}
+ *
+ * @version $Revision: 1054524 $ $Date: 2011-01-03 05:59:18 +0100 (lun. 03 janv. 2011) $
+ */
+public class ChiSquaredDistributionImpl
+    extends AbstractContinuousDistribution
+    implements ChiSquaredDistribution, Serializable  {
+
+    /**
+     * 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 GammaDistribution gamma;
+
+    /** Inverse cumulative probability accuracy */
+    private final double solverAbsoluteAccuracy;
+
+    /**
+     * Create a Chi-Squared distribution with the given degrees of freedom.
+     * @param df degrees of freedom.
+     */
+    public ChiSquaredDistributionImpl(double df) {
+        this(df, new GammaDistributionImpl(df / 2.0, 2.0));
+    }
+
+    /**
+     * Create a Chi-Squared distribution with the given degrees of freedom.
+     * @param df degrees of freedom.
+     * @param g the underlying gamma distribution used to compute probabilities.
+     * @since 1.2
+     * @deprecated as of 2.1 (to avoid possibly inconsistent state, the
+     * "GammaDistribution" will be instantiated internally)
+     */
+    @Deprecated
+    public ChiSquaredDistributionImpl(double df, GammaDistribution g) {
+        super();
+        setGammaInternal(g);
+        setDegreesOfFreedomInternal(df);
+        solverAbsoluteAccuracy = DEFAULT_INVERSE_ABSOLUTE_ACCURACY;
+    }
+
+    /**
+     * Create a Chi-Squared distribution with the given degrees of freedom and
+     * inverse cumulative probability accuracy.
+     * @param df 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 ChiSquaredDistributionImpl(double df, double inverseCumAccuracy) {
+        super();
+        gamma = new GammaDistributionImpl(df / 2.0, 2.0);
+        setDegreesOfFreedomInternal(df);
+        solverAbsoluteAccuracy = inverseCumAccuracy;
+    }
+
+    /**
+     * Modify the degrees of freedom.
+     * @param degreesOfFreedom the new degrees of freedom.
+     * @deprecated as of 2.1 (class will become immutable in 3.0)
+     */
+    @Deprecated
+    public void setDegreesOfFreedom(double degreesOfFreedom) {
+        setDegreesOfFreedomInternal(degreesOfFreedom);
+    }
+    /**
+     * Modify the degrees of freedom.
+     * @param degreesOfFreedom the new degrees of freedom.
+     */
+    private void setDegreesOfFreedomInternal(double degreesOfFreedom) {
+        gamma.setAlpha(degreesOfFreedom / 2.0);
+    }
+
+    /**
+     * Access the degrees of freedom.
+     * @return the degrees of freedom.
+     */
+    public double getDegreesOfFreedom() {
+        return gamma.getAlpha() * 2.0;
+    }
+
+    /**
+     * Return the probability density for a particular point.
+     *
+     * @param x The point at which the density should be computed.
+     * @return The pdf at point x.
+     * @deprecated
+     */
+    @Deprecated
+    public double density(Double x) {
+        return density(x.doubleValue());
+    }
+
+    /**
+     * Return the probability density for a particular point.
+     *
+     * @param x The point at which the density should be computed.
+     * @return The pdf at point x.
+     * @since 2.1
+     */
+    @Override
+    public double density(double x) {
+        return gamma.density(x);
+    }
+
+    /**
+     * For this distribution, X, this method returns P(X &lt; x).
+     * @param x the value at which the CDF is evaluated.
+     * @return CDF for this distribution.
+     * @throws MathException if the cumulative probability can not be
+     *            computed due to convergence or other numerical errors.
+     */
+    public double cumulativeProbability(double x) throws MathException {
+        return gamma.cumulativeProbability(x);
+    }
+
+    /**
+     * For this distribution, X, this method returns the critical point x, such
+     * that P(X &lt; x) = <code>p</code>.
+     * <p>
+     * Returns 0 for p=0 and <code>Double.POSITIVE_INFINITY</code> for p=1.</p>
+     *
+     * @param p the desired probability
+     * @return x, such that P(X &lt; x) = <code>p</code>
+     * @throws MathException if the inverse cumulative probability can not be
+     *         computed due to convergence or other numerical errors.
+     * @throws IllegalArgumentException if <code>p</code> is not a valid
+     *         probability.
+     */
+    @Override
+    public double inverseCumulativeProbability(final double p)
+        throws MathException {
+        if (p == 0) {
+            return 0d;
+        }
+        if (p == 1) {
+            return Double.POSITIVE_INFINITY;
+        }
+        return super.inverseCumulativeProbability(p);
+    }
+
+    /**
+     * Access the domain value lower bound, based on <code>p</code>, used to
+     * bracket a CDF root.  This method is used by
+     * {@link #inverseCumulativeProbability(double)} to find critical values.
+     *
+     * @param p the desired probability for the critical value
+     * @return domain value lower bound, i.e.
+     *         P(X &lt; <i>lower bound</i>) &lt; <code>p</code>
+     */
+    @Override
+    protected double getDomainLowerBound(double p) {
+        return Double.MIN_VALUE * gamma.getBeta();
+    }
+
+    /**
+     * Access the domain value upper bound, based on <code>p</code>, used to
+     * bracket a CDF root.  This method is used by
+     * {@link #inverseCumulativeProbability(double)} to find critical values.
+     *
+     * @param p the desired probability for the critical value
+     * @return domain value upper bound, i.e.
+     *         P(X &lt; <i>upper bound</i>) &gt; <code>p</code>
+     */
+    @Override
+    protected double getDomainUpperBound(double p) {
+        // NOTE: chi squared is skewed to the left
+        // NOTE: therefore, P(X < &mu;) > .5
+
+        double ret;
+
+        if (p < .5) {
+            // use mean
+            ret = getDegreesOfFreedom();
+        } else {
+            // use max
+            ret = Double.MAX_VALUE;
+        }
+
+        return ret;
+    }
+
+    /**
+     * Access the initial domain value, based on <code>p</code>, used to
+     * bracket a CDF root.  This method is used by
+     * {@link #inverseCumulativeProbability(double)} to find critical values.
+     *
+     * @param p the desired probability for the critical value
+     * @return initial domain value
+     */
+    @Override
+    protected double getInitialDomain(double p) {
+        // NOTE: chi squared is skewed to the left
+        // NOTE: therefore, P(X < &mu;) > .5
+
+        double ret;
+
+        if (p < .5) {
+            // use 1/2 mean
+            ret = getDegreesOfFreedom() * .5;
+        } else {
+            // use mean
+            ret = getDegreesOfFreedom();
+        }
+
+        return ret;
+    }
+
+    /**
+     * Modify the underlying gamma distribution.  The caller is responsible for
+     * insuring the gamma distribution has the proper parameter settings.
+     * @param g the new distribution.
+     * @since 1.2 made public
+     * @deprecated as of 2.1 (class will become immutable in 3.0)
+     */
+    @Deprecated
+    public void setGamma(GammaDistribution g) {
+        setGammaInternal(g);
+    }
+    /**
+     * Modify the underlying gamma distribution.  The caller is responsible for
+     * insuring the gamma distribution has the proper parameter settings.
+     * @param g the new distribution.
+     * @since 1.2 made public
+     */
+    private void setGammaInternal(GammaDistribution g) {
+        this.gamma = g;
+
+    }
+
+
+    /**
+     * 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;
+    }
+
+    /**
+     * Returns the lower bound of the support for the distribution.
+     *
+     * The lower bound of the support is always 0 no matter the
+     * degrees of freedom.
+     *
+     * @return lower bound of the support (always 0)
+     * @since 2.2
+     */
+    public double getSupportLowerBound() {
+        return 0;
+    }
+
+    /**
+     * Returns the upper bound for the support for the distribution.
+     *
+     * The upper bound of the support is always positive infinity no matter the
+     * degrees of freedom.
+     *
+     * @return upper bound of the support (always Double.POSITIVE_INFINITY)
+     * @since 2.2
+     */
+    public double getSupportUpperBound() {
+        return Double.POSITIVE_INFINITY;
+    }
+
+    /**
+     * Returns the mean of the distribution.
+     *
+     * For <code>k</code> degrees of freedom, the mean is
+     * <code>k</code>
+     *
+     * @return the mean
+     * @since 2.2
+     */
+    public double getNumericalMean() {
+        return getDegreesOfFreedom();
+    }
+
+    /**
+     * Returns the variance of the distribution.
+     *
+     * For <code>k</code> degrees of freedom, the variance is
+     * <code>2 * k</code>
+     *
+     * @return the variance
+     * @since 2.2
+     */
+    public double getNumericalVariance() {
+        return 2*getDegreesOfFreedom();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/distribution/ContinuousDistribution.java b/src/main/java/org/apache/commons/math/distribution/ContinuousDistribution.java
new file mode 100644
index 0000000..afcd4c3
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/ContinuousDistribution.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.math.distribution;
+
+import org.apache.commons.math.MathException;
+
+/**
+ * <p>Base interface for continuous distributions.</p>
+ *
+ * <p>Note: this interface will be extended in version 3.0 to include
+ * <br/><code>public double density(double x)</code><br/>
+ * that is, from version 3.0 forward, continuous distributions <strong>must</strong>
+ * include implementations of probability density functions. As of version
+ * 2.1, all continuous distribution implementations included in commons-math
+ * provide implementations of this method.</p>
+ *
+ * @version $Revision: 924362 $ $Date: 2010-03-17 17:45:31 +0100 (mer. 17 mars 2010) $
+ */
+public interface ContinuousDistribution extends Distribution {
+
+    /**
+     * For this distribution, X, this method returns x such that P(X &lt; x) = p.
+     * @param p the cumulative probability.
+     * @return x.
+     * @throws MathException if the inverse cumulative probability can not be
+     *            computed due to convergence or other numerical errors.
+     */
+    double inverseCumulativeProbability(double p) throws MathException;
+}
diff --git a/src/main/java/org/apache/commons/math/distribution/DiscreteDistribution.java b/src/main/java/org/apache/commons/math/distribution/DiscreteDistribution.java
new file mode 100644
index 0000000..d6ea444
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/DiscreteDistribution.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.math.distribution;
+
+
+/**
+ * Base interface for discrete distributions.
+ *
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+public interface DiscreteDistribution extends Distribution {
+    /**
+     * For a random variable X whose values are distributed according
+     * to this distribution, this method returns P(X = x). In other words, this
+     * method represents the probability mass function, or PMF for the distribution.
+     *
+     * @param x the value at which the probability mass function is evaluated.
+     * @return the value of the probability mass function at x
+     */
+    double probability(double x);
+}
diff --git a/src/main/java/org/apache/commons/math/distribution/Distribution.java b/src/main/java/org/apache/commons/math/distribution/Distribution.java
new file mode 100644
index 0000000..221aeb7
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/Distribution.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.math.distribution;
+
+import org.apache.commons.math.MathException;
+
+/**
+ * Base interface for probability distributions.
+ *
+ * @version $Revision: 1054524 $ $Date: 2011-01-03 05:59:18 +0100 (lun. 03 janv. 2011) $
+ */
+public interface Distribution {
+    /**
+     * For a random variable X whose values are distributed according
+     * to this distribution, this method returns P(X &le; x).  In other words,
+     * this method represents the  (cumulative) distribution function, or
+     * CDF, for this distribution.
+     *
+     * @param x the value at which the distribution function is evaluated.
+     * @return the probability that a random variable with this
+     * distribution takes a value less than or equal to <code>x</code>
+     * @throws MathException if the cumulative probability can not be
+     * computed due to convergence or other numerical errors.
+     */
+    double cumulativeProbability(double x) throws MathException;
+
+    /**
+     * For a random variable X whose values are distributed according
+     * to this distribution, this method returns P(x0 &le; X &le; x1).
+     *
+     * @param x0 the (inclusive) 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</code> and <code>x1</code>,
+     * including the endpoints
+     * @throws MathException if the cumulative probability can not be
+     * computed due to convergence or other numerical errors.
+     * @throws IllegalArgumentException if <code>x0 > x1</code>
+     */
+    double cumulativeProbability(double x0, double x1) throws MathException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/distribution/ExponentialDistribution.java b/src/main/java/org/apache/commons/math/distribution/ExponentialDistribution.java
new file mode 100644
index 0000000..6d3fbe2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/ExponentialDistribution.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.math.distribution;
+
+/**
+ * The Exponential Distribution.
+ *
+ * <p>
+ * References:
+ * <ul>
+ * <li><a href="http://mathworld.wolfram.com/ExponentialDistribution.html">
+ * Exponential Distribution</a></li>
+ * </ul>
+ * </p>
+ *
+ * @version $Revision: 920852 $ $Date: 2010-03-09 13:53:44 +0100 (mar. 09 mars 2010) $
+ */
+public interface ExponentialDistribution extends ContinuousDistribution, HasDensity<Double> {
+    /**
+     * Modify the mean.
+     * @param mean the new mean.
+     * @deprecated as of v2.1
+     */
+    @Deprecated
+    void setMean(double mean);
+
+    /**
+     * Access the mean.
+     * @return the mean.
+     */
+    double getMean();
+
+    /**
+     * Return the probability density for a particular point.
+     * @param x  The point at which the density should be computed.
+     * @return  The pdf at point x.
+     */
+    double density(Double x);
+}
diff --git a/src/main/java/org/apache/commons/math/distribution/ExponentialDistributionImpl.java b/src/main/java/org/apache/commons/math/distribution/ExponentialDistributionImpl.java
new file mode 100644
index 0000000..25d81f4
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/ExponentialDistributionImpl.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.math.distribution;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * The default implementation of {@link ExponentialDistribution}.
+ *
+ * @version $Revision: 1055914 $ $Date: 2011-01-06 16:34:34 +0100 (jeu. 06 janv. 2011) $
+ */
+public class ExponentialDistributionImpl extends AbstractContinuousDistribution
+    implements ExponentialDistribution, Serializable {
+
+    /**
+     * 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;
+
+    /** The mean of this distribution. */
+    private double mean;
+
+    /** Inverse cumulative probability accuracy */
+    private final double solverAbsoluteAccuracy;
+
+    /**
+     * Create a exponential distribution with the given mean.
+     * @param mean mean of this distribution.
+     */
+    public ExponentialDistributionImpl(double mean) {
+        this(mean, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+    }
+
+    /**
+     * Create a exponential distribution with the given mean.
+     * @param mean mean of this distribution.
+     * @param inverseCumAccuracy the maximum absolute error in inverse cumulative probability estimates
+     * (defaults to {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY})
+     * @since 2.1
+     */
+    public ExponentialDistributionImpl(double mean, double inverseCumAccuracy) {
+        super();
+        setMeanInternal(mean);
+        solverAbsoluteAccuracy = inverseCumAccuracy;
+    }
+
+    /**
+     * Modify the mean.
+     * @param mean the new mean.
+     * @throws IllegalArgumentException if <code>mean</code> is not positive.
+     * @deprecated as of 2.1 (class will become immutable in 3.0)
+     */
+    @Deprecated
+    public void setMean(double mean) {
+        setMeanInternal(mean);
+    }
+    /**
+     * Modify the mean.
+     * @param newMean the new mean.
+     * @throws IllegalArgumentException if <code>newMean</code> is not positive.
+     */
+    private void setMeanInternal(double newMean) {
+        if (newMean <= 0.0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.NOT_POSITIVE_MEAN, newMean);
+        }
+        this.mean = newMean;
+    }
+
+    /**
+     * Access the mean.
+     * @return the mean.
+     */
+    public double getMean() {
+        return mean;
+    }
+
+    /**
+     * Return the probability density for a particular point.
+     *
+     * @param x The point at which the density should be computed.
+     * @return The pdf at point x.
+     * @deprecated - use density(double)
+     */
+    @Deprecated
+    public double density(Double x) {
+        return density(x.doubleValue());
+    }
+
+    /**
+     * Return the probability density for a particular point.
+     *
+     * @param x The point at which the density should be computed.
+     * @return The pdf at point x.
+     * @since 2.1
+     */
+    @Override
+    public double density(double x) {
+        if (x < 0) {
+            return 0;
+        }
+        return FastMath.exp(-x / mean) / mean;
+    }
+
+    /**
+     * For this distribution, X, this method returns P(X &lt; x).
+     *
+     * The implementation of this method is based on:
+     * <ul>
+     * <li>
+     * <a href="http://mathworld.wolfram.com/ExponentialDistribution.html">
+     * Exponential Distribution</a>, equation (1).</li>
+     * </ul>
+     *
+     * @param x the value at which the CDF is evaluated.
+     * @return CDF for this distribution.
+     * @throws MathException if the cumulative probability can not be
+     *            computed due to convergence or other numerical errors.
+     */
+    public double cumulativeProbability(double x) throws MathException{
+        double ret;
+        if (x <= 0.0) {
+            ret = 0.0;
+        } else {
+            ret = 1.0 - FastMath.exp(-x / mean);
+        }
+        return ret;
+    }
+
+    /**
+     * For this distribution, X, this method returns the critical point x, such
+     * that P(X &lt; x) = <code>p</code>.
+     * <p>
+     * Returns 0 for p=0 and <code>Double.POSITIVE_INFINITY</code> for p=1.</p>
+     *
+     * @param p the desired probability
+     * @return x, such that P(X &lt; x) = <code>p</code>
+     * @throws MathException if the inverse cumulative probability can not be
+     *            computed due to convergence or other numerical errors.
+     * @throws IllegalArgumentException if p < 0 or p > 1.
+     */
+    @Override
+    public double inverseCumulativeProbability(double p) throws MathException {
+        double ret;
+
+        if (p < 0.0 || p > 1.0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.OUT_OF_RANGE_SIMPLE, p, 0.0, 1.0);
+        } else if (p == 1.0) {
+            ret = Double.POSITIVE_INFINITY;
+        } else {
+            ret = -mean * FastMath.log(1.0 - p);
+        }
+
+        return ret;
+    }
+
+    /**
+     * Generates a random value sampled from this distribution.
+     *
+     * <p><strong>Algorithm Description</strong>: 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. </p>
+     *
+     * @return random value
+     * @since 2.2
+     * @throws MathException if an error occurs generating the random value
+     */
+    @Override
+    public double sample() throws MathException {
+        return randomData.nextExponential(mean);
+    }
+
+    /**
+     * Access the domain value lower bound, based on <code>p</code>, used to
+     * bracket a CDF root.
+     *
+     * @param p the desired probability for the critical value
+     * @return domain value lower bound, i.e.
+     *         P(X &lt; <i>lower bound</i>) &lt; <code>p</code>
+     */
+    @Override
+    protected double getDomainLowerBound(double p) {
+        return 0;
+    }
+
+    /**
+     * Access the domain value upper bound, based on <code>p</code>, used to
+     * bracket a CDF root.
+     *
+     * @param p the desired probability for the critical value
+     * @return domain value upper bound, i.e.
+     *         P(X &lt; <i>upper bound</i>) &gt; <code>p</code>
+     */
+    @Override
+    protected double getDomainUpperBound(double p) {
+        // NOTE: exponential is skewed to the left
+        // NOTE: therefore, P(X < &mu;) > .5
+
+        if (p < .5) {
+            // use mean
+            return mean;
+        } else {
+            // use max
+            return Double.MAX_VALUE;
+        }
+    }
+
+    /**
+     * Access the initial domain value, based on <code>p</code>, used to
+     * bracket a CDF root.
+     *
+     * @param p the desired probability for the critical value
+     * @return initial domain value
+     */
+    @Override
+    protected double getInitialDomain(double p) {
+        // TODO: try to improve on this estimate
+        // TODO: what should really happen here is not derive from AbstractContinuousDistribution
+        // TODO: because the inverse cumulative distribution is simple.
+        // Exponential is skewed to the left, therefore, P(X < &mu;) > .5
+        if (p < .5) {
+            // use 1/2 mean
+            return mean * .5;
+        } else {
+            // use mean
+            return mean;
+        }
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * Returns the lower bound of the support for the distribution.
+     *
+     * The lower bound of the support is always 0, regardless of the mean.
+     *
+     * @return lower bound of the support (always 0)
+     * @since 2.2
+     */
+    public double getSupportLowerBound() {
+        return 0;
+    }
+
+    /**
+     * Returns the upper bound of the support for the distribution.
+     *
+     * The upper bound of the support is always positive infinity,
+     * regardless of the mean.
+     *
+     * @return upper bound of the support (always Double.POSITIVE_INFINITY)
+     * @since 2.2
+     */
+    public double getSupportUpperBound() {
+        return Double.POSITIVE_INFINITY;
+    }
+
+    /**
+     * Returns the mean of the distribution.
+     *
+     * For mean parameter <code>k</code>, the mean is
+     * <code>k</code>
+     *
+     * @return the mean
+     * @since 2.2
+     */
+    public double getNumericalMean() {
+        return getMean();
+    }
+
+    /**
+     * Returns the variance of the distribution.
+     *
+     * For mean parameter <code>k</code>, the variance is
+     * <code>k^2</code>
+     *
+     * @return the variance
+     * @since 2.2
+     */
+    public double getNumericalVariance() {
+        final double m = getMean();
+        return m * m;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/distribution/FDistribution.java b/src/main/java/org/apache/commons/math/distribution/FDistribution.java
new file mode 100644
index 0000000..51c33bf
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/FDistribution.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.math.distribution;
+
+/**
+ * F-Distribution.
+ *
+ * <p>
+ * References:
+ * <ul>
+ * <li><a href="http://mathworld.wolfram.com/F-Distribution.html">
+ * F-Distribution</a></li>
+ * </ul>
+ * </p>
+ *
+ * @version $Revision: 920852 $ $Date: 2010-03-09 13:53:44 +0100 (mar. 09 mars 2010) $
+ */
+public interface FDistribution extends ContinuousDistribution {
+    /**
+     * Modify the numerator degrees of freedom.
+     * @param degreesOfFreedom the new numerator degrees of freedom.
+     * @deprecated as of v2.1
+     */
+    @Deprecated
+    void setNumeratorDegreesOfFreedom(double degreesOfFreedom);
+
+    /**
+     * Access the numerator degrees of freedom.
+     * @return the numerator degrees of freedom.
+     */
+    double getNumeratorDegreesOfFreedom();
+
+    /**
+     * Modify the denominator degrees of freedom.
+     * @param degreesOfFreedom the new denominator degrees of freedom.
+     * @deprecated as of v2.1
+     */
+    @Deprecated
+    void setDenominatorDegreesOfFreedom(double degreesOfFreedom);
+
+    /**
+     * Access the denominator degrees of freedom.
+     * @return the denominator degrees of freedom.
+     */
+    double getDenominatorDegreesOfFreedom();
+}
diff --git a/src/main/java/org/apache/commons/math/distribution/FDistributionImpl.java b/src/main/java/org/apache/commons/math/distribution/FDistributionImpl.java
new file mode 100644
index 0000000..5b4049e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/FDistributionImpl.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.math.distribution;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.special.Beta;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Default implementation of
+ * {@link org.apache.commons.math.distribution.FDistribution}.
+ *
+ * @version $Revision: 1054524 $ $Date: 2011-01-03 05:59:18 +0100 (lun. 03 janv. 2011) $
+ */
+public class FDistributionImpl
+    extends AbstractContinuousDistribution
+    implements FDistribution, Serializable  {
+
+    /**
+     * 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 double numeratorDegreesOfFreedom;
+
+    /** The numerator degrees of freedom*/
+    private double denominatorDegreesOfFreedom;
+
+    /** Inverse cumulative probability accuracy */
+    private final double solverAbsoluteAccuracy;
+
+    /**
+     * Create a F distribution using the given degrees of freedom.
+     * @param numeratorDegreesOfFreedom the numerator degrees of freedom.
+     * @param denominatorDegreesOfFreedom the denominator degrees of freedom.
+     */
+    public FDistributionImpl(double numeratorDegreesOfFreedom,
+                             double denominatorDegreesOfFreedom) {
+        this(numeratorDegreesOfFreedom, denominatorDegreesOfFreedom, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+    }
+
+    /**
+     * Create a F distribution using the given degrees of freedom and inverse cumulative probability accuracy.
+     * @param numeratorDegreesOfFreedom the numerator degrees of freedom.
+     * @param denominatorDegreesOfFreedom the denominator 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 FDistributionImpl(double numeratorDegreesOfFreedom, double denominatorDegreesOfFreedom,
+            double inverseCumAccuracy) {
+        super();
+        setNumeratorDegreesOfFreedomInternal(numeratorDegreesOfFreedom);
+        setDenominatorDegreesOfFreedomInternal(denominatorDegreesOfFreedom);
+        solverAbsoluteAccuracy = inverseCumAccuracy;
+    }
+
+    /**
+     * Returns the probability density for a particular point.
+     *
+     * @param x The point at which the density should be computed.
+     * @return The pdf at point x.
+     * @since 2.1
+     */
+    @Override
+    public double density(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 FastMath.exp(nhalf*logn + nhalf*logx - logx + mhalf*logm - nhalf*lognxm -
+               mhalf*lognxm - Beta.logBeta(nhalf, mhalf));
+    }
+
+    /**
+     * For this distribution, X, this method returns P(X &lt; x).
+     *
+     * The implementation of this method is based on:
+     * <ul>
+     * <li>
+     * <a href="http://mathworld.wolfram.com/F-Distribution.html">
+     * F-Distribution</a>, equation (4).</li>
+     * </ul>
+     *
+     * @param x the value at which the CDF is evaluated.
+     * @return CDF for this distribution.
+     * @throws MathException if the cumulative probability can not be
+     *            computed due to convergence or other numerical errors.
+     */
+    public double cumulativeProbability(double x) throws MathException {
+        double ret;
+        if (x <= 0.0) {
+            ret = 0.0;
+        } else {
+            double n = numeratorDegreesOfFreedom;
+            double m = denominatorDegreesOfFreedom;
+
+            ret = Beta.regularizedBeta((n * x) / (m + n * x),
+                0.5 * n,
+                0.5 * m);
+        }
+        return ret;
+    }
+
+    /**
+     * For this distribution, X, this method returns the critical point x, such
+     * that P(X &lt; x) = <code>p</code>.
+     * <p>
+     * Returns 0 for p=0 and <code>Double.POSITIVE_INFINITY</code> for p=1.</p>
+     *
+     * @param p the desired probability
+     * @return x, such that P(X &lt; x) = <code>p</code>
+     * @throws MathException if the inverse cumulative probability can not be
+     *         computed due to convergence or other numerical errors.
+     * @throws IllegalArgumentException if <code>p</code> is not a valid
+     *         probability.
+     */
+    @Override
+    public double inverseCumulativeProbability(final double p)
+        throws MathException {
+        if (p == 0) {
+            return 0d;
+        }
+        if (p == 1) {
+            return Double.POSITIVE_INFINITY;
+        }
+        return super.inverseCumulativeProbability(p);
+    }
+
+    /**
+     * Access the domain value lower bound, based on <code>p</code>, used to
+     * bracket a CDF root.  This method is used by
+     * {@link #inverseCumulativeProbability(double)} to find critical values.
+     *
+     * @param p the desired probability for the critical value
+     * @return domain value lower bound, i.e.
+     *         P(X &lt; <i>lower bound</i>) &lt; <code>p</code>
+     */
+    @Override
+    protected double getDomainLowerBound(double p) {
+        return 0.0;
+    }
+
+    /**
+     * Access the domain value upper bound, based on <code>p</code>, used to
+     * bracket a CDF root.  This method is used by
+     * {@link #inverseCumulativeProbability(double)} to find critical values.
+     *
+     * @param p the desired probability for the critical value
+     * @return domain value upper bound, i.e.
+     *         P(X &lt; <i>upper bound</i>) &gt; <code>p</code>
+     */
+    @Override
+    protected double getDomainUpperBound(double p) {
+        return Double.MAX_VALUE;
+    }
+
+    /**
+     * Access the initial domain value, based on <code>p</code>, used to
+     * bracket a CDF root.  This method is used by
+     * {@link #inverseCumulativeProbability(double)} to find critical values.
+     *
+     * @param p the desired probability for the critical value
+     * @return initial domain value
+     */
+    @Override
+    protected double getInitialDomain(double p) {
+        double ret = 1.0;
+        double d = denominatorDegreesOfFreedom;
+        if (d > 2.0) {
+            // use mean
+            ret = d / (d - 2.0);
+        }
+        return ret;
+    }
+
+    /**
+     * Modify the numerator degrees of freedom.
+     * @param degreesOfFreedom the new numerator degrees of freedom.
+     * @throws IllegalArgumentException if <code>degreesOfFreedom</code> is not
+     *         positive.
+     * @deprecated as of 2.1 (class will become immutable in 3.0)
+     */
+    @Deprecated
+    public void setNumeratorDegreesOfFreedom(double degreesOfFreedom) {
+        setNumeratorDegreesOfFreedomInternal(degreesOfFreedom);
+    }
+
+    /**
+     * Modify the numerator degrees of freedom.
+     * @param degreesOfFreedom the new numerator degrees of freedom.
+     * @throws IllegalArgumentException if <code>degreesOfFreedom</code> is not
+     *         positive.
+     */
+    private void setNumeratorDegreesOfFreedomInternal(double degreesOfFreedom) {
+        if (degreesOfFreedom <= 0.0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.NOT_POSITIVE_DEGREES_OF_FREEDOM, degreesOfFreedom);
+        }
+        this.numeratorDegreesOfFreedom = degreesOfFreedom;
+    }
+
+    /**
+     * Access the numerator degrees of freedom.
+     * @return the numerator degrees of freedom.
+     */
+    public double getNumeratorDegreesOfFreedom() {
+        return numeratorDegreesOfFreedom;
+    }
+
+    /**
+     * Modify the denominator degrees of freedom.
+     * @param degreesOfFreedom the new denominator degrees of freedom.
+     * @throws IllegalArgumentException if <code>degreesOfFreedom</code> is not
+     *         positive.
+     * @deprecated as of 2.1 (class will become immutable in 3.0)
+     */
+    @Deprecated
+    public void setDenominatorDegreesOfFreedom(double degreesOfFreedom) {
+        setDenominatorDegreesOfFreedomInternal(degreesOfFreedom);
+    }
+
+    /**
+     * Modify the denominator degrees of freedom.
+     * @param degreesOfFreedom the new denominator degrees of freedom.
+     * @throws IllegalArgumentException if <code>degreesOfFreedom</code> is not
+     *         positive.
+     */
+    private void setDenominatorDegreesOfFreedomInternal(double degreesOfFreedom) {
+        if (degreesOfFreedom <= 0.0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.NOT_POSITIVE_DEGREES_OF_FREEDOM, degreesOfFreedom);
+        }
+        this.denominatorDegreesOfFreedom = degreesOfFreedom;
+    }
+
+    /**
+     * Access the denominator degrees of freedom.
+     * @return the denominator degrees of freedom.
+     */
+    public double getDenominatorDegreesOfFreedom() {
+        return denominatorDegreesOfFreedom;
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * Returns the lower bound of the support for the distribution.
+     *
+     * The lower bound of the support is always 0, regardless of the parameters.
+     *
+     * @return lower bound of the support (always 0)
+     * @since 2.2
+     */
+    public double getSupportLowerBound() {
+        return 0;
+    }
+
+    /**
+     * Returns the upper bound of the support for the distribution.
+     *
+     * The upper bound of the support is always positive infinity,
+     * regardless of the parameters.
+     *
+     * @return upper bound of the support (always Double.POSITIVE_INFINITY)
+     * @since 2.2
+     */
+    public double getSupportUpperBound() {
+        return Double.POSITIVE_INFINITY;
+    }
+
+    /**
+     * Returns the mean of the distribution.
+     *
+     * For denominator degrees of freedom parameter <code>b</code>,
+     * the mean is
+     * <ul>
+     *  <li>if <code>b &gt; 2</code> then <code>b / (b - 2)</code></li>
+     *  <li>else <code>undefined</code>
+     * </ul>
+     *
+     * @return the mean
+     * @since 2.2
+     */
+    public double getNumericalMean() {
+        final double denominatorDF = getDenominatorDegreesOfFreedom();
+
+        if (denominatorDF > 2) {
+            return denominatorDF / (denominatorDF - 2);
+        }
+
+        return Double.NaN;
+    }
+
+    /**
+     * Returns the variance of the distribution.
+     *
+     * For numerator degrees of freedom parameter <code>a</code>
+     * and denominator degrees of freedom parameter <code>b</code>,
+     * the variance is
+     * <ul>
+     *  <li>
+     *    if <code>b &gt; 4</code> then
+     *    <code>[ 2 * b^2 * (a + b - 2) ] / [ a * (b - 2)^2 * (b - 4) ]</code>
+     *  </li>
+     *  <li>else <code>undefined</code>
+     * </ul>
+     *
+     * @return the variance
+     * @since 2.2
+     */
+    public double getNumericalVariance() {
+        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;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/distribution/GammaDistribution.java b/src/main/java/org/apache/commons/math/distribution/GammaDistribution.java
new file mode 100644
index 0000000..71f8f78
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/GammaDistribution.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.math.distribution;
+
+/**
+ * The Gamma Distribution.
+ *
+ * <p>
+ * References:
+ * <ul>
+ * <li><a href="http://mathworld.wolfram.com/GammaDistribution.html">
+ * Gamma Distribution</a></li>
+ * </ul>
+ * </p>
+ *
+ * @version $Revision: 920852 $ $Date: 2010-03-09 13:53:44 +0100 (mar. 09 mars 2010) $
+ */
+public interface GammaDistribution extends ContinuousDistribution, HasDensity<Double> {
+    /**
+     * Modify the shape parameter, alpha.
+     * @param alpha the new shape parameter.
+     * @deprecated as of v2.1
+     */
+    @Deprecated
+    void setAlpha(double alpha);
+
+    /**
+     * Access the shape parameter, alpha
+     * @return alpha.
+     */
+    double getAlpha();
+
+    /**
+     * Modify the scale parameter, beta.
+     * @param beta the new scale parameter.
+     * @deprecated as of v2.1
+     */
+    @Deprecated
+    void setBeta(double beta);
+
+    /**
+     * Access the scale parameter, beta
+     * @return beta.
+     */
+    double getBeta();
+
+    /**
+     * Return the probability density for a particular point.
+     * @param x  The point at which the density should be computed.
+     * @return  The pdf at point x.
+     */
+    double density(Double x);
+}
diff --git a/src/main/java/org/apache/commons/math/distribution/GammaDistributionImpl.java b/src/main/java/org/apache/commons/math/distribution/GammaDistributionImpl.java
new file mode 100644
index 0000000..a187892
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/GammaDistributionImpl.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.math.distribution;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.special.Gamma;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * The default implementation of {@link GammaDistribution}.
+ *
+ * @version $Revision: 1054524 $ $Date: 2011-01-03 05:59:18 +0100 (lun. 03 janv. 2011) $
+ */
+public class GammaDistributionImpl extends AbstractContinuousDistribution
+    implements GammaDistribution, Serializable  {
+
+    /**
+     * 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 = -3239549463135430361L;
+
+    /** The shape parameter. */
+    private double alpha;
+
+    /** The scale parameter. */
+    private double beta;
+
+    /** Inverse cumulative probability accuracy */
+    private final double solverAbsoluteAccuracy;
+
+    /**
+     * Create a new gamma distribution with the given alpha and beta values.
+     * @param alpha the shape parameter.
+     * @param beta the scale parameter.
+     */
+    public GammaDistributionImpl(double alpha, double beta) {
+        this(alpha, beta, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+    }
+
+    /**
+     * Create a new gamma distribution with the given alpha and beta values.
+     * @param alpha the shape parameter.
+     * @param beta the scale parameter.
+     * @param inverseCumAccuracy the maximum absolute error in inverse cumulative probability estimates
+     * (defaults to {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY})
+     * @since 2.1
+     */
+    public GammaDistributionImpl(double alpha, double beta, double inverseCumAccuracy) {
+        super();
+        setAlphaInternal(alpha);
+        setBetaInternal(beta);
+        solverAbsoluteAccuracy = inverseCumAccuracy;
+    }
+
+    /**
+     * For this distribution, X, this method returns P(X &lt; x).
+     *
+     * 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>
+     * <li>Casella, G., & Berger, R. (1990). <i>Statistical Inference</i>.
+     * Belmont, CA: Duxbury Press.</li>
+     * </ul>
+     *
+     * @param x the value at which the CDF is evaluated.
+     * @return CDF for this distribution.
+     * @throws MathException if the cumulative probability can not be
+     *            computed due to convergence or other numerical errors.
+     */
+    public double cumulativeProbability(double x) throws MathException{
+        double ret;
+
+        if (x <= 0.0) {
+            ret = 0.0;
+        } else {
+            ret = Gamma.regularizedGammaP(alpha, x / beta);
+        }
+
+        return ret;
+    }
+
+    /**
+     * For this distribution, X, this method returns the critical point x, such
+     * that P(X &lt; x) = <code>p</code>.
+     * <p>
+     * Returns 0 for p=0 and <code>Double.POSITIVE_INFINITY</code> for p=1.</p>
+     *
+     * @param p the desired probability
+     * @return x, such that P(X &lt; x) = <code>p</code>
+     * @throws MathException if the inverse cumulative probability can not be
+     *         computed due to convergence or other numerical errors.
+     * @throws IllegalArgumentException if <code>p</code> is not a valid
+     *         probability.
+     */
+    @Override
+    public double inverseCumulativeProbability(final double p)
+    throws MathException {
+        if (p == 0) {
+            return 0d;
+        }
+        if (p == 1) {
+            return Double.POSITIVE_INFINITY;
+        }
+        return super.inverseCumulativeProbability(p);
+    }
+
+    /**
+     * Modify the shape parameter, alpha.
+     * @param alpha the new shape parameter.
+     * @throws IllegalArgumentException if <code>alpha</code> is not positive.
+     * @deprecated as of 2.1 (class will become immutable in 3.0)
+     */
+    @Deprecated
+    public void setAlpha(double alpha) {
+        setAlphaInternal(alpha);
+    }
+
+    /**
+     * Modify the shape parameter, alpha.
+     * @param newAlpha the new shape parameter.
+     * @throws IllegalArgumentException if <code>newAlpha</code> is not positive.
+     */
+    private void setAlphaInternal(double newAlpha) {
+        if (newAlpha <= 0.0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.NOT_POSITIVE_ALPHA,
+                  newAlpha);
+        }
+        this.alpha = newAlpha;
+    }
+
+    /**
+     * Access the shape parameter, alpha
+     * @return alpha.
+     */
+    public double getAlpha() {
+        return alpha;
+    }
+
+    /**
+     * Modify the scale parameter, beta.
+     * @param newBeta the new scale parameter.
+     * @throws IllegalArgumentException if <code>newBeta</code> is not positive.
+     * @deprecated as of 2.1 (class will become immutable in 3.0)
+     */
+    @Deprecated
+    public void setBeta(double newBeta) {
+        setBetaInternal(newBeta);
+    }
+
+    /**
+     * Modify the scale parameter, beta.
+     * @param newBeta the new scale parameter.
+     * @throws IllegalArgumentException if <code>newBeta</code> is not positive.
+     */
+    private void setBetaInternal(double newBeta) {
+        if (newBeta <= 0.0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.NOT_POSITIVE_BETA,
+                  newBeta);
+        }
+        this.beta = newBeta;
+    }
+
+    /**
+     * Access the scale parameter, beta
+     * @return beta.
+     */
+    public double getBeta() {
+        return beta;
+    }
+
+    /**
+     * Returns the probability density for a particular point.
+     *
+     * @param x The point at which the density should be computed.
+     * @return The pdf at point x.
+     */
+    @Override
+    public double density(double x) {
+        if (x < 0) return 0;
+        return FastMath.pow(x / beta, alpha - 1) / beta * FastMath.exp(-x / beta) / FastMath.exp(Gamma.logGamma(alpha));
+    }
+
+    /**
+     * Return the probability density for a particular point.
+     *
+     * @param x The point at which the density should be computed.
+     * @return The pdf at point x.
+     * @deprecated
+     */
+    @Deprecated
+    public double density(Double x) {
+        return density(x.doubleValue());
+    }
+
+    /**
+     * Access the domain value lower bound, based on <code>p</code>, used to
+     * bracket a CDF root.  This method is used by
+     * {@link #inverseCumulativeProbability(double)} to find critical values.
+     *
+     * @param p the desired probability for the critical value
+     * @return domain value lower bound, i.e.
+     *         P(X &lt; <i>lower bound</i>) &lt; <code>p</code>
+     */
+    @Override
+    protected double getDomainLowerBound(double p) {
+        // TODO: try to improve on this estimate
+        return Double.MIN_VALUE;
+    }
+
+    /**
+     * Access the domain value upper bound, based on <code>p</code>, used to
+     * bracket a CDF root.  This method is used by
+     * {@link #inverseCumulativeProbability(double)} to find critical values.
+     *
+     * @param p the desired probability for the critical value
+     * @return domain value upper bound, i.e.
+     *         P(X &lt; <i>upper bound</i>) &gt; <code>p</code>
+     */
+    @Override
+    protected double getDomainUpperBound(double p) {
+        // TODO: try to improve on this estimate
+        // NOTE: gamma is skewed to the left
+        // NOTE: therefore, P(X < &mu;) > .5
+
+        double ret;
+
+        if (p < .5) {
+            // use mean
+            ret = alpha * beta;
+        } else {
+            // use max value
+            ret = Double.MAX_VALUE;
+        }
+
+        return ret;
+    }
+
+    /**
+     * Access the initial domain value, based on <code>p</code>, used to
+     * bracket a CDF root.  This method is used by
+     * {@link #inverseCumulativeProbability(double)} to find critical values.
+     *
+     * @param p the desired probability for the critical value
+     * @return initial domain value
+     */
+    @Override
+    protected double getInitialDomain(double p) {
+        // TODO: try to improve on this estimate
+        // Gamma is skewed to the left, therefore, P(X < &mu;) > .5
+
+        double ret;
+
+        if (p < .5) {
+            // use 1/2 mean
+            ret = alpha * beta * .5;
+        } else {
+            // use mean
+            ret = alpha * beta;
+        }
+
+        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;
+    }
+
+    /**
+     * Returns the upper bound of the support for the distribution.
+     *
+     * The lower bound of the support is always 0, regardless of the parameters.
+     *
+     * @return lower bound of the support (always 0)
+     * @since 2.2
+     */
+    public double getSupportLowerBound() {
+        return 0;
+    }
+
+    /**
+     * Returns the upper bound of the support for the distribution.
+     *
+     * The upper bound of the support is always positive infinity,
+     * regardless of the parameters.
+     *
+     * @return upper bound of the support (always Double.POSITIVE_INFINITY)
+     * @since 2.2
+     */
+    public double getSupportUpperBound() {
+        return Double.POSITIVE_INFINITY;
+    }
+
+    /**
+     * Returns the mean.
+     *
+     * For shape parameter <code>alpha</code> and scale
+     * parameter <code>beta</code>, the mean is
+     * <code>alpha * beta</code>
+     *
+     * @return the mean
+     * @since 2.2
+     */
+    public double getNumericalMean() {
+        return getAlpha() * getBeta();
+    }
+
+    /**
+     * Returns the variance.
+     *
+     * For shape parameter <code>alpha</code> and scale
+     * parameter <code>beta</code>, the variance is
+     * <code>alpha * beta^2</code>
+     *
+     * @return the variance
+     * @since 2.2
+     */
+    public double getNumericalVariance() {
+        final double b = getBeta();
+        return getAlpha() * b * b;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/distribution/HasDensity.java b/src/main/java/org/apache/commons/math/distribution/HasDensity.java
new file mode 100644
index 0000000..0e15c6c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/HasDensity.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.math.distribution;
+
+import org.apache.commons.math.MathException;
+
+/**
+ * <p>Interface that signals that a distribution can compute the probability density function
+ * for a particular point.
+ * @param <P> the type of the point at which density is to be computed, this
+ * may be for example <code>Double.</code></p>
+ *
+ * <p>This interface is deprecated.  As of version 2.0, the {@link ContinuousDistribution}
+ * interface will be extended to include a <code>density(double)<code> method.</p>
+ *
+ * @deprecated to be removed in math 3.0
+ * @version $Revision: 1042336 $ $Date: 2010-12-05 13:40:48 +0100 (dim. 05 déc. 2010) $
+ */
+@Deprecated
+public interface HasDensity<P> {
+
+    /**
+     * Compute the probability density function.
+     * @param x point for which the probability density is requested
+     * @return probability density at point x
+     * @throws MathException if probability density cannot be computed at specifed point
+     */
+    double density(P x) throws MathException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/distribution/HypergeometricDistribution.java b/src/main/java/org/apache/commons/math/distribution/HypergeometricDistribution.java
new file mode 100644
index 0000000..d3595e0
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/HypergeometricDistribution.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.math.distribution;
+
+/**
+ * The Hypergeometric Distribution.
+ *
+ * <p>
+ * References:
+ * <ul>
+ * <li><a href="http://mathworld.wolfram.com/HypergeometricDistribution.html">
+ * Hypergeometric Distribution</a></li>
+ * </ul>
+ * </p>
+ *
+ * @version $Revision: 920852 $ $Date: 2010-03-09 13:53:44 +0100 (mar. 09 mars 2010) $
+ */
+public interface HypergeometricDistribution extends IntegerDistribution {
+
+    /**
+     * Access the number of successes.
+     * @return the number of successes.
+     */
+    int getNumberOfSuccesses();
+
+    /**
+     * Access the population size.
+     * @return the population size.
+     */
+    int getPopulationSize();
+
+    /**
+     * Access the sample size.
+     * @return the sample size.
+     */
+    int getSampleSize();
+
+    /**
+     * Modify the number of successes.
+     * @param num the new number of successes.
+     * @deprecated as of v2.1
+     */
+    @Deprecated
+    void setNumberOfSuccesses(int num);
+
+    /**
+     * Modify the population size.
+     * @param size the new population size.
+     * @deprecated as of v2.1
+     */
+    @Deprecated
+    void setPopulationSize(int size);
+
+    /**
+     * Modify the sample size.
+     * @param size the new sample size.
+     * @deprecated as of v2.1
+     */
+    @Deprecated
+    void setSampleSize(int size);
+}
diff --git a/src/main/java/org/apache/commons/math/distribution/HypergeometricDistributionImpl.java b/src/main/java/org/apache/commons/math/distribution/HypergeometricDistributionImpl.java
new file mode 100644
index 0000000..f9dff2d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/HypergeometricDistributionImpl.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.math.distribution;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.util.MathUtils;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * The default implementation of {@link HypergeometricDistribution}.
+ *
+ * @version $Revision: 1054524 $ $Date: 2011-01-03 05:59:18 +0100 (lun. 03 janv. 2011) $
+ */
+public class HypergeometricDistributionImpl extends AbstractIntegerDistribution
+        implements HypergeometricDistribution, Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -436928820673516179L;
+
+    /** The number of successes in the population. */
+    private int numberOfSuccesses;
+
+    /** The population size. */
+    private int populationSize;
+
+    /** The sample size. */
+    private int sampleSize;
+
+    /**
+     * Construct a new hypergeometric distribution with the given the population
+     * size, the number of successes in the population, and the sample size.
+     *
+     * @param populationSize the population size.
+     * @param numberOfSuccesses number of successes in the population.
+     * @param sampleSize the sample size.
+     */
+    public HypergeometricDistributionImpl(int populationSize,
+            int numberOfSuccesses, int sampleSize) {
+        super();
+        if (numberOfSuccesses > populationSize) {
+            throw MathRuntimeException
+                    .createIllegalArgumentException(
+                            LocalizedFormats.NUMBER_OF_SUCCESS_LARGER_THAN_POPULATION_SIZE,
+                            numberOfSuccesses, populationSize);
+        }
+        if (sampleSize > populationSize) {
+            throw MathRuntimeException
+                    .createIllegalArgumentException(
+                            LocalizedFormats.SAMPLE_SIZE_LARGER_THAN_POPULATION_SIZE,
+                            sampleSize, populationSize);
+        }
+
+        setPopulationSizeInternal(populationSize);
+        setSampleSizeInternal(sampleSize);
+        setNumberOfSuccessesInternal(numberOfSuccesses);
+    }
+
+    /**
+     * For this distribution, X, this method returns P(X &le; x).
+     *
+     * @param x the value at which the PDF is evaluated.
+     * @return PDF for this distribution.
+     */
+    @Override
+    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, populationSize,
+                                             numberOfSuccesses, sampleSize);
+        }
+
+        return ret;
+    }
+
+    /**
+     * Return the domain for the given hypergeometric distribution parameters.
+     *
+     * @param n the population size.
+     * @param m number of successes in the population.
+     * @param k the 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) };
+    }
+
+    /**
+     * Access the domain value lower bound, based on <code>p</code>, used to
+     * bracket a PDF root.
+     *
+     * @param p the desired probability for the critical value
+     * @return domain value lower bound, i.e. P(X &lt; <i>lower bound</i>) &lt;
+     *         <code>p</code>
+     */
+    @Override
+    protected int getDomainLowerBound(double p) {
+        return getLowerDomain(populationSize, numberOfSuccesses, sampleSize);
+    }
+
+    /**
+     * Access the domain value upper bound, based on <code>p</code>, used to
+     * bracket a PDF root.
+     *
+     * @param p the desired probability for the critical value
+     * @return domain value upper bound, i.e. P(X &lt; <i>upper bound</i>) &gt;
+     *         <code>p</code>
+     */
+    @Override
+    protected int getDomainUpperBound(double p) {
+        return getUpperDomain(sampleSize, numberOfSuccesses);
+    }
+
+    /**
+     * Return the lowest domain value for the given hypergeometric distribution
+     * parameters.
+     *
+     * @param n the population size.
+     * @param m number of successes in the population.
+     * @param k the 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 the sample size.
+     * @return the highest domain value of the hypergeometric distribution.
+     */
+    private int getUpperDomain(int m, int k) {
+        return FastMath.min(k, m);
+    }
+
+    /**
+     * For this distribution, X, this method returns P(X = x).
+     *
+     * @param x the value at which the PMF is evaluated.
+     * @return PMF for this distribution.
+     */
+    public double probability(int x) {
+        double ret;
+
+        int[] domain = getDomain(populationSize, numberOfSuccesses, sampleSize);
+        if (x < domain[0] || x > domain[1]) {
+            ret = 0.0;
+        } 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 = FastMath.exp(p1 + p2 - p3);
+        }
+
+        return ret;
+    }
+
+    /**
+     * For the distribution, X, defined by the given hypergeometric distribution
+     * parameters, this method returns P(X = x).
+     *
+     * @param n the population size.
+     * @param m number of successes in the population.
+     * @param k the sample size.
+     * @param x the value at which the PMF is evaluated.
+     * @return PMF for the distribution.
+     */
+    private double probability(int n, int m, int k, int x) {
+        return FastMath.exp(MathUtils.binomialCoefficientLog(m, x) +
+               MathUtils.binomialCoefficientLog(n - m, k - x) -
+               MathUtils.binomialCoefficientLog(n, k));
+    }
+
+    /**
+     * Modify the number of successes.
+     *
+     * @param num the new number of successes.
+     * @throws IllegalArgumentException if <code>num</code> is negative.
+     * @deprecated as of 2.1 (class will become immutable in 3.0)
+     */
+    @Deprecated
+    public void setNumberOfSuccesses(int num) {
+        setNumberOfSuccessesInternal(num);
+    }
+
+    /**
+     * Modify the number of successes.
+     *
+     * @param num the new number of successes.
+     * @throws IllegalArgumentException if <code>num</code> is negative.
+     */
+    private void setNumberOfSuccessesInternal(int num) {
+        if (num < 0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.NEGATIVE_NUMBER_OF_SUCCESSES, num);
+        }
+        numberOfSuccesses = num;
+    }
+
+    /**
+     * Modify the population size.
+     *
+     * @param size the new population size.
+     * @throws IllegalArgumentException if <code>size</code> is not positive.
+     * @deprecated as of 2.1 (class will become immutable in 3.0)
+     */
+    @Deprecated
+    public void setPopulationSize(int size) {
+        setPopulationSizeInternal(size);
+    }
+
+    /**
+     * Modify the population size.
+     *
+     * @param size the new population size.
+     * @throws IllegalArgumentException if <code>size</code> is not positive.
+     */
+    private void setPopulationSizeInternal(int size) {
+        if (size <= 0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.NOT_POSITIVE_POPULATION_SIZE, size);
+        }
+        populationSize = size;
+    }
+
+    /**
+     * Modify the sample size.
+     *
+     * @param size the new sample size.
+     * @throws IllegalArgumentException if <code>size</code> is negative.
+     * @deprecated as of 2.1 (class will become immutable in 3.0)
+     */
+    @Deprecated
+    public void setSampleSize(int size) {
+        setSampleSizeInternal(size);
+    }
+    /**
+     * Modify the sample size.
+     *
+     * @param size the new sample size.
+     * @throws IllegalArgumentException if <code>size</code> is negative.
+     */
+    private void setSampleSizeInternal(int size) {
+        if (size < 0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.NOT_POSITIVE_SAMPLE_SIZE, size);
+        }
+        sampleSize = size;
+    }
+
+    /**
+     * For this distribution, X, this method returns P(X &ge; x).
+     *
+     * @param x the value at which the CDF is evaluated.
+     * @return 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, populationSize, numberOfSuccesses, sampleSize);
+        }
+
+        return ret;
+    }
+
+    /**
+     * For this distribution, X, this method returns P(x0 &le; X &le; x1). This
+     * probability is computed by summing the point probabilities for the values
+     * x0, x0 + 1, x0 + 2, ..., x1, in the order directed by dx.
+     *
+     * @param x0 the inclusive, lower bound
+     * @param x1 the inclusive, upper bound
+     * @param dx the direction of summation. 1 indicates summing from x0 to x1.
+     *            0 indicates summing from x1 to x0.
+     * @param n the population size.
+     * @param m number of successes in the population.
+     * @param k the sample size.
+     * @return P(x0 &le; X &le; x1).
+     */
+    private double innerCumulativeProbability(int x0, int x1, int dx, int n,
+            int m, int k) {
+        double ret = probability(n, m, k, x0);
+        while (x0 != x1) {
+            x0 += dx;
+            ret += probability(n, m, k, x0);
+        }
+        return ret;
+    }
+
+    /**
+     * Returns the lower bound for the support for the distribution.
+     *
+     * For population size <code>N</code>,
+     * number of successes <code>m</code>, and
+     * sample size <code>n</code>,
+     * the lower bound of the support is
+     * <code>max(0, n + m - N)</code>
+     *
+     * @return lower bound of the support
+     * @since 2.2
+     */
+    public int getSupportLowerBound() {
+        return FastMath.max(0,
+                getSampleSize() + getNumberOfSuccesses() - getPopulationSize());
+    }
+
+    /**
+     * Returns the upper bound for the support of the distribution.
+     *
+     * For number of successes <code>m</code> and
+     * sample size <code>n</code>,
+     * the upper bound of the support is
+     * <code>min(m, n)</code>
+     *
+     * @return upper bound of the support
+     * @since 2.2
+     */
+    public int getSupportUpperBound() {
+        return FastMath.min(getNumberOfSuccesses(), getSampleSize());
+    }
+
+    /**
+     * Returns the mean.
+     *
+     * For population size <code>N</code>,
+     * number of successes <code>m</code>, and
+     * sample size <code>n</code>, the mean is
+     * <code>n * m / N</code>
+     *
+     * @return the mean
+     * @since 2.2
+     */
+    protected double getNumericalMean() {
+        return (double)(getSampleSize() * getNumberOfSuccesses()) / (double)getPopulationSize();
+    }
+
+    /**
+     * Returns the variance.
+     *
+     * For population size <code>N</code>,
+     * number of successes <code>m</code>, and
+     * sample size <code>n</code>, the variance is
+     * <code>[ n * m * (N - n) * (N - m) ] / [ N^2 * (N - 1) ]</code>
+     *
+     * @return the variance
+     * @since 2.2
+     */
+    public double getNumericalVariance() {
+        final double N = getPopulationSize();
+        final double m = getNumberOfSuccesses();
+        final double n = getSampleSize();
+        return ( n * m * (N - n) * (N - m) ) / ( (N*N * (N - 1)) );
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/distribution/IntegerDistribution.java b/src/main/java/org/apache/commons/math/distribution/IntegerDistribution.java
new file mode 100644
index 0000000..096af17
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/IntegerDistribution.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.math.distribution;
+
+import org.apache.commons.math.MathException;
+
+/**
+ * Interface for discrete distributions of integer-valued random variables.
+ *
+ * @version $Revision: 949535 $ $Date: 2010-05-30 19:00:15 +0200 (dim. 30 mai 2010) $
+ */
+public interface IntegerDistribution extends DiscreteDistribution {
+    /**
+     * For a random variable X whose values are distributed according
+     * to this distribution, this method returns P(X = x). In other words, this
+     * method represents the probability mass function for the distribution.
+     *
+     * @param x the value at which the probability density function is evaluated.
+     * @return the value of the probability density function at x
+     */
+    double probability(int x);
+
+    /**
+     * For a random variable X whose values are distributed according
+     * to this distribution, this method returns P(X &le; x).  In other words,
+     * this method represents the probability distribution function, or PDF
+     * for the distribution.
+     *
+     * @param x the value at which the PDF is evaluated.
+     * @return PDF for this distribution.
+     * @throws MathException if the cumulative probability can not be
+     *            computed due to convergence or other numerical errors.
+     */
+    double cumulativeProbability(int x) throws MathException;
+
+    /**
+     * For this distribution, X, this method returns P(x0 &le; X &le; x1).
+     * @param x0 the inclusive, lower bound
+     * @param x1 the inclusive, upper bound
+     * @return the cumulative probability.
+     * @throws MathException if the cumulative probability can not be
+     *            computed due to convergence or other numerical errors.
+     * @throws IllegalArgumentException if x0 > x1
+     */
+    double cumulativeProbability(int x0, int x1) throws MathException;
+
+    /**
+     * For this distribution, X, this method returns the largest x such that
+     * P(X &le; x) <= p.
+     * <p>
+     * Note that this definition implies: <ul>
+     * <li> If there is a minimum value, <code>m</code>, with positive
+     * probability under (the density of) X, then <code>m - 1</code> is
+     * returned by <code>inverseCumulativeProbability(0).</code>  If there is
+     * no such value <code>m,  Integer.MIN_VALUE</code> is
+     * returned.</li>
+     * <li> If there is a maximum value, <code>M</code>, such that
+     * P(X &le; M) =1, then <code>M</code> is returned by
+     * <code>inverseCumulativeProbability(1).</code>
+     * If there is no such value, <code>M, Integer.MAX_VALUE</code> is
+     * returned.</li></ul></p>
+     *
+     * @param p the cumulative probability.
+     * @return the largest x such that P(X &le; x) <= p
+     * @throws MathException if the inverse cumulative probability can not be
+     *            computed due to convergence or other numerical errors.
+     * @throws IllegalArgumentException if p is not between 0 and 1 (inclusive)
+     */
+    int inverseCumulativeProbability(double p) throws MathException;
+}
diff --git a/src/main/java/org/apache/commons/math/distribution/NormalDistribution.java b/src/main/java/org/apache/commons/math/distribution/NormalDistribution.java
new file mode 100644
index 0000000..67f5d5c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/NormalDistribution.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.math.distribution;
+
+/**
+ * Normal (Gauss) Distribution.
+ *
+ * <p>
+ * References:</p><p>
+ * <ul>
+ * <li><a href="http://mathworld.wolfram.com/NormalDistribution.html">
+ * Normal Distribution</a></li>
+ * </ul>
+ * </p>
+ *
+ * @version $Revision: 920852 $ $Date: 2010-03-09 13:53:44 +0100 (mar. 09 mars 2010) $
+ */
+public interface NormalDistribution extends ContinuousDistribution, HasDensity<Double> {
+    /**
+     * Access the mean.
+     * @return mean for this distribution
+     */
+    double getMean();
+    /**
+     * Modify the mean.
+     * @param mean for this distribution
+     * @deprecated as of v2.1
+     */
+    @Deprecated
+    void setMean(double mean);
+    /**
+     * Access the standard deviation.
+     * @return standard deviation for this distribution
+     */
+    double getStandardDeviation();
+    /**
+     * Modify the standard deviation.
+     * @param sd standard deviation for this distribution
+     * @deprecated as of v2.1
+     */
+    @Deprecated
+    void setStandardDeviation(double sd);
+
+    /**
+     * Return the probability density for a particular point.
+     * @param x  The point at which the density should be computed.
+     * @return  The pdf at point x.
+     */
+    double density(Double x);
+}
diff --git a/src/main/java/org/apache/commons/math/distribution/NormalDistributionImpl.java b/src/main/java/org/apache/commons/math/distribution/NormalDistributionImpl.java
new file mode 100644
index 0000000..1164649
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/NormalDistributionImpl.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.math.distribution;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.special.Erf;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Default implementation of
+ * {@link org.apache.commons.math.distribution.NormalDistribution}.
+ *
+ * @version $Revision: 1054524 $ $Date: 2011-01-03 05:59:18 +0100 (lun. 03 janv. 2011) $
+ */
+public class NormalDistributionImpl extends AbstractContinuousDistribution
+        implements NormalDistribution, Serializable {
+
+    /**
+     * 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;
+
+    /** &sqrt;(2 &pi;) */
+    private static final double SQRT2PI = FastMath.sqrt(2 * FastMath.PI);
+
+    /** The mean of this distribution. */
+    private double mean = 0;
+
+    /** The standard deviation of this distribution. */
+    private double standardDeviation = 1;
+
+    /** Inverse cumulative probability accuracy */
+    private final double solverAbsoluteAccuracy;
+
+    /**
+     * Create a normal distribution using the given mean and standard deviation.
+     * @param mean mean for this distribution
+     * @param sd standard deviation for this distribution
+     */
+    public NormalDistributionImpl(double mean, double sd){
+        this(mean, sd, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+    }
+
+    /**
+     * Create a normal distribution using the given mean, standard deviation and
+     * inverse cumulative distribution accuracy.
+     *
+     * @param mean mean for this distribution
+     * @param sd standard deviation for this distribution
+     * @param inverseCumAccuracy inverse cumulative probability accuracy
+     * @since 2.1
+     */
+    public NormalDistributionImpl(double mean, double sd, double inverseCumAccuracy) {
+        super();
+        setMeanInternal(mean);
+        setStandardDeviationInternal(sd);
+        solverAbsoluteAccuracy = inverseCumAccuracy;
+    }
+
+    /**
+     * Creates normal distribution with the mean equal to zero and standard
+     * deviation equal to one.
+     */
+    public NormalDistributionImpl(){
+        this(0.0, 1.0);
+    }
+
+    /**
+     * Access the mean.
+     * @return mean for this distribution
+     */
+    public double getMean() {
+        return mean;
+    }
+
+    /**
+     * Modify the mean.
+     * @param mean for this distribution
+     * @deprecated as of 2.1 (class will become immutable in 3.0)
+     */
+    @Deprecated
+    public void setMean(double mean) {
+        setMeanInternal(mean);
+    }
+
+    /**
+     * Modify the mean.
+     * @param newMean for this distribution
+     */
+    private void setMeanInternal(double newMean) {
+        this.mean = newMean;
+    }
+
+    /**
+     * Access the standard deviation.
+     * @return standard deviation for this distribution
+     */
+    public double getStandardDeviation() {
+        return standardDeviation;
+    }
+
+    /**
+     * Modify the standard deviation.
+     * @param sd standard deviation for this distribution
+     * @throws IllegalArgumentException if <code>sd</code> is not positive.
+     * @deprecated as of 2.1 (class will become immutable in 3.0)
+     */
+    @Deprecated
+    public void setStandardDeviation(double sd) {
+        setStandardDeviationInternal(sd);
+    }
+
+    /**
+     * Modify the standard deviation.
+     * @param sd standard deviation for this distribution
+     * @throws IllegalArgumentException if <code>sd</code> is not positive.
+     */
+    private void setStandardDeviationInternal(double sd) {
+        if (sd <= 0.0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.NOT_POSITIVE_STANDARD_DEVIATION,
+                  sd);
+        }
+        standardDeviation = sd;
+    }
+
+    /**
+     * Return the probability density for a particular point.
+     *
+     * @param x The point at which the density should be computed.
+     * @return The pdf at point x.
+     * @deprecated
+     */
+    @Deprecated
+    public double density(Double x) {
+        return density(x.doubleValue());
+    }
+
+    /**
+     * Returns the probability density for a particular point.
+     *
+     * @param x The point at which the density should be computed.
+     * @return The pdf at point x.
+     * @since 2.1
+     */
+    @Override
+    public double density(double x) {
+        double x0 = x - mean;
+        return FastMath.exp(-x0 * x0 / (2 * standardDeviation * standardDeviation)) / (standardDeviation * SQRT2PI);
+    }
+
+    /**
+     * For this distribution, X, this method returns P(X &lt; <code>x</code>).
+     * If <code>x</code>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</code> of 0 or 1.
+     *
+     * @param x the value at which the CDF is evaluated.
+     * @return CDF evaluated at <code>x</code>.
+     * @throws MathException if the algorithm fails to converge
+     */
+    public double cumulativeProbability(double x) throws MathException {
+        final double dev = x - mean;
+        if (FastMath.abs(dev) > 40 * standardDeviation) {
+            return dev < 0 ? 0.0d : 1.0d;
+        }
+        return 0.5 * (1.0 + Erf.erf(dev /
+                    (standardDeviation * FastMath.sqrt(2.0))));
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * For this distribution, X, this method returns the critical point x, such
+     * that P(X &lt; x) = <code>p</code>.
+     * <p>
+     * Returns <code>Double.NEGATIVE_INFINITY</code> for p=0 and
+     * <code>Double.POSITIVE_INFINITY</code> for p=1.</p>
+     *
+     * @param p the desired probability
+     * @return x, such that P(X &lt; x) = <code>p</code>
+     * @throws MathException if the inverse cumulative probability can not be
+     *         computed due to convergence or other numerical errors.
+     * @throws IllegalArgumentException if <code>p</code> is not a valid
+     *         probability.
+     */
+    @Override
+    public double inverseCumulativeProbability(final double p)
+    throws MathException {
+        if (p == 0) {
+            return Double.NEGATIVE_INFINITY;
+        }
+        if (p == 1) {
+            return Double.POSITIVE_INFINITY;
+        }
+        return super.inverseCumulativeProbability(p);
+    }
+
+    /**
+     * Generates a random value sampled from this distribution.
+     *
+     * @return random value
+     * @since 2.2
+     * @throws MathException if an error occurs generating the random value
+     */
+    @Override
+    public double sample() throws MathException {
+        return randomData.nextGaussian(mean, standardDeviation);
+    }
+
+    /**
+     * Access the domain value lower bound, based on <code>p</code>, used to
+     * bracket a CDF root.  This method is used by
+     * {@link #inverseCumulativeProbability(double)} to find critical values.
+     *
+     * @param p the desired probability for the critical value
+     * @return domain value lower bound, i.e.
+     *         P(X &lt; <i>lower bound</i>) &lt; <code>p</code>
+     */
+    @Override
+    protected double getDomainLowerBound(double p) {
+        double ret;
+
+        if (p < .5) {
+            ret = -Double.MAX_VALUE;
+        } else {
+            ret = mean;
+        }
+
+        return ret;
+    }
+
+    /**
+     * Access the domain value upper bound, based on <code>p</code>, used to
+     * bracket a CDF root.  This method is used by
+     * {@link #inverseCumulativeProbability(double)} to find critical values.
+     *
+     * @param p the desired probability for the critical value
+     * @return domain value upper bound, i.e.
+     *         P(X &lt; <i>upper bound</i>) &gt; <code>p</code>
+     */
+    @Override
+    protected double getDomainUpperBound(double p) {
+        double ret;
+
+        if (p < .5) {
+            ret = mean;
+        } else {
+            ret = Double.MAX_VALUE;
+        }
+
+        return ret;
+    }
+
+    /**
+     * Access the initial domain value, based on <code>p</code>, used to
+     * bracket a CDF root.  This method is used by
+     * {@link #inverseCumulativeProbability(double)} to find critical values.
+     *
+     * @param p the desired probability for the critical value
+     * @return initial domain value
+     */
+    @Override
+    protected double getInitialDomain(double p) {
+        double ret;
+
+        if (p < .5) {
+            ret = mean - standardDeviation;
+        } else if (p > .5) {
+            ret = mean + standardDeviation;
+        } else {
+            ret = mean;
+        }
+
+        return ret;
+    }
+
+    /**
+     * Returns the lower bound of the support for the distribution.
+     *
+     * The lower bound of the support is always negative infinity
+     * no matter the parameters.
+     *
+     * @return lower bound of the support (always Double.NEGATIVE_INFINITY)
+     * @since 2.2
+     */
+    public double getSupportLowerBound() {
+        return Double.NEGATIVE_INFINITY;
+    }
+
+    /**
+     * Returns the upper bound of the support for the distribution.
+     *
+     * The upper bound of the support is always positive infinity
+     * no matter the parameters.
+     *
+     * @return upper bound of the support (always Double.POSITIVE_INFINITY)
+     * @since 2.2
+     */
+    public double getSupportUpperBound() {
+        return Double.POSITIVE_INFINITY;
+    }
+
+    /**
+     * Returns the variance.
+     *
+     * For standard deviation parameter <code>s</code>,
+     * the variance is <code>s^2</code>
+     *
+     * @return the variance
+     * @since 2.2
+     */
+    public double getNumericalVariance() {
+        final double s = getStandardDeviation();
+        return s * s;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/distribution/PascalDistribution.java b/src/main/java/org/apache/commons/math/distribution/PascalDistribution.java
new file mode 100644
index 0000000..88c5924
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/PascalDistribution.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.math.distribution;
+
+/**
+ * The Pascal distribution.  The Pascal distribution is a special case of the
+ * Negative Binomial distribution where the number of successes parameter is an
+ * integer.
+ *
+ * There are various ways to express the probability mass and distribution
+ * functions for the Pascal distribution.  The convention employed by the
+ * library is to express these functions in terms of the number of failures in
+ * a Bernoulli experiment [2].
+ *
+ * <p>
+ * References:
+ * <ol>
+ * <li><a href="http://mathworld.wolfram.com/NegativeBinomialDistribution.html">
+ * Negative Binomial Distribution</a></li>
+ * <oi><a href="http://en.wikipedia.org/wiki/Negative_binomial_distribution#Waiting_time_in_a_Bernoulli_process">Waiting Time in a Bernoulli Process</a></li>
+ * </ul>
+ * </p>
+ *
+ * @version $Revision: 920852 $ $Date: 2010-03-09 13:53:44 +0100 (mar. 09 mars 2010) $
+ * @since 1.2
+ */
+public interface PascalDistribution extends IntegerDistribution {
+    /**
+     * Access the number of successes for this distribution.
+     *
+     * @return the number of successes
+     */
+    int getNumberOfSuccesses();
+
+    /**
+     * Access the probability of success for this distribution.
+     *
+     * @return the probability of success
+     */
+    double getProbabilityOfSuccess();
+
+    /**
+     * Change the number of successes for this distribution.
+     *
+     * @param successes the new number of successes
+     * @deprecated as of v2.1
+     */
+    @Deprecated
+    void setNumberOfSuccesses(int successes);
+
+    /**
+     * Change the probability of success for this distribution.
+     *
+     * @param p the new probability of success
+     * @deprecated as of v2.1
+     */
+    @Deprecated
+    void setProbabilityOfSuccess(double p);
+}
diff --git a/src/main/java/org/apache/commons/math/distribution/PascalDistributionImpl.java b/src/main/java/org/apache/commons/math/distribution/PascalDistributionImpl.java
new file mode 100644
index 0000000..437cbc7
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/PascalDistributionImpl.java
@@ -0,0 +1,276 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.distribution;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.special.Beta;
+import org.apache.commons.math.util.MathUtils;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * The default implementation of {@link PascalDistribution}.
+ * @version $Revision: 1054524 $ $Date: 2011-01-03 05:59:18 +0100 (lun. 03 janv. 2011) $
+ * @since 1.2
+ */
+public class PascalDistributionImpl extends AbstractIntegerDistribution
+    implements PascalDistribution, Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 6751309484392813623L;
+
+    /** The number of successes */
+    private int numberOfSuccesses;
+
+    /** The probability of success */
+    private double probabilityOfSuccess;
+
+    /**
+     * Create a Pascal distribution with the given number of trials and
+     * probability of success.
+     * @param r the number of successes
+     * @param p the probability of success
+     */
+    public PascalDistributionImpl(int r, double p) {
+        super();
+        setNumberOfSuccessesInternal(r);
+        setProbabilityOfSuccessInternal(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;
+    }
+
+    /**
+     * Change the number of successes for this distribution.
+     * @param successes the new number of successes
+     * @throws IllegalArgumentException if <code>successes</code> is not
+     *         positive.
+     * @deprecated as of 2.1 (class will become immutable in 3.0)
+     */
+    @Deprecated
+    public void setNumberOfSuccesses(int successes) {
+        setNumberOfSuccessesInternal(successes);
+    }
+
+    /**
+     * Change the number of successes for this distribution.
+     * @param successes the new number of successes
+     * @throws IllegalArgumentException if <code>successes</code> is not
+     *         positive.
+     */
+    private void setNumberOfSuccessesInternal(int successes) {
+        if (successes < 0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.NEGATIVE_NUMBER_OF_SUCCESSES,
+                  successes);
+        }
+        numberOfSuccesses = successes;
+    }
+
+    /**
+     * Change the probability of success for this distribution.
+     * @param p the new probability of success
+     * @throws IllegalArgumentException if <code>p</code> is not a valid
+     *         probability.
+     * @deprecated as of 2.1 (class will become immutable in 3.0)
+     */
+    @Deprecated
+    public void setProbabilityOfSuccess(double p) {
+        setProbabilityOfSuccessInternal(p);
+    }
+
+    /**
+     * Change the probability of success for this distribution.
+     * @param p the new probability of success
+     * @throws IllegalArgumentException if <code>p</code> is not a valid
+     *         probability.
+     */
+    private void setProbabilityOfSuccessInternal(double p) {
+        if (p < 0.0 || p > 1.0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.OUT_OF_RANGE_SIMPLE, p, 0.0, 1.0);
+        }
+        probabilityOfSuccess = p;
+    }
+
+    /**
+     * Access the domain value lower bound, based on <code>p</code>, used to
+     * bracket a PDF root.
+     * @param p the desired probability for the critical value
+     * @return domain value lower bound, i.e. P(X &lt; <i>lower bound</i>) &lt;
+     *         <code>p</code>
+     */
+    @Override
+    protected int getDomainLowerBound(double p) {
+        return -1;
+    }
+
+    /**
+     * Access the domain value upper bound, based on <code>p</code>, used to
+     * bracket a PDF root.
+     * @param p the desired probability for the critical value
+     * @return domain value upper bound, i.e. P(X &lt; <i>upper bound</i>) &gt;
+     *         <code>p</code>
+     */
+    @Override
+    protected int getDomainUpperBound(double p) {
+        // use MAX - 1 because MAX causes loop
+        return Integer.MAX_VALUE - 1;
+    }
+
+    /**
+     * For this distribution, X, this method returns P(X &le; x).
+     * @param x the value at which the PDF is evaluated
+     * @return PDF for this distribution
+     * @throws MathException if the cumulative probability can not be computed
+     *         due to convergence or other numerical errors
+     */
+    @Override
+    public double cumulativeProbability(int x) throws MathException {
+        double ret;
+        if (x < 0) {
+            ret = 0.0;
+        } else {
+            ret = Beta.regularizedBeta(probabilityOfSuccess,
+                numberOfSuccesses, x + 1);
+        }
+        return ret;
+    }
+
+    /**
+     * For this distribution, X, this method returns P(X = x).
+     * @param x the value at which the PMF is evaluated
+     * @return PMF for this distribution
+     */
+    public double probability(int x) {
+        double ret;
+        if (x < 0) {
+            ret = 0.0;
+        } else {
+            ret = MathUtils.binomialCoefficientDouble(x +
+                  numberOfSuccesses - 1, numberOfSuccesses - 1) *
+                  FastMath.pow(probabilityOfSuccess, numberOfSuccesses) *
+                  FastMath.pow(1.0 - probabilityOfSuccess, x);
+        }
+        return ret;
+    }
+
+    /**
+     * For this distribution, X, this method returns the largest x, such that
+     * P(X &le; x) &le; <code>p</code>.
+     * <p>
+     * Returns <code>-1</code> for p=0 and <code>Integer.MAX_VALUE</code>
+     * for p=1.</p>
+     * @param p the desired probability
+     * @return the largest x such that P(X &le; x) <= p
+     * @throws MathException if the inverse cumulative probability can not be
+     *         computed due to convergence or other numerical errors.
+     * @throws IllegalArgumentException if p < 0 or p > 1
+     */
+    @Override
+    public int inverseCumulativeProbability(final double p)
+        throws MathException {
+        int ret;
+
+        // handle extreme values explicitly
+        if (p == 0) {
+            ret = -1;
+        } else if (p == 1) {
+            ret = Integer.MAX_VALUE;
+        } else {
+            ret = super.inverseCumulativeProbability(p);
+        }
+
+        return ret;
+    }
+
+    /**
+     * Returns the lower bound of the support for the distribution.
+     *
+     * The lower bound of the support is always 0 no matter the parameters.
+     *
+     * @return lower bound of the support (always 0)
+     * @since 2.2
+     */
+    public int getSupportLowerBound() {
+        return 0;
+    }
+
+    /**
+     * Returns the upper bound of the support for the distribution.
+     *
+     * The upper bound of the support is always positive infinity
+     * no matter the parameters. Positive infinity is represented
+     * by <code>Integer.MAX_VALUE</code> together with
+     * {@link #isSupportUpperBoundInclusive()} being <code>false</code>
+     *
+     * @return upper bound of the support (always <code>Integer.MAX_VALUE</code> for positive infinity)
+     * @since 2.2
+     */
+    public int getSupportUpperBound() {
+        return Integer.MAX_VALUE;
+    }
+
+    /**
+     * Returns the mean.
+     *
+     * For number of successes <code>r</code> and
+     * probability of success <code>p</code>, the mean is
+     * <code>( r * p ) / ( 1 - p )</code>
+     *
+     * @return the mean
+     * @since 2.2
+     */
+    public double getNumericalMean() {
+        final double p = getProbabilityOfSuccess();
+        final double r = getNumberOfSuccesses();
+        return ( r * p ) / ( 1 - p );
+    }
+
+    /**
+     * Returns the variance.
+     *
+     * For number of successes <code>r</code> and
+     * probability of success <code>p</code>, the mean is
+     * <code>( r * p ) / ( 1 - p )^2</code>
+     *
+     * @return the variance
+     * @since 2.2
+     */
+    public double getNumericalVariance() {
+        final double p = getProbabilityOfSuccess();
+        final double r = getNumberOfSuccesses();
+        final double pInv = 1 - p;
+        return ( r * p ) / (pInv * pInv);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/distribution/PoissonDistribution.java b/src/main/java/org/apache/commons/math/distribution/PoissonDistribution.java
new file mode 100644
index 0000000..b3a2f12
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/PoissonDistribution.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.math.distribution;
+
+import org.apache.commons.math.MathException;
+
+/**
+ * Interface representing the Poisson Distribution.
+ *
+ * <p>
+ * References:
+ * <ul>
+ * <li><a href="http://mathworld.wolfram.com/PoissonDistribution.html">
+ * Poisson distribution</a></li>
+ * </ul>
+ * </p>
+ *
+ * @version $Revision: 920852 $ $Date: 2010-03-09 13:53:44 +0100 (mar. 09 mars 2010) $
+ */
+public interface PoissonDistribution extends IntegerDistribution {
+
+    /**
+     * Get the mean for the distribution.
+     *
+     * @return the mean for the distribution.
+     */
+    double getMean();
+
+    /**
+     * Set the mean for the distribution.
+     * The parameter value must be positive; otherwise an
+     * <code>IllegalArgument</code> is thrown.
+     *
+     * @param p the mean
+     * @throws IllegalArgumentException if p &le; 0
+     * @deprecated as of v2.1
+     */
+    @Deprecated
+    void setMean(double p);
+
+    /**
+     * Calculates the Poisson distribution function using a normal approximation.
+     *
+     * @param x the upper bound, inclusive
+     * @return the distribution function value calculated using a normal approximation
+     * @throws MathException if an error occurs computing the normal approximation
+     */
+    double normalApproximateProbability(int x) throws MathException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/distribution/PoissonDistributionImpl.java b/src/main/java/org/apache/commons/math/distribution/PoissonDistributionImpl.java
new file mode 100644
index 0000000..85623d4
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/PoissonDistributionImpl.java
@@ -0,0 +1,343 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.distribution;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.special.Gamma;
+import org.apache.commons.math.util.MathUtils;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Implementation for the {@link PoissonDistribution}.
+ *
+ * @version $Revision: 1054524 $ $Date: 2011-01-03 05:59:18 +0100 (lun. 03 janv. 2011) $
+ */
+public class PoissonDistributionImpl extends AbstractIntegerDistribution
+        implements PoissonDistribution, Serializable {
+
+    /**
+     * 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 NormalDistribution normal;
+
+    /**
+     * Holds the Poisson mean for the distribution.
+     */
+    private double mean;
+
+    /**
+     * Maximum number of iterations for cumulative probability.
+     *
+     * Cumulative probabilities are estimated using either Lanczos series approximation of
+     * Gamma#regularizedGammaP or continued fraction approximation of Gamma#regularizedGammaQ.
+     */
+    private int maxIterations = DEFAULT_MAX_ITERATIONS;
+
+    /**
+     * Convergence criterion for cumulative probability.
+     */
+    private double epsilon = DEFAULT_EPSILON;
+
+    /**
+     * Create a new Poisson distribution with the given the mean. The mean value
+     * must be positive; otherwise an <code>IllegalArgument</code> is thrown.
+     *
+     * @param p the Poisson mean
+     * @throws IllegalArgumentException if p &le; 0
+     */
+    public PoissonDistributionImpl(double p) {
+        this(p, new NormalDistributionImpl());
+    }
+
+    /**
+     * Create a new Poisson distribution with the given mean, convergence criterion
+     * and maximum number of iterations.
+     *
+     * @param p the Poisson mean
+     * @param epsilon the convergence criteria for cumulative probabilites
+     * @param maxIterations the maximum number of iterations for cumulative probabilites
+     * @since 2.1
+     */
+    public PoissonDistributionImpl(double p, double epsilon, int maxIterations) {
+        setMean(p);
+        this.epsilon = epsilon;
+        this.maxIterations = maxIterations;
+    }
+
+    /**
+     * Create a new Poisson distribution with the given mean and convergence criterion.
+     *
+     * @param p the Poisson mean
+     * @param epsilon the convergence criteria for cumulative probabilites
+     * @since 2.1
+     */
+    public PoissonDistributionImpl(double p, double epsilon) {
+        setMean(p);
+        this.epsilon = epsilon;
+    }
+
+    /**
+     * Create a new Poisson distribution with the given mean and maximum number of iterations.
+     *
+     * @param p the Poisson mean
+     * @param maxIterations the maximum number of iterations for cumulative probabilites
+     * @since 2.1
+     */
+    public PoissonDistributionImpl(double p, int maxIterations) {
+        setMean(p);
+        this.maxIterations = maxIterations;
+    }
+
+
+    /**
+     * Create a new Poisson distribution with the given the mean. The mean value
+     * must be positive; otherwise an <code>IllegalArgument</code> is thrown.
+     *
+     * @param p the Poisson mean
+     * @param z a normal distribution used to compute normal approximations.
+     * @throws IllegalArgumentException if p &le; 0
+     * @since 1.2
+     * @deprecated as of 2.1 (to avoid possibly inconsistent state, the
+     * "NormalDistribution" will be instantiated internally)
+     */
+    @Deprecated
+    public PoissonDistributionImpl(double p, NormalDistribution z) {
+        super();
+        setNormalAndMeanInternal(z, p);
+    }
+
+    /**
+     * Get the Poisson mean for the distribution.
+     *
+     * @return the Poisson mean for the distribution.
+     */
+    public double getMean() {
+        return mean;
+    }
+
+    /**
+     * Set the Poisson mean for the distribution. The mean value must be
+     * positive; otherwise an <code>IllegalArgument</code> is thrown.
+     *
+     * @param p the Poisson mean value
+     * @throws IllegalArgumentException if p &le; 0
+     * @deprecated as of 2.1 (class will become immutable in 3.0)
+     */
+    @Deprecated
+    public void setMean(double p) {
+        setNormalAndMeanInternal(normal, p);
+    }
+    /**
+     * Set the Poisson mean for the distribution. The mean value must be
+     * positive; otherwise an <code>IllegalArgument</code> is thrown.
+     *
+     * @param z the new distribution
+     * @param p the Poisson mean value
+     * @throws IllegalArgumentException if p &le; 0
+     */
+    private void setNormalAndMeanInternal(NormalDistribution z,
+                                          double p) {
+        if (p <= 0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.NOT_POSITIVE_POISSON_MEAN, p);
+        }
+        mean = p;
+        normal = z;
+        normal.setMean(p);
+        normal.setStandardDeviation(FastMath.sqrt(p));
+    }
+
+    /**
+     * The probability mass function P(X = x) for a Poisson distribution.
+     *
+     * @param x the value at which the probability density function is
+     *            evaluated.
+     * @return the value of the probability mass function at x
+     */
+    public double probability(int x) {
+        double ret;
+        if (x < 0 || x == Integer.MAX_VALUE) {
+            ret = 0.0;
+        } else if (x == 0) {
+            ret = FastMath.exp(-mean);
+        } else {
+            ret = FastMath.exp(-SaddlePointExpansion.getStirlingError(x) -
+                  SaddlePointExpansion.getDeviancePart(x, mean)) /
+                  FastMath.sqrt(MathUtils.TWO_PI * x);
+        }
+        return ret;
+    }
+
+    /**
+     * The probability distribution function P(X <= x) for a Poisson
+     * distribution.
+     *
+     * @param x the value at which the PDF is evaluated.
+     * @return Poisson distribution function evaluated at x
+     * @throws MathException if the cumulative probability can not be computed
+     *             due to convergence or other numerical errors.
+     */
+    @Override
+    public double cumulativeProbability(int x) throws MathException {
+        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))</code> distribution is used
+     * to approximate the Poisson distribution.
+     * <p>
+     * The computation uses "half-correction" -- evaluating the normal
+     * distribution function at <code>x + 0.5</code>
+     * </p>
+     *
+     * @param x the upper bound, inclusive
+     * @return the distribution function value calculated using a normal
+     *         approximation
+     * @throws MathException if an error occurs computing the normal
+     *             approximation
+     */
+    public double normalApproximateProbability(int x) throws MathException {
+        // calculate the probability using half-correction
+        return normal.cumulativeProbability(x + 0.5);
+    }
+
+    /**
+     * Generates a random value sampled from this distribution.
+     *
+     * <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><
+     *
+     * <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.</li></ul></p>
+     *
+     * @return random value
+     * @since 2.2
+     * @throws MathException if an error occurs generating the random value
+     */
+    @Override
+    public int sample() throws MathException {
+        return (int) FastMath.min(randomData.nextPoisson(mean), Integer.MAX_VALUE);
+    }
+
+    /**
+     * Access the domain value lower bound, based on <code>p</code>, used to
+     * bracket a CDF root. This method is used by
+     * {@link #inverseCumulativeProbability(double)} to find critical values.
+     *
+     * @param p the desired probability for the critical value
+     * @return domain lower bound
+     */
+    @Override
+    protected int getDomainLowerBound(double p) {
+        return 0;
+    }
+
+    /**
+     * Access the domain value upper bound, based on <code>p</code>, used to
+     * bracket a CDF root. This method is used by
+     * {@link #inverseCumulativeProbability(double)} to find critical values.
+     *
+     * @param p the desired probability for the critical value
+     * @return domain upper bound
+     */
+    @Override
+    protected int getDomainUpperBound(double p) {
+        return Integer.MAX_VALUE;
+    }
+
+    /**
+     * Modify the normal distribution used to compute normal approximations. The
+     * caller is responsible for insuring the normal distribution has the proper
+     * parameter settings.
+     *
+     * @param value the new distribution
+     * @since 1.2
+     * @deprecated as of 2.1 (class will become immutable in 3.0)
+     */
+    @Deprecated
+    public void setNormal(NormalDistribution value) {
+        setNormalAndMeanInternal(value, mean);
+    }
+
+    /**
+     * Returns the lower bound of the support for the distribution.
+     *
+     * The lower bound of the support is always 0 no matter the mean parameter.
+     *
+     * @return lower bound of the support (always 0)
+     * @since 2.2
+     */
+    public int getSupportLowerBound() {
+        return 0;
+    }
+
+    /**
+     * Returns the upper bound of the support for the distribution.
+     *
+     * 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</code> and
+     * {@link #isSupportUpperBoundInclusive()} returns <code>true</code>.
+     *
+     * @return upper bound of the support (always <code>Integer.MAX_VALUE</code> for positive infinity)
+     * @since 2.2
+     */
+    public int getSupportUpperBound() {
+        return Integer.MAX_VALUE;
+    }
+
+    /**
+     * Returns the variance of the distribution.
+     *
+     * For mean parameter <code>p</code>, the variance is <code>p</code>
+     *
+     * @return the variance
+     * @since 2.2
+     */
+    public double getNumericalVariance() {
+        return getMean();
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/distribution/SaddlePointExpansion.java b/src/main/java/org/apache/commons/math/distribution/SaddlePointExpansion.java
new file mode 100644
index 0000000..a936123
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/SaddlePointExpansion.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.math.distribution;
+
+import org.apache.commons.math.special.Gamma;
+import org.apache.commons.math.util.FastMath;
+import org.apache.commons.math.util.MathUtils;
+
+/**
+ * <p>
+ * 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>
+ * <p>
+ * This class is not intended to be called directly.
+ * </p>
+ * <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></li>
+ * </ol>
+ * </p>
+ *
+ * @since 2.1
+ * @version $Revision$ $Date$
+ */
+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></li>
+     * </ol>
+     * </p>
+     *
+     * @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></li>
+     * </ol>
+     * </p>
+     *
+     * @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 * 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 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/math/distribution/TDistribution.java b/src/main/java/org/apache/commons/math/distribution/TDistribution.java
new file mode 100644
index 0000000..034c0d8
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/TDistribution.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.math.distribution;
+
+/**
+ * Student's t-Distribution.
+ *
+ * <p>
+ * References:
+ * <ul>
+ * <li><a href="http://mathworld.wolfram.com/Studentst-Distribution.html">
+ * Student's t-Distribution</a></li>
+ * </ul>
+ * </p>
+ *
+ * @version $Revision: 920852 $ $Date: 2010-03-09 13:53:44 +0100 (mar. 09 mars 2010) $
+ */
+public interface TDistribution extends ContinuousDistribution {
+    /**
+     * Modify the degrees of freedom.
+     * @param degreesOfFreedom the new degrees of freedom.
+     * @deprecated as of v2.1
+     */
+    @Deprecated
+    void setDegreesOfFreedom(double degreesOfFreedom);
+
+    /**
+     * Access the degrees of freedom.
+     * @return the degrees of freedom.
+     */
+    double getDegreesOfFreedom();
+}
diff --git a/src/main/java/org/apache/commons/math/distribution/TDistributionImpl.java b/src/main/java/org/apache/commons/math/distribution/TDistributionImpl.java
new file mode 100644
index 0000000..35b72cf
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/TDistributionImpl.java
@@ -0,0 +1,303 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.distribution;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.special.Beta;
+import org.apache.commons.math.special.Gamma;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Default implementation of
+ * {@link org.apache.commons.math.distribution.TDistribution}.
+ *
+ * @version $Revision: 1054524 $ $Date: 2011-01-03 05:59:18 +0100 (lun. 03 janv. 2011) $
+ */
+public class TDistributionImpl
+    extends AbstractContinuousDistribution
+    implements TDistribution, Serializable  {
+
+    /**
+     * 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 double degreesOfFreedom;
+
+    /** Inverse cumulative probability accuracy */
+    private final double solverAbsoluteAccuracy;
+
+    /**
+     * Create a t distribution using the given degrees of freedom and the
+     * specified inverse cumulative probability absolute accuracy.
+     *
+     * @param degreesOfFreedom the 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 TDistributionImpl(double degreesOfFreedom, double inverseCumAccuracy) {
+        super();
+        setDegreesOfFreedomInternal(degreesOfFreedom);
+        solverAbsoluteAccuracy = inverseCumAccuracy;
+    }
+
+    /**
+     * Create a t distribution using the given degrees of freedom.
+     * @param degreesOfFreedom the degrees of freedom.
+     */
+    public TDistributionImpl(double degreesOfFreedom) {
+        this(degreesOfFreedom, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+    }
+
+    /**
+     * Modify the degrees of freedom.
+     * @param degreesOfFreedom the new degrees of freedom.
+     * @deprecated as of 2.1 (class will become immutable in 3.0)
+     */
+    @Deprecated
+    public void setDegreesOfFreedom(double degreesOfFreedom) {
+        setDegreesOfFreedomInternal(degreesOfFreedom);
+    }
+
+    /**
+     * Modify the degrees of freedom.
+     * @param newDegreesOfFreedom the new degrees of freedom.
+     */
+    private void setDegreesOfFreedomInternal(double newDegreesOfFreedom) {
+        if (newDegreesOfFreedom <= 0.0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.NOT_POSITIVE_DEGREES_OF_FREEDOM,
+                  newDegreesOfFreedom);
+        }
+        this.degreesOfFreedom = newDegreesOfFreedom;
+    }
+
+    /**
+     * Access the degrees of freedom.
+     * @return the degrees of freedom.
+     */
+    public double getDegreesOfFreedom() {
+        return degreesOfFreedom;
+    }
+
+    /**
+     * Returns the probability density for a particular point.
+     *
+     * @param x The point at which the density should be computed.
+     * @return The pdf at point x.
+     * @since 2.1
+     */
+    @Override
+    public double density(double x) {
+        final double n = degreesOfFreedom;
+        final double nPlus1Over2 = (n + 1) / 2;
+        return FastMath.exp(Gamma.logGamma(nPlus1Over2) - 0.5 * (FastMath.log(FastMath.PI) + FastMath.log(n)) -
+                Gamma.logGamma(n/2) - nPlus1Over2 * FastMath.log(1 + x * x /n));
+    }
+
+    /**
+     * For this distribution, X, this method returns P(X &lt; <code>x</code>).
+     * @param x the value at which the CDF is evaluated.
+     * @return CDF evaluated at <code>x</code>.
+     * @throws MathException if the cumulative probability can not be
+     *            computed due to convergence or other numerical errors.
+     */
+    public double cumulativeProbability(double x) throws MathException{
+        double ret;
+        if (x == 0.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;
+    }
+
+    /**
+     * For this distribution, X, this method returns the critical point x, such
+     * that P(X &lt; x) = <code>p</code>.
+     * <p>
+     * Returns <code>Double.NEGATIVE_INFINITY</code> for p=0 and
+     * <code>Double.POSITIVE_INFINITY</code> for p=1.</p>
+     *
+     * @param p the desired probability
+     * @return x, such that P(X &lt; x) = <code>p</code>
+     * @throws MathException if the inverse cumulative probability can not be
+     *         computed due to convergence or other numerical errors.
+     * @throws IllegalArgumentException if <code>p</code> is not a valid
+     *         probability.
+     */
+    @Override
+    public double inverseCumulativeProbability(final double p)
+    throws MathException {
+        if (p == 0) {
+            return Double.NEGATIVE_INFINITY;
+        }
+        if (p == 1) {
+            return Double.POSITIVE_INFINITY;
+        }
+        return super.inverseCumulativeProbability(p);
+    }
+
+    /**
+     * Access the domain value lower bound, based on <code>p</code>, used to
+     * bracket a CDF root.  This method is used by
+     * {@link #inverseCumulativeProbability(double)} to find critical values.
+     *
+     * @param p the desired probability for the critical value
+     * @return domain value lower bound, i.e.
+     *         P(X &lt; <i>lower bound</i>) &lt; <code>p</code>
+     */
+    @Override
+    protected double getDomainLowerBound(double p) {
+        return -Double.MAX_VALUE;
+    }
+
+    /**
+     * Access the domain value upper bound, based on <code>p</code>, used to
+     * bracket a CDF root.  This method is used by
+     * {@link #inverseCumulativeProbability(double)} to find critical values.
+     *
+     * @param p the desired probability for the critical value
+     * @return domain value upper bound, i.e.
+     *         P(X &lt; <i>upper bound</i>) &gt; <code>p</code>
+     */
+    @Override
+    protected double getDomainUpperBound(double p) {
+        return Double.MAX_VALUE;
+    }
+
+    /**
+     * Access the initial domain value, based on <code>p</code>, used to
+     * bracket a CDF root.  This method is used by
+     * {@link #inverseCumulativeProbability(double)} to find critical values.
+     *
+     * @param p the desired probability for the critical value
+     * @return initial domain value
+     */
+    @Override
+    protected double getInitialDomain(double p) {
+        return 0.0;
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * Returns the lower bound of the support for the distribution.
+     *
+     * The lower bound of the support is always negative infinity
+     * no matter the parameters.
+     *
+     * @return lower bound of the support (always Double.NEGATIVE_INFINITY)
+     * @since 2.2
+     */
+    public double getSupportLowerBound() {
+        return Double.NEGATIVE_INFINITY;
+    }
+
+    /**
+     * Returns the upper bound of the support for the distribution.
+     *
+     * The upper bound of the support is always positive infinity
+     * no matter the parameters.
+     *
+     * @return upper bound of the support (always Double.POSITIVE_INFINITY)
+     * @since 2.2
+     */
+    public double getSupportUpperBound() {
+        return Double.POSITIVE_INFINITY;
+    }
+
+    /**
+     * Returns the mean.
+     *
+     * For degrees of freedom parameter df, the mean is
+     * <ul>
+     *  <li>if <code>df &gt; 1</code> then <code>0</code></li>
+     * <li>else <code>undefined</code></li>
+     * </ul>
+     *
+     * @return the mean
+     * @since 2.2
+     */
+    public double getNumericalMean() {
+        final double df = getDegreesOfFreedom();
+
+        if (df > 1) {
+            return 0;
+        }
+
+        return Double.NaN;
+    }
+
+    /**
+     * Returns the variance.
+     *
+     * For degrees of freedom parameter df, the variance is
+     * <ul>
+     *  <li>if <code>df &gt; 2</code> then <code>df / (df - 2)</code> </li>
+     *  <li>if <code>1 &lt; df &lt;= 2</code> then <code>positive infinity</code></li>
+     *  <li>else <code>undefined</code></li>
+     * </ul>
+     *
+     * @return the variance
+     * @since 2.2
+     */
+    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;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/distribution/WeibullDistribution.java b/src/main/java/org/apache/commons/math/distribution/WeibullDistribution.java
new file mode 100644
index 0000000..bd1df5e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/WeibullDistribution.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.math.distribution;
+
+/**
+ * Weibull Distribution.  This interface defines the two parameter form of the
+ * distribution as defined by
+ * <a href="http://mathworld.wolfram.com/WeibullDistribution.html">
+ * Weibull Distribution</a>, equations (1) and (2).
+ *
+ * <p>
+ * References:
+ * <ul>
+ * <li><a href="http://mathworld.wolfram.com/WeibullDistribution.html">
+ * Weibull Distribution</a></li>
+ * </ul>
+ * </p>
+ *
+ * @since 1.1
+ * @version $Revision: 920852 $ $Date: 2010-03-09 13:53:44 +0100 (mar. 09 mars 2010) $
+ */
+public interface WeibullDistribution extends ContinuousDistribution {
+
+    /**
+     * Access the shape parameter.
+     * @return the shape parameter.
+     */
+    double getShape();
+
+    /**
+     * Access the scale parameter.
+     * @return the scale parameter.
+     */
+    double getScale();
+
+    /**
+     * Modify the shape parameter.
+     * @param alpha The new shape parameter value.
+     * @deprecated as of v2.1
+     */
+    @Deprecated
+    void setShape(double alpha);
+
+    /**
+     * Modify the scale parameter.
+     * @param beta The new scale parameter value.
+     * @deprecated as of v2.1
+     */
+    @Deprecated
+    void setScale(double beta);
+}
diff --git a/src/main/java/org/apache/commons/math/distribution/WeibullDistributionImpl.java b/src/main/java/org/apache/commons/math/distribution/WeibullDistributionImpl.java
new file mode 100644
index 0000000..c52caac
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/WeibullDistributionImpl.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.math.distribution;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.special.Gamma;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Default implementation of
+ * {@link org.apache.commons.math.distribution.WeibullDistribution}.
+ *
+ * @since 1.1
+ * @version $Revision: 1054524 $ $Date: 2011-01-03 05:59:18 +0100 (lun. 03 janv. 2011) $
+ */
+public class WeibullDistributionImpl extends AbstractContinuousDistribution
+        implements WeibullDistribution, Serializable {
+
+    /**
+     * 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 double shape;
+
+    /** The scale parameter. */
+    private 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;
+
+    /**
+     * Creates weibull distribution with the given shape and scale and a
+     * location equal to zero.
+     * @param alpha the shape parameter.
+     * @param beta the scale parameter.
+     */
+    public WeibullDistributionImpl(double alpha, double beta){
+        this(alpha, beta, DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+    }
+
+    /**
+     * Creates weibull distribution with the given shape, scale and inverse
+     * cumulative probability accuracy and a location equal to zero.
+     * @param alpha the shape parameter.
+     * @param beta the scale parameter.
+     * @param inverseCumAccuracy the maximum absolute error in inverse cumulative probability estimates
+     * (defaults to {@link #DEFAULT_INVERSE_ABSOLUTE_ACCURACY})
+     * @since 2.1
+     */
+    public WeibullDistributionImpl(double alpha, double beta, double inverseCumAccuracy){
+        super();
+        setShapeInternal(alpha);
+        setScaleInternal(beta);
+        solverAbsoluteAccuracy = inverseCumAccuracy;
+    }
+
+    /**
+     * For this distribution, X, this method returns P(X &lt; <code>x</code>).
+     * @param x the value at which the CDF is evaluated.
+     * @return CDF evaluated at <code>x</code>.
+     */
+    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;
+    }
+
+    /**
+     * Access the shape parameter.
+     * @return the shape parameter.
+     */
+    public double getShape() {
+        return shape;
+    }
+
+    /**
+     * Access the scale parameter.
+     * @return the scale parameter.
+     */
+    public double getScale() {
+        return scale;
+    }
+
+    /**
+     * Returns the probability density for a particular point.
+     *
+     * @param x The point at which the density should be computed.
+     * @return The pdf at point x.
+     * @since 2.1
+     */
+    @Override
+    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);
+    }
+
+    /**
+     * For this distribution, X, this method returns the critical point x, such
+     * that P(X &lt; x) = <code>p</code>.
+     * <p>
+     * Returns <code>Double.NEGATIVE_INFINITY</code> for p=0 and
+     * <code>Double.POSITIVE_INFINITY</code> for p=1.</p>
+     *
+     * @param p the desired probability
+     * @return x, such that P(X &lt; x) = <code>p</code>
+     * @throws IllegalArgumentException if <code>p</code> is not a valid
+     *         probability.
+     */
+    @Override
+    public double inverseCumulativeProbability(double p) {
+        double ret;
+        if (p < 0.0 || p > 1.0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.OUT_OF_RANGE_SIMPLE, 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.log(1.0 - p), 1.0 / shape);
+        }
+        return ret;
+    }
+
+    /**
+     * Modify the shape parameter.
+     * @param alpha the new shape parameter value.
+     * @deprecated as of 2.1 (class will become immutable in 3.0)
+     */
+    @Deprecated
+    public void setShape(double alpha) {
+        setShapeInternal(alpha);
+        invalidateParameterDependentMoments();
+    }
+    /**
+     * Modify the shape parameter.
+     * @param alpha the new shape parameter value.
+     */
+    private void setShapeInternal(double alpha) {
+        if (alpha <= 0.0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.NOT_POSITIVE_SHAPE,
+                  alpha);
+        }
+        this.shape = alpha;
+    }
+
+    /**
+     * Modify the scale parameter.
+     * @param beta the new scale parameter value.
+     * @deprecated as of 2.1 (class will become immutable in 3.0)
+     */
+    @Deprecated
+    public void setScale(double beta) {
+        setScaleInternal(beta);
+        invalidateParameterDependentMoments();
+    }
+    /**
+     * Modify the scale parameter.
+     * @param beta the new scale parameter value.
+     */
+    private void setScaleInternal(double beta) {
+        if (beta <= 0.0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.NOT_POSITIVE_SCALE,
+                  beta);
+        }
+        this.scale = beta;
+    }
+
+    /**
+     * Access the domain value lower bound, based on <code>p</code>, used to
+     * bracket a CDF root.  This method is used by
+     * {@link #inverseCumulativeProbability(double)} to find critical values.
+     *
+     * @param p the desired probability for the critical value
+     * @return domain value lower bound, i.e.
+     *         P(X &lt; <i>lower bound</i>) &lt; <code>p</code>
+     */
+    @Override
+    protected double getDomainLowerBound(double p) {
+        return 0.0;
+    }
+
+    /**
+     * Access the domain value upper bound, based on <code>p</code>, used to
+     * bracket a CDF root.  This method is used by
+     * {@link #inverseCumulativeProbability(double)} to find critical values.
+     *
+     * @param p the desired probability for the critical value
+     * @return domain value upper bound, i.e.
+     *         P(X &lt; <i>upper bound</i>) &gt; <code>p</code>
+     */
+    @Override
+    protected double getDomainUpperBound(double p) {
+        return Double.MAX_VALUE;
+    }
+
+    /**
+     * Access the initial domain value, based on <code>p</code>, used to
+     * bracket a CDF root.  This method is used by
+     * {@link #inverseCumulativeProbability(double)} to find critical values.
+     *
+     * @param p the desired probability for the critical value
+     * @return initial domain value
+     */
+    @Override
+    protected double getInitialDomain(double p) {
+        // use median
+        return FastMath.pow(scale * FastMath.log(2.0), 1.0 / shape);
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * Returns the lower bound of the support for the distribution.
+     *
+     * The lower bound of the support is always 0 no matter the parameters.
+     *
+     * @return lower bound of the support (always 0)
+     * @since 2.2
+     */
+    public double getSupportLowerBound() {
+        return 0;
+    }
+
+    /**
+     * Returns the upper bound of the support for the distribution.
+     *
+     * The upper bound of the support is always positive infinity
+     * no matter the parameters.
+     *
+     * @return upper bound of the support (always Double.POSITIVE_INFINITY)
+     * @since 2.2
+     */
+    public double getSupportUpperBound() {
+        return Double.POSITIVE_INFINITY;
+    }
+
+    /**
+     * Calculates the mean.
+     *
+     * The mean is <code>scale * Gamma(1 + (1 / shape))</code>
+     * where <code>Gamma(...)</code> is the Gamma-function
+     *
+     * @return the mean
+     * @since 2.2
+     */
+    protected double calculateNumericalMean() {
+        final double sh = getShape();
+        final double sc = getScale();
+
+        return sc * FastMath.exp(Gamma.logGamma(1 + (1 / sh)));
+    }
+
+    /**
+     * Calculates the variance.
+     *
+     * The variance is
+     * <code>scale^2 * Gamma(1 + (2 / shape)) - mean^2</code>
+     * where <code>Gamma(...)</code> is the Gamma-function
+     *
+     * @return the variance
+     * @since 2.2
+     */
+    private 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);
+    }
+
+    /**
+     * Returns the mean of the distribution.
+     *
+     * @return the mean or Double.NaN if it's not defined
+     * @since 2.2
+     */
+    public double getNumericalMean() {
+        if (!numericalMeanIsCalculated) {
+            numericalMean = calculateNumericalMean();
+            numericalMeanIsCalculated = true;
+        }
+
+        return numericalMean;
+    }
+
+    /**
+     * Returns the variance of the distribution.
+     *
+     * @return the variance (possibly Double.POSITIVE_INFINITY as
+     * for certain cases in {@link TDistributionImpl}) or
+     * Double.NaN if it's not defined
+     * @since 2.2
+     */
+    public double getNumericalVariance() {
+        if (!numericalVarianceIsCalculated) {
+            numericalVariance = calculateNumericalVariance();
+            numericalVarianceIsCalculated = true;
+        }
+
+        return numericalVariance;
+    }
+
+    /**
+     * Invalidates the cached mean and variance.
+     */
+    private void invalidateParameterDependentMoments() {
+        numericalMeanIsCalculated = false;
+        numericalVarianceIsCalculated = false;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/distribution/ZipfDistribution.java b/src/main/java/org/apache/commons/math/distribution/ZipfDistribution.java
new file mode 100644
index 0000000..885adac
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/ZipfDistribution.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.math.distribution;
+
+/**
+ * The Zipf (or zeta) Distribution.
+ * <p>
+ * References:
+ * <ul>
+ * <li><a href="http://mathworld.wolfram.com/ZipfDistribution.html">Zipf
+ * Distribution</a></li>
+ * </ul>
+ * </p>
+ *
+ * @version $Revision: 920852 $ $Date: 2010-03-09 13:53:44 +0100 (mar. 09 mars 2010) $
+ */
+public interface ZipfDistribution extends IntegerDistribution {
+
+    /**
+     * Get the number of elements (e.g. corpus size) for the distribution.
+     *
+     * @return the number of elements
+     */
+    int getNumberOfElements();
+
+    /**
+     * Set the number of elements (e.g. corpus size) for the distribution.
+     * The parameter value must be positive; otherwise an
+     * <code>IllegalArgumentException</code> is thrown.
+     *
+     * @param n the number of elements
+     * @throws IllegalArgumentException if n &le; 0
+     * @deprecated as of v2.1
+     */
+    @Deprecated
+    void setNumberOfElements(int n);
+
+    /**
+     * Get the exponent characterising the distribution.
+     *
+     * @return the exponent
+     */
+    double getExponent();
+
+    /**
+     * Set the exponent characterising the distribution.
+     * The parameter value must be positive; otherwise an
+     * <code>IllegalArgumentException</code> is thrown.
+     *
+     * @param s the exponent
+     * @throws IllegalArgumentException if s &le; 0.0
+     * @deprecated as of v2.1
+     */
+    @Deprecated
+    void setExponent(double s);
+}
diff --git a/src/main/java/org/apache/commons/math/distribution/ZipfDistributionImpl.java b/src/main/java/org/apache/commons/math/distribution/ZipfDistributionImpl.java
new file mode 100644
index 0000000..9f19528
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/ZipfDistributionImpl.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.math.distribution;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Implementation for the {@link ZipfDistribution}.
+ *
+ * @version $Revision: 1054524 $ $Date: 2011-01-03 05:59:18 +0100 (lun. 03 janv. 2011) $
+ */
+public class ZipfDistributionImpl extends AbstractIntegerDistribution
+    implements ZipfDistribution, Serializable {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -140627372283420404L;
+
+    /** Number of elements. */
+    private int numberOfElements;
+
+    /** Exponent parameter of the distribution. */
+    private double exponent;
+
+    /**
+     * Create a new Zipf distribution with the given number of elements and
+     * exponent. Both values must be positive; otherwise an
+     * <code>IllegalArgumentException</code> is thrown.
+     *
+     * @param numberOfElements the number of elements
+     * @param exponent the exponent
+     * @exception IllegalArgumentException if n &le; 0 or s &le; 0.0
+     */
+    public ZipfDistributionImpl(final int numberOfElements, final double exponent)
+        throws IllegalArgumentException {
+        setNumberOfElementsInternal(numberOfElements);
+        setExponentInternal(exponent);
+    }
+
+    /**
+     * Get the number of elements (e.g. corpus size) for the distribution.
+     *
+     * @return the number of elements
+     */
+    public int getNumberOfElements() {
+        return numberOfElements;
+    }
+
+    /**
+     * Set the number of elements (e.g. corpus size) for the distribution.
+     * The parameter value must be positive; otherwise an
+     * <code>IllegalArgumentException</code> is thrown.
+     *
+     * @param n the number of elements
+     * @exception IllegalArgumentException if n &le; 0
+     * @deprecated as of 2.1 (class will become immutable in 3.0)
+     */
+    @Deprecated
+    public void setNumberOfElements(final int n) {
+        setNumberOfElementsInternal(n);
+    }
+    /**
+     * Set the number of elements (e.g. corpus size) for the distribution.
+     * The parameter value must be positive; otherwise an
+     * <code>IllegalArgumentException</code> is thrown.
+     *
+     * @param n the number of elements
+     * @exception IllegalArgumentException if n &le; 0
+     */
+    private void setNumberOfElementsInternal(final int n)
+        throws IllegalArgumentException {
+        if (n <= 0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.INSUFFICIENT_DIMENSION, n, 0);
+        }
+        this.numberOfElements = n;
+    }
+
+    /**
+     * Get the exponent characterising the distribution.
+     *
+     * @return the exponent
+     */
+    public double getExponent() {
+        return exponent;
+    }
+
+    /**
+     * Set the exponent characterising the distribution.
+     * The parameter value must be positive; otherwise an
+     * <code>IllegalArgumentException</code> is thrown.
+     *
+     * @param s the exponent
+     * @exception IllegalArgumentException if s &le; 0.0
+     * @deprecated as of 2.1 (class will become immutable in 3.0)
+     */
+    @Deprecated
+    public void setExponent(final double s) {
+        setExponentInternal(s);
+    }
+
+    /**
+     * Set the exponent characterising the distribution.
+     * The parameter value must be positive; otherwise an
+     * <code>IllegalArgumentException</code> is thrown.
+     *
+     * @param s the exponent
+     * @exception IllegalArgumentException if s &le; 0.0
+     */
+    private void setExponentInternal(final double s)
+        throws IllegalArgumentException {
+        if (s <= 0.0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.NOT_POSITIVE_EXPONENT,
+                    s);
+        }
+        this.exponent = s;
+    }
+
+    /**
+     * The probability mass function P(X = x) for a Zipf distribution.
+     *
+     * @param x the value at which the probability density function is evaluated.
+     * @return the value of the probability mass function at x
+     */
+    public double probability(final int x) {
+        if (x <= 0 || x > numberOfElements) {
+            return 0.0;
+        }
+
+        return (1.0 / FastMath.pow(x, exponent)) / generalizedHarmonic(numberOfElements, exponent);
+
+    }
+
+    /**
+     * The probability distribution function P(X <= x) for a Zipf distribution.
+     *
+     * @param x the value at which the PDF is evaluated.
+     * @return Zipf distribution function evaluated at x
+     */
+    @Override
+    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);
+
+    }
+
+    /**
+     * Access the domain value lower bound, based on <code>p</code>, used to
+     * bracket a PDF root.
+     *
+     * @param p the desired probability for the critical value
+     * @return domain value lower bound, i.e.
+     *         P(X &lt; <i>lower bound</i>) &lt; <code>p</code>
+     */
+    @Override
+    protected int getDomainLowerBound(final double p) {
+        return 0;
+    }
+
+    /**
+     * Access the domain value upper bound, based on <code>p</code>, used to
+     * bracket a PDF root.
+     *
+     * @param p the desired probability for the critical value
+     * @return domain value upper bound, i.e.
+     *         P(X &lt; <i>upper bound</i>) &gt; <code>p</code>
+     */
+    @Override
+    protected int getDomainUpperBound(final double p) {
+        return numberOfElements;
+    }
+
+
+    /**
+     * Calculates the Nth generalized harmonic number. See
+     * <a href="http://mathworld.wolfram.com/HarmonicSeries.html">Harmonic
+     * Series</a>.
+     *
+     * @param n the term in the series to calculate (must be &ge; 1)
+     * @param m the exponent; special case m == 1.0 is the harmonic series
+     * @return the nth 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;
+    }
+
+    /**
+     * Returns the lower bound of the support for the distribution.
+     *
+     * The lower bound of the support is always 1 no matter the parameters.
+     *
+     * @return lower bound of the support (always 1)
+     * @since 2.2
+     */
+    public int getSupportLowerBound() {
+        return 1;
+    }
+
+    /**
+     * Returns the upper bound of the support for the distribution.
+     *
+     * The upper bound of the support is the number of elements
+     *
+     * @return upper bound of the support
+     * @since 2.2
+     */
+    public int getSupportUpperBound() {
+        return getNumberOfElements();
+    }
+
+    /**
+     * Returns the mean.
+     *
+     * For number of elements N and exponent s, the mean is
+     * <code>Hs1 / Hs</code> where
+     * <ul>
+     *  <li><code>Hs1 = generalizedHarmonic(N, s - 1)</code></li>
+     *  <li><code>Hs = generalizedHarmonic(N, s)</code></li>
+     * </ul>
+     *
+     * @return the mean
+     * @since 2.2
+     */
+    protected double getNumericalMean() {
+        final int N = getNumberOfElements();
+        final double s = getExponent();
+
+        final double Hs1 = generalizedHarmonic(N, s - 1);
+        final double Hs = generalizedHarmonic(N, s);
+
+        return Hs1 / Hs;
+    }
+
+    /**
+     * Returns the variance.
+     *
+     * For number of elements N and exponent s, the mean is
+     * <code>(Hs2 / Hs) - (Hs1^2 / Hs^2)</code> where
+     * <ul>
+     *  <li><code>Hs2 = generalizedHarmonic(N, s - 2)</code></li>
+     *  <li><code>Hs1 = generalizedHarmonic(N, s - 1)</code></li>
+     *  <li><code>Hs = generalizedHarmonic(N, s)</code></li>
+     * </ul>
+     *
+     * @return the variance
+     * @since 2.2
+     */
+    protected double getNumericalVariance() {
+        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));
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/distribution/package.html b/src/main/java/org/apache/commons/math/distribution/package.html
new file mode 100644
index 0000000..c89e8d8
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/distribution/package.html
@@ -0,0 +1,20 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 480440 $ $Date: 2006-11-29 08:14:12 +0100 (mer. 29 nov. 2006) $ -->
+    <body>Implementations of common discrete and continuous distributions.</body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/estimation/AbstractEstimator.java b/src/main/java/org/apache/commons/math/estimation/AbstractEstimator.java
new file mode 100644
index 0000000..7a52b55
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/estimation/AbstractEstimator.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.math.estimation;
+
+import java.util.Arrays;
+
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.linear.InvalidMatrixException;
+import org.apache.commons.math.linear.LUDecompositionImpl;
+import org.apache.commons.math.linear.MatrixUtils;
+import org.apache.commons.math.linear.RealMatrix;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Base class for implementing estimators.
+ * <p>This base class handles the boilerplates methods associated to thresholds
+ * settings, jacobian and error estimation.</p>
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ * @since 1.2
+ * @deprecated as of 2.0, everything in package org.apache.commons.math.estimation has
+ * been deprecated and replaced by package org.apache.commons.math.optimization.general
+ *
+ */
+@Deprecated
+public abstract class AbstractEstimator implements Estimator {
+
+    /** Default maximal number of cost evaluations allowed. */
+    public static final int DEFAULT_MAX_COST_EVALUATIONS = 100;
+
+    /** Array of measurements. */
+    protected WeightedMeasurement[] measurements;
+
+    /** Array of parameters. */
+    protected EstimatedParameter[] parameters;
+
+    /**
+     * Jacobian matrix.
+     * <p>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 LevenbergMarquardtEstimator
+     * Levenberg-Marquardt estimator} does this).</p>
+     */
+    protected double[] jacobian;
+
+    /** Number of columns of the jacobian matrix. */
+    protected int cols;
+
+    /** Number of rows of the jacobian matrix. */
+    protected int rows;
+
+    /** Residuals array.
+     * <p>This array is in canonical form just after the calls to
+     * {@link #updateJacobian()}, but may be modified by the solver
+     * in the derived class (the {@link LevenbergMarquardtEstimator
+     * Levenberg-Marquardt estimator} does this).</p>
+     */
+    protected double[] residuals;
+
+    /** Cost value (square root of the sum of the residuals). */
+    protected double cost;
+
+    /** Maximal allowed number of cost evaluations. */
+    private int maxCostEval;
+
+    /** Number of cost evaluations. */
+    private int costEvaluations;
+
+    /** Number of jacobian evaluations. */
+    private int jacobianEvaluations;
+
+    /**
+     * Build an abstract estimator for least squares problems.
+     * <p>The maximal number of cost evaluations allowed is set
+     * to its default value {@link #DEFAULT_MAX_COST_EVALUATIONS}.</p>
+     */
+    protected AbstractEstimator() {
+        setMaxCostEval(DEFAULT_MAX_COST_EVALUATIONS);
+    }
+
+    /**
+     * Set the maximal number of cost evaluations allowed.
+     *
+     * @param maxCostEval maximal number of cost evaluations allowed
+     * @see #estimate
+     */
+    public final void setMaxCostEval(int maxCostEval) {
+        this.maxCostEval = maxCostEval;
+    }
+
+    /**
+     * Get the number of cost evaluations.
+     *
+     * @return number of cost evaluations
+     * */
+    public final int getCostEvaluations() {
+        return costEvaluations;
+    }
+
+    /**
+     * Get the number of jacobian evaluations.
+     *
+     * @return number of jacobian evaluations
+     * */
+    public final int getJacobianEvaluations() {
+        return jacobianEvaluations;
+    }
+
+    /**
+     * Update the jacobian matrix.
+     */
+    protected void updateJacobian() {
+        incrementJacobianEvaluationsCounter();
+        Arrays.fill(jacobian, 0);
+        int index = 0;
+        for (int i = 0; i < rows; i++) {
+            WeightedMeasurement wm = measurements[i];
+            double factor = -FastMath.sqrt(wm.getWeight());
+            for (int j = 0; j < cols; ++j) {
+                jacobian[index++] = factor * wm.getPartial(parameters[j]);
+            }
+        }
+    }
+
+    /**
+     * Increment the jacobian evaluations counter.
+     */
+    protected final void incrementJacobianEvaluationsCounter() {
+      ++jacobianEvaluations;
+    }
+
+    /**
+     * Update the residuals array and cost function value.
+     * @exception EstimationException if the number of cost evaluations
+     * exceeds the maximum allowed
+     */
+    protected void updateResidualsAndCost()
+    throws EstimationException {
+
+        if (++costEvaluations > maxCostEval) {
+            throw new EstimationException(LocalizedFormats.MAX_EVALUATIONS_EXCEEDED,
+                                          maxCostEval);
+        }
+
+        cost = 0;
+        int index = 0;
+        for (int i = 0; i < rows; i++, index += cols) {
+            WeightedMeasurement wm = measurements[i];
+            double residual = wm.getResidual();
+            residuals[i] = FastMath.sqrt(wm.getWeight()) * residual;
+            cost += wm.getWeight() * residual * residual;
+        }
+        cost = FastMath.sqrt(cost);
+
+    }
+
+    /**
+     * 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 estimator 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>.
+     *
+     * @param problem estimation problem
+     * @return RMS value
+     */
+    public double getRMS(EstimationProblem problem) {
+        WeightedMeasurement[] wm = problem.getMeasurements();
+        double criterion = 0;
+        for (int i = 0; i < wm.length; ++i) {
+            double residual = wm[i].getResidual();
+            criterion += wm[i].getWeight() * residual * residual;
+        }
+        return FastMath.sqrt(criterion / wm.length);
+    }
+
+    /**
+     * Get the Chi-Square value.
+     * @param problem estimation problem
+     * @return chi-square value
+     */
+    public double getChiSquare(EstimationProblem problem) {
+        WeightedMeasurement[] wm = problem.getMeasurements();
+        double chiSquare = 0;
+        for (int i = 0; i < wm.length; ++i) {
+            double residual = wm[i].getResidual();
+            chiSquare += residual * residual / wm[i].getWeight();
+        }
+        return chiSquare;
+    }
+
+    /**
+     * Get the covariance matrix of unbound estimated parameters.
+     * @param problem estimation problem
+     * @return covariance matrix
+     * @exception EstimationException if the covariance matrix
+     * cannot be computed (singular problem)
+     */
+    public double[][] getCovariances(EstimationProblem problem)
+      throws EstimationException {
+
+        // set up the jacobian
+        updateJacobian();
+
+        // compute transpose(J).J, avoiding building big intermediate matrices
+        final int n = problem.getMeasurements().length;
+        final int m = problem.getUnboundParameters().length;
+        final int max  = m * n;
+        double[][] jTj = new double[m][m];
+        for (int i = 0; i < m; ++i) {
+            for (int j = i; j < m; ++j) {
+                double sum = 0;
+                for (int k = 0; k < max; k += m) {
+                    sum += jacobian[k + i] * jacobian[k + j];
+                }
+                jTj[i][j] = sum;
+                jTj[j][i] = sum;
+            }
+        }
+
+        try {
+            // compute the covariances matrix
+            RealMatrix inverse =
+                new LUDecompositionImpl(MatrixUtils.createRealMatrix(jTj)).getSolver().getInverse();
+            return inverse.getData();
+        } catch (InvalidMatrixException ime) {
+            throw new EstimationException(LocalizedFormats.UNABLE_TO_COMPUTE_COVARIANCE_SINGULAR_PROBLEM);
+        }
+
+    }
+
+    /**
+     * Guess the errors in unbound estimated parameters.
+     * <p>Guessing is covariance-based, it only gives rough order of magnitude.</p>
+     * @param problem estimation problem
+     * @return errors in estimated parameters
+     * @exception EstimationException if the covariances matrix cannot be computed
+     * or the number of degrees of freedom is not positive (number of measurements
+     * lesser or equal to number of parameters)
+     */
+    public double[] guessParametersErrors(EstimationProblem problem)
+      throws EstimationException {
+        int m = problem.getMeasurements().length;
+        int p = problem.getUnboundParameters().length;
+        if (m <= p) {
+            throw new EstimationException(
+                    LocalizedFormats.NO_DEGREES_OF_FREEDOM,
+                    m, p);
+        }
+        double[] errors = new double[problem.getUnboundParameters().length];
+        final double c = FastMath.sqrt(getChiSquare(problem) / (m - p));
+        double[][] covar = getCovariances(problem);
+        for (int i = 0; i < errors.length; ++i) {
+            errors[i] = FastMath.sqrt(covar[i][i]) * c;
+        }
+        return errors;
+    }
+
+    /**
+     * Initialization of the common parts of the estimation.
+     * <p>This method <em>must</em> be called at the start
+     * of the {@link #estimate(EstimationProblem) estimate}
+     * method.</p>
+     * @param problem estimation problem to solve
+     */
+    protected void initializeEstimate(EstimationProblem problem) {
+
+        // reset counters
+        costEvaluations     = 0;
+        jacobianEvaluations = 0;
+
+        // retrieve the equations and the parameters
+        measurements = problem.getMeasurements();
+        parameters   = problem.getUnboundParameters();
+
+        // arrays shared with the other private methods
+        rows      = measurements.length;
+        cols      = parameters.length;
+        jacobian  = new double[rows * cols];
+        residuals = new double[rows];
+
+        cost = Double.POSITIVE_INFINITY;
+
+    }
+
+    /**
+     * Solve an estimation problem.
+     *
+     * <p>The method should set the parameters of the problem to several
+     * trial values until it reaches convergence. If this method returns
+     * normally (i.e. without throwing an exception), then the best
+     * estimate of the parameters can be retrieved from the problem
+     * itself, through the {@link EstimationProblem#getAllParameters
+     * EstimationProblem.getAllParameters} method.</p>
+     *
+     * @param problem estimation problem to solve
+     * @exception EstimationException if the problem cannot be solved
+     *
+     */
+    public abstract void estimate(EstimationProblem problem)
+    throws EstimationException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/estimation/EstimatedParameter.java b/src/main/java/org/apache/commons/math/estimation/EstimatedParameter.java
new file mode 100644
index 0000000..e0047bb
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/estimation/EstimatedParameter.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.estimation;
+
+import java.io.Serializable;
+
+/** This class represents the estimated parameters of an estimation problem.
+ *
+ * <p>The parameters of an estimation problem have a name, a value and
+ * a bound flag. The value of bound parameters is considered trusted
+ * and the solvers should not adjust them. On the other hand, the
+ * solvers should adjust the value of unbounds parameters until they
+ * satisfy convergence criterions specific to each solver.</p>
+ *
+ * @version $Revision: 922710 $ $Date: 2010-03-14 02:20:56 +0100 (dim. 14 mars 2010) $
+ * @since 1.2
+ * @deprecated as of 2.0, everything in package org.apache.commons.math.estimation has
+ * been deprecated and replaced by package org.apache.commons.math.optimization.general
+ *
+ */
+@Deprecated
+public class EstimatedParameter
+  implements Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -555440800213416949L;
+
+    /** Current value of the parameter */
+    protected double  estimate;
+
+    /** Name of the parameter */
+    private final String  name;
+
+    /** Indicator for bound parameters
+     * (ie parameters that should not be estimated)
+     */
+    private   boolean bound;
+
+    /** Simple constructor.
+     * Build an instance from a first estimate of the parameter,
+     * initially considered unbound.
+     * @param name name of the parameter
+     * @param firstEstimate first estimate of the parameter
+     */
+    public EstimatedParameter(String name, double firstEstimate) {
+        this.name = name;
+        estimate  = firstEstimate;
+        bound     = false;
+    }
+
+    /** Simple constructor.
+     * Build an instance from a first estimate of the parameter and a
+     * bound flag
+     * @param name name of the parameter
+     * @param firstEstimate first estimate of the parameter
+     * @param bound flag, should be true if the parameter is bound
+     */
+    public EstimatedParameter(String name,
+                              double firstEstimate,
+                              boolean bound) {
+        this.name  = name;
+        estimate   = firstEstimate;
+        this.bound = bound;
+    }
+
+    /** Copy constructor.
+     * Build a copy of a parameter
+     * @param parameter instance to copy
+     */
+    public EstimatedParameter(EstimatedParameter parameter) {
+        name     = parameter.name;
+        estimate = parameter.estimate;
+        bound    = parameter.bound;
+    }
+
+    /** Set a new estimated value for the parameter.
+     * @param estimate new estimate for the parameter
+     */
+    public void setEstimate(double estimate) {
+        this.estimate = estimate;
+    }
+
+    /** Get the current estimate of the parameter
+     * @return current estimate
+     */
+    public double getEstimate() {
+        return estimate;
+    }
+
+    /** get the name of the parameter
+     * @return parameter name
+     */
+    public String getName() {
+        return name;
+    }
+
+    /** Set the bound flag of the parameter
+     * @param bound this flag should be set to true if the parameter is
+     * bound (i.e. if it should not be adjusted by the solver).
+     */
+    public void setBound(boolean bound) {
+        this.bound = bound;
+    }
+
+    /** Check if the parameter is bound
+     * @return true if the parameter is bound */
+    public boolean isBound() {
+        return bound;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/estimation/EstimationException.java b/src/main/java/org/apache/commons/math/estimation/EstimationException.java
new file mode 100644
index 0000000..463ea49
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/estimation/EstimationException.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.math.estimation;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.exception.util.DummyLocalizable;
+import org.apache.commons.math.exception.util.Localizable;
+
+/**
+ * This class represents exceptions thrown by the estimation solvers.
+ *
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ * @since 1.2
+ * @deprecated as of 2.0, everything in package org.apache.commons.math.estimation has
+ * been deprecated and replaced by package org.apache.commons.math.optimization.general
+ *
+ */
+@Deprecated
+public class EstimationException
+extends MathException {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -573038581493881337L;
+
+    /**
+     * 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)
+     */
+    public EstimationException(String specifier, Object ... parts) {
+        this(new DummyLocalizable(specifier), parts);
+    }
+
+    /**
+     * 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 EstimationException(Localizable specifier, Object ... parts) {
+        super(specifier, parts);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/estimation/EstimationProblem.java b/src/main/java/org/apache/commons/math/estimation/EstimationProblem.java
new file mode 100644
index 0000000..a0d660b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/estimation/EstimationProblem.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.math.estimation;
+
+/**
+ * This interface represents an estimation problem.
+ *
+ * <p>This interface should be implemented by all real estimation
+ * problems before they can be handled by the estimators through the
+ * {@link Estimator#estimate Estimator.estimate} method.</p>
+ *
+ * <p>An estimation problem, as seen by a solver is a set of
+ * parameters and a set of measurements. The parameters are adjusted
+ * during the estimation through the {@link #getUnboundParameters
+ * getUnboundParameters} and {@link EstimatedParameter#setEstimate
+ * EstimatedParameter.setEstimate} methods. The measurements both have
+ * a measured value which is generally fixed at construction and a
+ * theoretical value which depends on the model and hence varies as
+ * the parameters are adjusted. The purpose of the solver is to reduce
+ * the residual between these values, it can retrieve the measurements
+ * through the {@link #getMeasurements getMeasurements} method.</p>
+ *
+ * @see Estimator
+ * @see WeightedMeasurement
+ *
+ * @version $Revision: 811786 $ $Date: 2009-09-06 11:36:08 +0200 (dim. 06 sept. 2009) $
+ * @since 1.2
+ * @deprecated as of 2.0, everything in package org.apache.commons.math.estimation has
+ * been deprecated and replaced by package org.apache.commons.math.optimization.general
+ *
+ */
+@Deprecated
+public interface EstimationProblem {
+
+    /**
+     * Get the measurements of an estimation problem.
+     * @return measurements
+     */
+    WeightedMeasurement[] getMeasurements();
+
+    /**
+     * Get the unbound parameters of the problem.
+     * @return unbound parameters
+     */
+    EstimatedParameter[] getUnboundParameters();
+
+    /**
+     * Get all the parameters of the problem.
+     * @return parameters
+     */
+    EstimatedParameter[] getAllParameters();
+
+}
diff --git a/src/main/java/org/apache/commons/math/estimation/Estimator.java b/src/main/java/org/apache/commons/math/estimation/Estimator.java
new file mode 100644
index 0000000..5d6d85a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/estimation/Estimator.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.math.estimation;
+
+/**
+ * This interface represents solvers for estimation problems.
+ *
+ * <p>The classes which are devoted to solve estimation problems
+ * should implement this interface. The problems which can be handled
+ * should implement the {@link EstimationProblem} interface which
+ * gather all the information needed by the solver.</p>
+ *
+ * <p>The interface is composed only of the {@link #estimate estimate}
+ * method.</p>
+ *
+ * @see EstimationProblem
+ *
+ * @version $Revision: 811786 $ $Date: 2009-09-06 11:36:08 +0200 (dim. 06 sept. 2009) $
+ * @since 1.2
+ * @deprecated as of 2.0, everything in package org.apache.commons.math.estimation has
+ * been deprecated and replaced by package org.apache.commons.math.optimization.general
+ *
+ */
+@Deprecated
+public interface Estimator {
+
+  /**
+   * Solve an estimation problem.
+   *
+   * <p>The method should set the parameters of the problem to several
+   * trial values until it reaches convergence. If this method returns
+   * normally (i.e. without throwing an exception), then the best
+   * estimate of the parameters can be retrieved from the problem
+   * itself, through the {@link EstimationProblem#getAllParameters
+   * EstimationProblem.getAllParameters} method.</p>
+   *
+   * @param problem estimation problem to solve
+   * @exception EstimationException if the problem cannot be solved
+   *
+   */
+  void estimate(EstimationProblem problem) throws EstimationException;
+
+  /**
+   * 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 estimator as follows: if
+   * <em>c</em> is the criterion, and <em>n</em> is the number of
+   * measurements, then the RMS is <em>sqrt (c/n)</em>.
+   * @see #guessParametersErrors(EstimationProblem)
+   *
+   * @param problem estimation problem
+   * @return RMS value
+   */
+  double getRMS(EstimationProblem problem);
+
+  /**
+   * Get the covariance matrix of estimated parameters.
+   * @param problem estimation problem
+   * @return covariance matrix
+   * @exception EstimationException if the covariance matrix
+   * cannot be computed (singular problem)
+   */
+  double[][] getCovariances(EstimationProblem problem) throws EstimationException;
+
+  /**
+   * Guess the errors in estimated parameters.
+   * @see #getRMS(EstimationProblem)
+   * @param problem estimation problem
+   * @return errors in estimated parameters
+     * @exception EstimationException if the error cannot be guessed
+   */
+  double[] guessParametersErrors(EstimationProblem problem) throws EstimationException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/estimation/GaussNewtonEstimator.java b/src/main/java/org/apache/commons/math/estimation/GaussNewtonEstimator.java
new file mode 100644
index 0000000..c5d0dd3
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/estimation/GaussNewtonEstimator.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.math.estimation;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.linear.InvalidMatrixException;
+import org.apache.commons.math.linear.LUDecompositionImpl;
+import org.apache.commons.math.linear.MatrixUtils;
+import org.apache.commons.math.linear.RealMatrix;
+import org.apache.commons.math.linear.RealVector;
+import org.apache.commons.math.linear.ArrayRealVector;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * This class implements a solver for estimation problems.
+ *
+ * <p>This class solves estimation problems using a weighted least
+ * squares criterion on the measurement residuals. It uses a
+ * Gauss-Newton algorithm.</p>
+ *
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ * @since 1.2
+ * @deprecated as of 2.0, everything in package org.apache.commons.math.estimation has
+ * been deprecated and replaced by package org.apache.commons.math.optimization.general
+ *
+ */
+@Deprecated
+public class GaussNewtonEstimator extends AbstractEstimator implements Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 5485001826076289109L;
+
+    /** Default threshold for cost steady state detection. */
+    private static final double DEFAULT_STEADY_STATE_THRESHOLD = 1.0e-6;
+
+    /** Default threshold for cost convergence. */
+    private static final double DEFAULT_CONVERGENCE = 1.0e-6;
+
+    /** Threshold for cost steady state detection. */
+    private double steadyStateThreshold;
+
+    /** Threshold for cost convergence. */
+    private double convergence;
+
+    /** Simple constructor with default settings.
+     * <p>
+     * The estimator is built with default values for all settings.
+     * </p>
+     * @see #DEFAULT_STEADY_STATE_THRESHOLD
+     * @see #DEFAULT_CONVERGENCE
+     * @see AbstractEstimator#DEFAULT_MAX_COST_EVALUATIONS
+     */
+    public GaussNewtonEstimator() {
+        this.steadyStateThreshold = DEFAULT_STEADY_STATE_THRESHOLD;
+        this.convergence          = DEFAULT_CONVERGENCE;
+    }
+
+    /**
+     * Simple constructor.
+     *
+     * <p>This constructor builds an estimator and stores its convergence
+     * characteristics.</p>
+     *
+     * <p>An estimator is considered to have converged whenever either
+     * the criterion goes below a physical threshold under which
+     * improvements are considered useless or when the algorithm is
+     * unable to improve it (even if it is still high). The first
+     * condition that is met stops the iterations.</p>
+     *
+     * <p>The fact an estimator has converged does not mean that the
+     * model accurately fits the measurements. It only means no better
+     * solution can be found, it does not mean this one is good. Such an
+     * analysis is left to the caller.</p>
+     *
+     * <p>If neither conditions are fulfilled before a given number of
+     * iterations, the algorithm is considered to have failed and an
+     * {@link EstimationException} is thrown.</p>
+     *
+     * @param maxCostEval maximal number of cost evaluations allowed
+     * @param convergence criterion threshold below which we do not need
+     * to improve the criterion anymore
+     * @param steadyStateThreshold steady state detection threshold, the
+     * problem has converged has reached a steady state if
+     * <code>FastMath.abs(J<sub>n</sub> - J<sub>n-1</sub>) &lt;
+     * J<sub>n</sub> &times convergence</code>, where <code>J<sub>n</sub></code>
+     * and <code>J<sub>n-1</sub></code> are the current and preceding criterion
+     * values (square sum of the weighted residuals of considered measurements).
+     */
+    public GaussNewtonEstimator(final int maxCostEval, final double convergence,
+                                final double steadyStateThreshold) {
+        setMaxCostEval(maxCostEval);
+        this.steadyStateThreshold = steadyStateThreshold;
+        this.convergence          = convergence;
+    }
+
+    /**
+     * Set the convergence criterion threshold.
+     * @param convergence criterion threshold below which we do not need
+     * to improve the criterion anymore
+     */
+    public void setConvergence(final double convergence) {
+        this.convergence = convergence;
+    }
+
+    /**
+     * Set the steady state detection threshold.
+     * <p>
+     * The problem has converged has reached a steady state if
+     * <code>FastMath.abs(J<sub>n</sub> - J<sub>n-1</sub>) &lt;
+     * J<sub>n</sub> &times convergence</code>, where <code>J<sub>n</sub></code>
+     * and <code>J<sub>n-1</sub></code> are the current and preceding criterion
+     * values (square sum of the weighted residuals of considered measurements).
+     * </p>
+     * @param steadyStateThreshold steady state detection threshold
+     */
+    public void setSteadyStateThreshold(final double steadyStateThreshold) {
+        this.steadyStateThreshold = steadyStateThreshold;
+    }
+
+    /**
+     * Solve an estimation problem using a least squares criterion.
+     *
+     * <p>This method set the unbound parameters of the given problem
+     * starting from their current values through several iterations. At
+     * each step, the unbound parameters are changed in order to
+     * minimize a weighted least square criterion based on the
+     * measurements of the problem.</p>
+     *
+     * <p>The iterations are stopped either when the criterion goes
+     * below a physical threshold under which improvement are considered
+     * useless or when the algorithm is unable to improve it (even if it
+     * is still high). The first condition that is met stops the
+     * iterations. If the convergence it not reached before the maximum
+     * number of iterations, an {@link EstimationException} is
+     * thrown.</p>
+     *
+     * @param problem estimation problem to solve
+     * @exception EstimationException if the problem cannot be solved
+     *
+     * @see EstimationProblem
+     *
+     */
+    @Override
+    public void estimate(EstimationProblem problem)
+    throws EstimationException {
+
+        initializeEstimate(problem);
+
+        // work matrices
+        double[] grad             = new double[parameters.length];
+        ArrayRealVector bDecrement = new ArrayRealVector(parameters.length);
+        double[] bDecrementData   = bDecrement.getDataRef();
+        RealMatrix wGradGradT     = MatrixUtils.createRealMatrix(parameters.length, parameters.length);
+
+        // iterate until convergence is reached
+        double previous = Double.POSITIVE_INFINITY;
+        do {
+
+            // build the linear problem
+            incrementJacobianEvaluationsCounter();
+            RealVector b = new ArrayRealVector(parameters.length);
+            RealMatrix a = MatrixUtils.createRealMatrix(parameters.length, parameters.length);
+            for (int i = 0; i < measurements.length; ++i) {
+                if (! measurements [i].isIgnored()) {
+
+                    double weight   = measurements[i].getWeight();
+                    double residual = measurements[i].getResidual();
+
+                    // compute the normal equation
+                    for (int j = 0; j < parameters.length; ++j) {
+                        grad[j] = measurements[i].getPartial(parameters[j]);
+                        bDecrementData[j] = weight * residual * grad[j];
+                    }
+
+                    // build the contribution matrix for measurement i
+                    for (int k = 0; k < parameters.length; ++k) {
+                        double gk = grad[k];
+                        for (int l = 0; l < parameters.length; ++l) {
+                            wGradGradT.setEntry(k, l, weight * gk * grad[l]);
+                        }
+                    }
+
+                    // update the matrices
+                    a = a.add(wGradGradT);
+                    b = b.add(bDecrement);
+
+                }
+            }
+
+            try {
+
+                // solve the linearized least squares problem
+                RealVector dX = new LUDecompositionImpl(a).getSolver().solve(b);
+
+                // update the estimated parameters
+                for (int i = 0; i < parameters.length; ++i) {
+                    parameters[i].setEstimate(parameters[i].getEstimate() + dX.getEntry(i));
+                }
+
+            } catch(InvalidMatrixException e) {
+                throw new EstimationException(LocalizedFormats.UNABLE_TO_SOLVE_SINGULAR_PROBLEM);
+            }
+
+
+            previous = cost;
+            updateResidualsAndCost();
+
+        } while ((getCostEvaluations() < 2) ||
+                 (FastMath.abs(previous - cost) > (cost * steadyStateThreshold) &&
+                  (FastMath.abs(cost) > convergence)));
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/estimation/LevenbergMarquardtEstimator.java b/src/main/java/org/apache/commons/math/estimation/LevenbergMarquardtEstimator.java
new file mode 100644
index 0000000..78a4661
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/estimation/LevenbergMarquardtEstimator.java
@@ -0,0 +1,897 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.estimation;
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.util.FastMath;
+
+
+/**
+ * This class solves a least squares problem.
+ *
+ * <p>This implementation <em>should</em> work even for over-determined systems
+ * (i.e. systems having more variables than equations). Over-determined systems
+ * are solved by ignoring the variables 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 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>
+
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ * @since 1.2
+ * @deprecated as of 2.0, everything in package org.apache.commons.math.estimation has
+ * been deprecated and replaced by package org.apache.commons.math.optimization.general
+ *
+ */
+@Deprecated
+public class LevenbergMarquardtEstimator extends AbstractEstimator implements Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -5705952631533171019L;
+
+    /** Number of solved variables. */
+    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 double initialStepBoundFactor;
+
+    /** Desired relative error in the sum of squares. */
+    private double costRelativeTolerance;
+
+    /**  Desired relative error in the approximate solution parameters. */
+    private double parRelativeTolerance;
+
+    /** Desired max cosine on the orthogonality between the function vector
+     * and the columns of the jacobian. */
+    private double orthoTolerance;
+
+  /**
+   * Build an estimator for least squares problems.
+   * <p>The default values for the algorithm settings are:
+   *   <ul>
+   *    <li>{@link #setInitialStepBoundFactor initial step bound factor}: 100.0</li>
+   *    <li>{@link #setMaxCostEval maximal cost evaluations}: 1000</li>
+   *    <li>{@link #setCostRelativeTolerance cost relative tolerance}: 1.0e-10</li>
+   *    <li>{@link #setParRelativeTolerance parameters relative tolerance}: 1.0e-10</li>
+   *    <li>{@link #setOrthoTolerance orthogonality tolerance}: 1.0e-10</li>
+   *   </ul>
+   * </p>
+   */
+  public LevenbergMarquardtEstimator() {
+
+    // set up the superclass with a default  max cost evaluations setting
+    setMaxCostEval(1000);
+
+    // default values for the tuning parameters
+    setInitialStepBoundFactor(100.0);
+    setCostRelativeTolerance(1.0e-10);
+    setParRelativeTolerance(1.0e-10);
+    setOrthoTolerance(1.0e-10);
+
+  }
+
+  /**
+   * Set the positive input variable used in determining the initial step bound.
+   * This bound is set to the product of initialStepBoundFactor and the euclidean norm of diag*x if nonzero,
+   * or else to initialStepBoundFactor itself. In most cases factor should lie
+   * in the interval (0.1, 100.0). 100.0 is a generally recommended value
+   *
+   * @param initialStepBoundFactor initial step bound factor
+   * @see #estimate
+   */
+  public void setInitialStepBoundFactor(double initialStepBoundFactor) {
+    this.initialStepBoundFactor = initialStepBoundFactor;
+  }
+
+  /**
+   * Set the desired relative error in the sum of squares.
+   *
+   * @param costRelativeTolerance desired relative error in the sum of squares
+   * @see #estimate
+   */
+  public void setCostRelativeTolerance(double costRelativeTolerance) {
+    this.costRelativeTolerance = costRelativeTolerance;
+  }
+
+  /**
+   * Set the desired relative error in the approximate solution parameters.
+   *
+   * @param parRelativeTolerance desired relative error
+   * in the approximate solution parameters
+   * @see #estimate
+   */
+  public void setParRelativeTolerance(double parRelativeTolerance) {
+    this.parRelativeTolerance = parRelativeTolerance;
+  }
+
+  /**
+   * Set the desired max cosine on the orthogonality.
+   *
+   * @param orthoTolerance desired max cosine on the orthogonality
+   * between the function vector and the columns of the jacobian
+   * @see #estimate
+   */
+  public void setOrthoTolerance(double orthoTolerance) {
+    this.orthoTolerance = orthoTolerance;
+  }
+
+  /**
+   * Solve an estimation problem using the Levenberg-Marquardt algorithm.
+   * <p>The algorithm used is a modified Levenberg-Marquardt one, based
+   * on the MINPACK <a href="http://www.netlib.org/minpack/lmder.f">lmder</a>
+   * routine. The algorithm settings must have been set up before this method
+   * is called with the {@link #setInitialStepBoundFactor},
+   * {@link #setMaxCostEval}, {@link #setCostRelativeTolerance},
+   * {@link #setParRelativeTolerance} and {@link #setOrthoTolerance} methods.
+   * If these methods have not been called, the default values set up by the
+   * {@link #LevenbergMarquardtEstimator() constructor} will be used.</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 problem estimation problem to solve
+   * @exception EstimationException if convergence cannot be
+   * reached with the specified algorithm settings or if there are more variables
+   * than equations
+   * @see #setInitialStepBoundFactor
+   * @see #setCostRelativeTolerance
+   * @see #setParRelativeTolerance
+   * @see #setOrthoTolerance
+   */
+  @Override
+  public void estimate(EstimationProblem problem)
+    throws EstimationException {
+
+    initializeEstimate(problem);
+
+    // arrays shared with the other private methods
+    solvedCols  = FastMath.min(rows, cols);
+    diagR       = new double[cols];
+    jacNorm     = new double[cols];
+    beta        = new double[cols];
+    permutation = new int[cols];
+    lmDir       = new double[cols];
+
+    // local variables
+    double   delta   = 0;
+    double   xNorm = 0;
+    double[] diag    = new double[cols];
+    double[] oldX    = new double[cols];
+    double[] oldRes  = new double[rows];
+    double[] work1   = new double[cols];
+    double[] work2   = new double[cols];
+    double[] work3   = new double[cols];
+
+    // evaluate the function at the starting point and calculate its norm
+    updateResidualsAndCost();
+
+    // outer loop
+    lmPar = 0;
+    boolean firstIteration = true;
+    while (true) {
+
+      // compute the Q.R. decomposition of the jacobian matrix
+      updateJacobian();
+      qrDecomposition();
+
+      // compute Qt.res
+      qTy(residuals);
+
+      // 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];
+        jacobian[k * cols + pk] = diagR[pk];
+      }
+
+      if (firstIteration) {
+
+        // scale the variables according to the norms of the columns
+        // of the initial jacobian
+        xNorm = 0;
+        for (int k = 0; k < cols; ++k) {
+          double dk = jacNorm[k];
+          if (dk == 0) {
+            dk = 1.0;
+          }
+          double xk = dk * parameters[k].getEstimate();
+          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 (cost != 0) {
+        for (int j = 0; j < solvedCols; ++j) {
+          int    pj = permutation[j];
+          double s  = jacNorm[pj];
+          if (s != 0) {
+            double sum = 0;
+            int index = pj;
+            for (int i = 0; i <= j; ++i) {
+              sum += jacobian[index] * residuals[i];
+              index += cols;
+            }
+            maxCosine = FastMath.max(maxCosine, FastMath.abs(sum) / (s * cost));
+          }
+        }
+      }
+      if (maxCosine <= orthoTolerance) {
+        return;
+      }
+
+      // rescale if necessary
+      for (int j = 0; j < cols; ++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] = parameters[pj].getEstimate();
+        }
+        double previousCost = cost;
+        double[] tmpVec = residuals;
+        residuals = oldRes;
+        oldRes    = tmpVec;
+
+        // determine the Levenberg-Marquardt parameter
+        determineLMParameter(oldRes, 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];
+          parameters[pj].setEstimate(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
+        updateResidualsAndCost();
+
+        // compute the scaled actual reduction
+        double actRed = -1.0;
+        if (0.1 * cost < previousCost) {
+          double r = cost / 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;
+          int index = pj;
+          for (int i = 0; i <= j; ++i) {
+            work1[i] += jacobian[index] * dirJ;
+            index += cols;
+          }
+        }
+        double coeff1 = 0;
+        for (int j = 0; j < solvedCols; ++j) {
+         coeff1 += work1[j] * work1[j];
+        }
+        double pc2 = previousCost * previousCost;
+        coeff1 = 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 * cost >= 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 < cols; ++k) {
+            double xK = diag[k] * parameters[k].getEstimate();
+            xNorm    += xK * xK;
+          }
+          xNorm = FastMath.sqrt(xNorm);
+        } else {
+          // failed iteration, reset the previous values
+          cost = previousCost;
+          for (int j = 0; j < solvedCols; ++j) {
+            int pj = permutation[j];
+            parameters[pj].setEstimate(oldX[pj]);
+          }
+          tmpVec    = residuals;
+          residuals = oldRes;
+          oldRes    = tmpVec;
+        }
+
+        // tests for convergence.
+        if (((FastMath.abs(actRed) <= costRelativeTolerance) &&
+             (preRed <= costRelativeTolerance) &&
+             (ratio <= 2.0)) ||
+             (delta <= parRelativeTolerance * xNorm)) {
+          return;
+        }
+
+        // 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 EstimationException("cost relative tolerance is too small ({0})," +
+                                        " no further reduction in the" +
+                                        " sum of squares is possible",
+                                        costRelativeTolerance);
+        } else if (delta <= 2.2204e-16 * xNorm) {
+          throw new EstimationException("parameters relative tolerance is too small" +
+                                        " ({0}), no further improvement in" +
+                                        " the approximate solution is possible",
+                                        parRelativeTolerance);
+        } else if (maxCosine <= 2.2204e-16)  {
+          throw new EstimationException("orthogonality tolerance is too small ({0})," +
+                                        " solution is orthogonal to the jacobian",
+                                        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) {
+
+    // 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 < cols; ++j) {
+      lmDir[permutation[j]] = 0;
+    }
+    for (int k = rank - 1; k >= 0; --k) {
+      int pk = permutation[k];
+      double ypk = lmDir[pk] / diagR[pk];
+      int index = pk;
+      for (int i = 0; i < k; ++i) {
+        lmDir[permutation[i]] -= ypk * jacobian[index];
+        index += cols;
+      }
+      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;
+        int index = pj;
+        for (int i = 0; i < j; ++i) {
+          sum += jacobian[index] * work1[permutation[i]];
+          index += cols;
+        }
+        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;
+      int index = pj;
+      for (int i = 0; i <= j; ++i) {
+        sum += jacobian[index] * qy[i];
+        index += cols;
+      }
+      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]] -= jacobian[i * cols + 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) {
+        jacobian[i * cols + pj] = jacobian[j * cols + 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 = jacobian[k * cols + 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)
+          jacobian[k * cols + 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 = jacobian[i * cols + pk];
+            final double temp2 = cos * rik + sin * lmDiag[i];
+            lmDiag[i] = -sin * rik + cos * lmDiag[i];
+            jacobian[i * cols + pk] = temp2;
+          }
+
+        }
+      }
+
+      // store the diagonal element of s and restore
+      // the corresponding diagonal element of R
+      int index = j * cols + permutation[j];
+      lmDiag[j]       = jacobian[index];
+      jacobian[index] = 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 += jacobian[i * cols + 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>
+   * @exception EstimationException if the decomposition cannot be performed
+   */
+  private void qrDecomposition() throws EstimationException {
+
+    // initializations
+    for (int k = 0; k < cols; ++k) {
+      permutation[k] = k;
+      double norm2 = 0;
+      for (int index = k; index < jacobian.length; index += cols) {
+        double akk = jacobian[index];
+        norm2 += akk * akk;
+      }
+      jacNorm[k] = FastMath.sqrt(norm2);
+    }
+
+    // transform the matrix column after column
+    for (int k = 0; k < cols; ++k) {
+
+      // select the column with the greatest norm on active components
+      int nextColumn = -1;
+      double ak2 = Double.NEGATIVE_INFINITY;
+      for (int i = k; i < cols; ++i) {
+        double norm2 = 0;
+        int iDiag = k * cols + permutation[i];
+        for (int index = iDiag; index < jacobian.length; index += cols) {
+          double aki = jacobian[index];
+          norm2 += aki * aki;
+        }
+        if (Double.isInfinite(norm2) || Double.isNaN(norm2)) {
+            throw new EstimationException(
+                    LocalizedFormats.UNABLE_TO_PERFORM_QR_DECOMPOSITION_ON_JACOBIAN,
+                    rows, cols);
+        }
+        if (norm2 > ak2) {
+          nextColumn = i;
+          ak2        = norm2;
+        }
+      }
+      if (ak2 == 0) {
+        rank = k;
+        return;
+      }
+      int pk                  = permutation[nextColumn];
+      permutation[nextColumn] = permutation[k];
+      permutation[k]          = pk;
+
+      // choose alpha such that Hk.u = alpha ek
+      int    kDiag = k * cols + pk;
+      double akk   = jacobian[kDiag];
+      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;
+      jacobian[kDiag] -= alpha;
+
+      // transform the remaining columns
+      for (int dk = cols - 1 - k; dk > 0; --dk) {
+        int dkp = permutation[k + dk] - pk;
+        double gamma = 0;
+        for (int index = kDiag; index < jacobian.length; index += cols) {
+          gamma += jacobian[index] * jacobian[index + dkp];
+        }
+        gamma *= betak;
+        for (int index = kDiag; index < jacobian.length; index += cols) {
+          jacobian[index + dkp] -= gamma * jacobian[index];
+        }
+      }
+
+    }
+
+    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) {
+    for (int k = 0; k < cols; ++k) {
+      int pk = permutation[k];
+      int kDiag = k * cols + pk;
+      double gamma = 0;
+      int index = kDiag;
+      for (int i = k; i < rows; ++i) {
+        gamma += jacobian[index] * y[i];
+        index += cols;
+      }
+      gamma *= beta[pk];
+      index = kDiag;
+      for (int i = k; i < rows; ++i) {
+        y[i] -= gamma * jacobian[index];
+        index += cols;
+      }
+    }
+  }
+
+}
diff --git a/src/main/java/org/apache/commons/math/estimation/SimpleEstimationProblem.java b/src/main/java/org/apache/commons/math/estimation/SimpleEstimationProblem.java
new file mode 100644
index 0000000..d9449d0
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/estimation/SimpleEstimationProblem.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.math.estimation;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Simple implementation of the {@link EstimationProblem
+ * EstimationProblem} interface for boilerplate data handling.
+ * <p>This class <em>only</em> handles parameters and measurements
+ * storage and unbound parameters filtering. It does not compute
+ * anything by itself. It should either be used with measurements
+ * implementation that are smart enough to know about the
+ * various parameters in order to compute the partial derivatives
+ * appropriately. Since the problem-specific logic is mainly related to
+ * the various measurements models, the simplest way to use this class
+ * is by extending it and using one internal class extending
+ * {@link WeightedMeasurement WeightedMeasurement} for each measurement
+ * type. The instances of the internal classes would have access to the
+ * various parameters and their current estimate.</p>
+
+ * @version $Revision: 811827 $ $Date: 2009-09-06 17:32:50 +0200 (dim. 06 sept. 2009) $
+ * @since 1.2
+ * @deprecated as of 2.0, everything in package org.apache.commons.math.estimation has
+ * been deprecated and replaced by package org.apache.commons.math.optimization.general
+
+ */
+@Deprecated
+public class SimpleEstimationProblem implements EstimationProblem {
+
+    /** Estimated parameters. */
+    private final List<EstimatedParameter> parameters;
+
+    /** Measurements. */
+    private final List<WeightedMeasurement> measurements;
+
+    /**
+     * Build an empty instance without parameters nor measurements.
+     */
+    public SimpleEstimationProblem() {
+        parameters   = new ArrayList<EstimatedParameter>();
+        measurements = new ArrayList<WeightedMeasurement>();
+    }
+
+    /**
+     * Get all the parameters of the problem.
+     * @return parameters
+     */
+    public EstimatedParameter[] getAllParameters() {
+        return parameters.toArray(new EstimatedParameter[parameters.size()]);
+    }
+
+    /**
+     * Get the unbound parameters of the problem.
+     * @return unbound parameters
+     */
+    public EstimatedParameter[] getUnboundParameters() {
+
+        // filter the unbound parameters
+        List<EstimatedParameter> unbound = new ArrayList<EstimatedParameter>(parameters.size());
+        for (EstimatedParameter p : parameters) {
+            if (! p.isBound()) {
+                unbound.add(p);
+            }
+        }
+
+        // convert to an array
+        return unbound.toArray(new EstimatedParameter[unbound.size()]);
+
+    }
+
+    /**
+     * Get the measurements of an estimation problem.
+     * @return measurements
+     */
+    public WeightedMeasurement[] getMeasurements() {
+        return measurements.toArray(new WeightedMeasurement[measurements.size()]);
+    }
+
+    /** Add a parameter to the problem.
+     * @param p parameter to add
+     */
+    protected void addParameter(EstimatedParameter p) {
+        parameters.add(p);
+    }
+
+    /**
+     * Add a new measurement to the set.
+     * @param m measurement to add
+     */
+    protected void addMeasurement(WeightedMeasurement m) {
+        measurements.add(m);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/estimation/WeightedMeasurement.java b/src/main/java/org/apache/commons/math/estimation/WeightedMeasurement.java
new file mode 100644
index 0000000..cc6ac9f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/estimation/WeightedMeasurement.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.math.estimation;
+
+import java.io.Serializable;
+
+/**
+ * This class represents measurements in estimation problems.
+ *
+ * <p>This abstract class implements all the methods needed to handle
+ * measurements in a general way. It defines neither the {@link
+ * #getTheoreticalValue getTheoreticalValue} nor the {@link
+ * #getPartial getPartial} methods, which should be defined by
+ * sub-classes according to the specific problem.</p>
+ *
+ * <p>The {@link #getTheoreticalValue getTheoreticalValue} and {@link
+ * #getPartial getPartial} methods must always use the current
+ * estimate of the parameters set by the solver in the problem. These
+ * parameters can be retrieved through the {@link
+ * EstimationProblem#getAllParameters
+ * EstimationProblem.getAllParameters} method if the measurements are
+ * independent of the problem, or directly if they are implemented as
+ * inner classes of the problem.</p>
+ *
+ * <p>The instances for which the <code>ignored</code> flag is set
+ * through the {@link #setIgnored setIgnored} method are ignored by the
+ * solvers. This can be used to reject wrong measurements at some
+ * steps of the estimation.</p>
+ *
+ * @see EstimationProblem
+ *
+ * @version $Revision: 811827 $ $Date: 2009-09-06 17:32:50 +0200 (dim. 06 sept. 2009) $
+ * @since 1.2
+ * @deprecated as of 2.0, everything in package org.apache.commons.math.estimation has
+ * been deprecated and replaced by package org.apache.commons.math.optimization.general
+ */
+
+@Deprecated
+public abstract class WeightedMeasurement implements Serializable {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 4360046376796901941L;
+
+    /** Measurement weight. */
+    private final double  weight;
+
+    /** Value of the measurements. */
+    private final double  measuredValue;
+
+    /** Ignore measurement indicator. */
+    private boolean ignored;
+
+  /**
+   * Simple constructor.
+   * Build a measurement with the given parameters, and set its ignore
+   * flag to false.
+   * @param weight weight of the measurement in the least squares problem
+   * (two common choices are either to use 1.0 for all measurements, or to
+   * use a value proportional to the inverse of the variance of the measurement
+   * type)
+   *
+   * @param measuredValue measured value
+   */
+  public WeightedMeasurement(double weight, double measuredValue) {
+    this.weight        = weight;
+    this.measuredValue = measuredValue;
+    ignored            = false;
+  }
+
+  /** Simple constructor.
+   *
+   * Build a measurement with the given parameters
+   *
+   * @param weight weight of the measurement in the least squares problem
+   * @param measuredValue measured value
+   * @param ignored true if the measurement should be ignored
+   */
+  public WeightedMeasurement(double weight, double measuredValue,
+                             boolean ignored) {
+    this.weight        = weight;
+    this.measuredValue = measuredValue;
+    this.ignored       = ignored;
+  }
+
+  /**
+   * Get the weight of the measurement in the least squares problem
+   *
+   * @return weight
+   */
+  public double getWeight() {
+    return weight;
+  }
+
+  /**
+   * Get the measured value
+   *
+   * @return measured value
+   */
+  public double getMeasuredValue() {
+    return measuredValue;
+  }
+
+  /**
+   * Get the residual for this measurement
+   * The residual is the measured value minus the theoretical value.
+   *
+   * @return residual
+   */
+  public double getResidual() {
+    return measuredValue - getTheoreticalValue();
+  }
+
+  /**
+   * Get the theoretical value expected for this measurement
+   * <p>The theoretical value is the value expected for this measurement
+   * if the model and its parameter were all perfectly known.</p>
+   * <p>The value must be computed using the current estimate of the parameters
+   * set by the solver in the problem.</p>
+   *
+   * @return theoretical value
+   */
+  public abstract double getTheoreticalValue();
+
+  /**
+   * Get the partial derivative of the {@link #getTheoreticalValue
+   * theoretical value} according to the parameter.
+   * <p>The value must be computed using the current estimate of the parameters
+   * set by the solver in the problem.</p>
+   *
+   * @param parameter parameter against which the partial derivative
+   * should be computed
+   * @return partial derivative of the {@link #getTheoreticalValue
+   * theoretical value}
+   */
+  public abstract double getPartial(EstimatedParameter parameter);
+
+  /**
+   * Set the ignore flag to the specified value
+   * Setting the ignore flag to true allow to reject wrong
+   * measurements, which sometimes can be detected only rather late.
+   *
+   * @param ignored value for the ignore flag
+   */
+  public void setIgnored(boolean ignored) {
+    this.ignored = ignored;
+  }
+
+  /**
+   * Check if this measurement should be ignored
+   *
+   * @return true if the measurement should be ignored
+   */
+  public boolean isIgnored() {
+    return ignored;
+  }
+
+}
diff --git a/src/main/java/org/apache/commons/math/estimation/package.html b/src/main/java/org/apache/commons/math/estimation/package.html
new file mode 100644
index 0000000..2f4dc05
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/estimation/package.html
@@ -0,0 +1,25 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 754732 $ -->
+<body>
+This package provided classes to solve estimation problems, it is deprecated since 2.0.
+
+<p>This package has been deprecated as of 2.0. It is replaced by the optimization.general package.</p>
+
+</body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/exception/ConvergenceException.java b/src/main/java/org/apache/commons/math/exception/ConvergenceException.java
new file mode 100644
index 0000000..0e1a976
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/exception/ConvergenceException.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.math.exception;
+
+import org.apache.commons.math.exception.util.Localizable;
+import org.apache.commons.math.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
+ * @version $Revision: 1026666 $ $Date: 2010-10-23 21:30:48 +0200 (sam. 23 oct. 2010) $
+ */
+public class ConvergenceException extends MathIllegalStateException {
+    /** Serializable version Id. */
+    private static final long serialVersionUID = 4330003017885151975L;
+
+    /**
+     * Construct the exception.
+     */
+    public ConvergenceException() {
+        this(null);
+    }
+    /**
+     * Construct the exception with a specific context.
+     *
+     * @param specific Specific contexte pattern.
+     */
+    public ConvergenceException(Localizable specific) {
+        this(specific,
+             LocalizedFormats.CONVERGENCE_FAILED,
+             null);
+    }
+    /**
+     * Construct the exception with a specific context and arguments.
+     *
+     * @param specific Specific contexte pattern.
+     * @param args Arguments.
+     */
+    public ConvergenceException(Localizable specific,
+                                Object ... args) {
+        super(specific,
+              LocalizedFormats.CONVERGENCE_FAILED,
+              args);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/exception/DimensionMismatchException.java b/src/main/java/org/apache/commons/math/exception/DimensionMismatchException.java
new file mode 100644
index 0000000..d6a522d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/exception/DimensionMismatchException.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.math.exception;
+
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * Exception to be thrown when two dimensions differ.
+ *
+ * @since 2.2
+ * @version $Revision$ $Date$
+ */
+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 wrong Wrong dimension.
+     * @param expected Expected dimension.
+     */
+    public DimensionMismatchException(int wrong,
+                                      int expected) {
+        super(LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, wrong, expected);
+        dimension = expected;
+    }
+
+    /**
+     * @return the expected dimension.
+     */
+    public int getDimension() {
+        return dimension;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/exception/MathIllegalArgumentException.java b/src/main/java/org/apache/commons/math/exception/MathIllegalArgumentException.java
new file mode 100644
index 0000000..fb0ed88
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/exception/MathIllegalArgumentException.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.math.exception;
+
+import java.util.Locale;
+
+import org.apache.commons.math.exception.util.ArgUtils;
+import org.apache.commons.math.exception.util.MessageFactory;
+import org.apache.commons.math.exception.util.Localizable;
+
+/**
+ * Base class for all preconditions violation exceptions.
+ * This class is not intended to be instantiated directly: it should serve
+ * as a base class to create all the exceptions that share the semantics of
+ * the standard {@link IllegalArgumentException}, but must also provide a
+ * localized message.
+ *
+ * @since 2.2
+ * @version $Revision$ $Date$
+ */
+public class MathIllegalArgumentException extends IllegalArgumentException implements MathThrowable {
+
+    /** Serializable version Id. */
+    private static final long serialVersionUID = -6024911025449780478L;
+
+    /**
+     * Pattern used to build the message (specific context).
+     */
+    private final Localizable specific;
+    /**
+     * Pattern used to build the message (general problem description).
+     */
+    private final Localizable general;
+    /**
+     * Arguments used to build the message.
+     */
+    private final Object[] arguments;
+
+    /**
+     * @param specific Message pattern providing the specific context of
+     * the error.
+     * @param general Message pattern explaining the cause of the error.
+     * @param args Arguments.
+     */
+    protected MathIllegalArgumentException(Localizable specific,
+                                           Localizable general,
+                                           Object ... args) {
+        this.specific = specific;
+        this.general = general;
+        arguments = ArgUtils.flatten(args);
+    }
+    /**
+     * @param general Message pattern explaining the cause of the error.
+     * @param args Arguments.
+     */
+    protected MathIllegalArgumentException(Localizable general,
+                                           Object ... args) {
+        this(null, general, args);
+    }
+
+    /** {@inheritDoc} */
+    public Localizable getSpecificPattern() {
+        return specific;
+    }
+
+    /** {@inheritDoc} */
+    public Localizable getGeneralPattern() {
+        return general;
+    }
+
+    /** {@inheritDoc} */
+    public Object[] getArguments() {
+        return arguments.clone();
+    }
+
+    /**
+     * Get 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 MessageFactory.buildMessage(locale, specific, general, arguments);
+    }
+
+   /** {@inheritDoc} */
+    @Override
+    public String getMessage() {
+        return getMessage(Locale.US);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String getLocalizedMessage() {
+        return getMessage(Locale.getDefault());
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/exception/MathIllegalNumberException.java b/src/main/java/org/apache/commons/math/exception/MathIllegalNumberException.java
new file mode 100644
index 0000000..b96ce20
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/exception/MathIllegalNumberException.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.math.exception;
+
+import org.apache.commons.math.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
+ * @version $Revision$ $Date$
+ */
+public class MathIllegalNumberException extends MathIllegalArgumentException {
+
+    /** Serializable version Id. */
+    private static final long serialVersionUID = -7447085893598031110L;
+
+    /** Requested. */
+    private final Number argument;
+
+    /**
+     * Construct an exception.
+     *
+     * @param specific Localizable pattern.
+     * @param general Localizable pattern.
+     * @param wrong wrong number
+     * @param arguments Arguments.
+     */
+    protected MathIllegalNumberException(Localizable specific,
+                                         Localizable general,
+                                         Number wrong,
+                                         Object ... arguments) {
+        super(specific, general, wrong, arguments);
+        argument = wrong;
+    }
+
+    /**
+     * Construct an exception.
+     *
+     * @param general Localizable pattern.
+     * @param wrong wrong number
+     * @param arguments Arguments.
+     */
+    protected MathIllegalNumberException(Localizable general,
+                                         Number wrong,
+                                         Object ... arguments) {
+        super(general, wrong, arguments);
+        argument = wrong;
+    }
+
+    /**
+     * @return the requested value.
+     */
+    public Number getArgument() {
+        return argument;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/exception/MathIllegalStateException.java b/src/main/java/org/apache/commons/math/exception/MathIllegalStateException.java
new file mode 100644
index 0000000..d4e83d4
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/exception/MathIllegalStateException.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.math.exception;
+
+import java.util.Locale;
+
+import org.apache.commons.math.exception.util.ArgUtils;
+import org.apache.commons.math.exception.util.MessageFactory;
+import org.apache.commons.math.exception.util.Localizable;
+
+/**
+ * Base class for all exceptions that signal a mismatch between the
+ * current state and the user's expectations.
+ *
+ * @since 2.2
+ * @version $Revision: 1061496 $ $Date: 2011-01-20 21:32:16 +0100 (jeu. 20 janv. 2011) $
+ */
+public class MathIllegalStateException extends IllegalStateException implements MathThrowable {
+
+    /** Serializable version Id. */
+    private static final long serialVersionUID = -6024911025449780478L;
+
+    /**
+     * Pattern used to build the message (specific context).
+     */
+    private final Localizable specific;
+    /**
+     * Pattern used to build the message (general problem description).
+     */
+    private final Localizable general;
+    /**
+     * Arguments used to build the message.
+     */
+    private final Object[] arguments;
+
+    /**
+     * Simple constructor.
+     * @param specific Message pattern providing the specific context of
+     * the error.
+     * @param general Message pattern explaining the cause of the error.
+     * @param args Arguments.
+     */
+    public MathIllegalStateException(Localizable specific,
+                                     Localizable general,
+                                     Object ... args) {
+        this(null, specific, general, args);
+    }
+
+    /**
+     * Simple constructor.
+     * @param cause root cause
+     * @param specific Message pattern providing the specific context of
+     * the error.
+     * @param general Message pattern explaining the cause of the error.
+     * @param args Arguments.
+     */
+    public MathIllegalStateException(Throwable cause,
+                                     Localizable specific,
+                                     Localizable general,
+                                     Object ... args) {
+        super(cause);
+        this.specific = specific;
+        this.general = general;
+        arguments = ArgUtils.flatten(args);
+    }
+
+    /**
+     * @param general Message pattern explaining the cause of the error.
+     * @param args Arguments.
+     */
+    public MathIllegalStateException(Localizable general,
+                                     Object ... args) {
+        this(null, null, general, args);
+    }
+
+    /**
+     * Simple constructor.
+     * @param cause root cause
+     * @param general Message pattern explaining the cause of the error.
+     * @param args Arguments.
+     */
+    public MathIllegalStateException(Throwable cause,
+                                     Localizable general,
+                                     Object ... args) {
+        this(cause, null, general, args);
+    }
+
+    /** {@inheritDoc} */
+    public Localizable getSpecificPattern() {
+        return specific;
+    }
+
+    /** {@inheritDoc} */
+    public Localizable getGeneralPattern() {
+        return general;
+    }
+
+    /** {@inheritDoc} */
+    public Object[] getArguments() {
+        return arguments.clone();
+    }
+
+    /**
+     * Get 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 MessageFactory.buildMessage(locale, specific, general, arguments);
+    }
+
+   /** {@inheritDoc} */
+    @Override
+    public String getMessage() {
+        return getMessage(Locale.US);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String getLocalizedMessage() {
+        return getMessage(Locale.getDefault());
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/exception/MathInternalError.java b/src/main/java/org/apache/commons/math/exception/MathInternalError.java
new file mode 100644
index 0000000..623fe93
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/exception/MathInternalError.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.math.exception;
+
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * Exception triggered when something that shouldn't happen does happen.
+ *
+ * @since 2.2
+ * @version $Revision: 1061496 $ $Date: 2011-01-20 21:32:16 +0100 (jeu. 20 janv. 2011) $
+ */
+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() {
+        super(LocalizedFormats.INTERNAL_ERROR, REPORT_URL);
+    }
+
+    /**
+     * Simple constructor.
+     * @param cause root cause
+     */
+    public MathInternalError(final Throwable cause) {
+        super(LocalizedFormats.INTERNAL_ERROR, REPORT_URL);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/exception/MathThrowable.java b/src/main/java/org/apache/commons/math/exception/MathThrowable.java
new file mode 100644
index 0000000..5f47d5c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/exception/MathThrowable.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.math.exception;
+
+import java.util.Locale;
+
+import org.apache.commons.math.exception.util.Localizable;
+
+/**
+* Interface for commons-math throwables.
+*
+* @version $Revision: 1035475 $ $Date: 2010-11-15 23:39:25 +0100 (lun. 15 nov. 2010) $
+* @since 2.2
+*/
+public interface MathThrowable {
+
+    /** Gets the localizable pattern used to build the specific part of the message of this throwable.
+     * @return localizable pattern used to build the specific part of the message of this throwable
+     */
+    Localizable getSpecificPattern();
+
+    /** Gets the localizable pattern used to build the general part of the message of this throwable.
+     * @return localizable pattern used to build the general part of the message of this throwable
+     */
+    Localizable getGeneralPattern();
+
+    /** Gets the arguments used to build the message of this throwable.
+     * @return the arguments used to build the message of this throwable
+     */
+    Object[] getArguments();
+
+    /** Gets the message in a specified locale.
+     * @param locale Locale in which the message should be translated
+     * @return localized message
+     */
+    String getMessage(final Locale locale);
+
+    /** Gets the message in a conventional US locale.
+     * @return localized message
+     */
+    String getMessage();
+
+    /** Gets the message in the system default locale.
+     * @return localized message
+     */
+    String getLocalizedMessage();
+
+}
diff --git a/src/main/java/org/apache/commons/math/exception/MathUnsupportedOperationException.java b/src/main/java/org/apache/commons/math/exception/MathUnsupportedOperationException.java
new file mode 100644
index 0000000..592aa37
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/exception/MathUnsupportedOperationException.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.math.exception;
+
+import java.util.Locale;
+
+import org.apache.commons.math.exception.util.ArgUtils;
+import org.apache.commons.math.exception.util.MessageFactory;
+import org.apache.commons.math.exception.util.Localizable;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * Base class for all unsupported features.
+ * It is used for all the exceptions that share the semantics of the standard
+ * {@link UnsupportedOperationException}, but must also provide a localized
+ * message.
+ *
+ * @since 2.2
+ * @version $Revision$ $Date$
+ */
+public class MathUnsupportedOperationException extends UnsupportedOperationException implements MathThrowable {
+
+    /** Serializable version Id. */
+    private static final long serialVersionUID = -6024911025449780478L;
+
+    /**
+     * Pattern used to build the message (specific context).
+     */
+    private final Localizable specific;
+    /**
+     * Arguments used to build the message.
+     */
+    private final Object[] arguments;
+
+    /**
+     * @param args Arguments.
+     */
+    public MathUnsupportedOperationException(Object ... args) {
+        this(null, args);
+    }
+    /**
+     * @param specific Message pattern providing the specific context of
+     * the error.
+     * @param args Arguments.
+     */
+    public MathUnsupportedOperationException(Localizable specific,
+                                             Object ... args) {
+        this.specific = specific;
+        arguments = ArgUtils.flatten(args);
+    }
+
+    /** {@inheritDoc} */
+    public Localizable getSpecificPattern() {
+        return specific;
+    }
+
+    /** {@inheritDoc} */
+    public Localizable getGeneralPattern() {
+        return LocalizedFormats.UNSUPPORTED_OPERATION;
+    }
+
+    /** {@inheritDoc} */
+    public Object[] getArguments() {
+        return arguments.clone();
+    }
+
+    /**
+     * Get 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 MessageFactory.buildMessage(locale,
+                                           specific,
+                                           LocalizedFormats.UNSUPPORTED_OPERATION,
+                                           arguments);
+    }
+
+   /** {@inheritDoc} */
+    @Override
+    public String getMessage() {
+        return getMessage(Locale.US);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String getLocalizedMessage() {
+        return getMessage(Locale.getDefault());
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/exception/NoDataException.java b/src/main/java/org/apache/commons/math/exception/NoDataException.java
new file mode 100644
index 0000000..c1b4d56
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/exception/NoDataException.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.math.exception;
+
+import org.apache.commons.math.exception.util.Localizable;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * Exception to be thrown when the required data is missing.
+ *
+ * @since 2.2
+ * @version $Revision$ $Date$
+ */
+public class NoDataException extends MathIllegalStateException {
+
+    /** Serializable version Id. */
+    private static final long serialVersionUID = -3629324471511904459L;
+
+    /**
+     * Construct the exception.
+     */
+    public NoDataException() {
+        this(null);
+    }
+    /**
+     * Construct the exception with a specific context.
+     *
+     * @param specific Contextual information on what caused the exception.
+     */
+    public NoDataException(Localizable specific) {
+        super(specific, LocalizedFormats.NO_DATA, (Object[]) null);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/exception/NonMonotonousSequenceException.java b/src/main/java/org/apache/commons/math/exception/NonMonotonousSequenceException.java
new file mode 100644
index 0000000..3ad11ec
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/exception/NonMonotonousSequenceException.java
@@ -0,0 +1,123 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.exception;
+
+import org.apache.commons.math.util.MathUtils;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * Exception to be thrown when the a sequence of values is not monotonously
+ * increasing or decreasing.
+ *
+ * @since 2.2
+ * @version $Revision$ $Date$
+ */
+public class NonMonotonousSequenceException extends MathIllegalNumberException {
+
+    /** Serializable version Id. */
+    private static final long serialVersionUID = 3596849179428944575L;
+
+    /**
+     * Direction (positive for increasing, negative for decreasing).
+     */
+    private final MathUtils.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 NonMonotonousSequenceException(Number wrong,
+                                          Number previous,
+                                          int index) {
+        this(wrong, previous, index, MathUtils.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 NonMonotonousSequenceException(Number wrong,
+                                          Number previous,
+                                          int index,
+                                          MathUtils.OrderDirection direction,
+                                          boolean strict) {
+        super(direction == MathUtils.OrderDirection.INCREASING ?
+              (strict ?
+               LocalizedFormats.NOT_STRICTLY_INCREASING_SEQUENCE :
+               LocalizedFormats.NOT_INCREASING_SEQUENCE) :
+              (strict ?
+               LocalizedFormats.NOT_STRICTLY_DECREASING_SEQUENCE :
+               LocalizedFormats.NOT_DECREASING_SEQUENCE),
+              wrong, previous, index, index - 1);
+
+        this.direction = direction;
+        this.strict = strict;
+        this.index = index;
+        this.previous = previous;
+    }
+
+    /**
+     * @return the order direction.
+     **/
+    public MathUtils.OrderDirection getDirection() {
+        return direction;
+    }
+    /**
+     * @return {@code true} is the sequence should be strictly monotonous.
+     **/
+    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/math/exception/NotPositiveException.java b/src/main/java/org/apache/commons/math/exception/NotPositiveException.java
new file mode 100644
index 0000000..4af597a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/exception/NotPositiveException.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.math.exception;
+
+import org.apache.commons.math.exception.util.Localizable;
+
+/**
+ * Exception to be thrown when the argument is negative.
+ *
+ * @since 2.2
+ * @version $Revision$ $Date$
+ */
+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, 0, 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, 0, true);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/exception/NotStrictlyPositiveException.java b/src/main/java/org/apache/commons/math/exception/NotStrictlyPositiveException.java
new file mode 100644
index 0000000..4b96b1d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/exception/NotStrictlyPositiveException.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.math.exception;
+
+import org.apache.commons.math.exception.util.Localizable;
+
+/**
+ * Exception to be thrown when the argument is negative.
+ *
+ * @since 2.2
+ * @version $Revision$ $Date$
+ */
+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, 0, 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, 0, false);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/exception/NullArgumentException.java b/src/main/java/org/apache/commons/math/exception/NullArgumentException.java
new file mode 100644
index 0000000..e06d25f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/exception/NullArgumentException.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.math.exception;
+
+import org.apache.commons.math.exception.util.Localizable;
+import org.apache.commons.math.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}.
+ * Proagation of {@code NullPointerException} from within Commons-Math is
+ * construed to be a bug.
+ *
+ * @since 2.2
+ * @version $Revision$ $Date$
+ */
+public class NullArgumentException extends MathIllegalArgumentException {
+    /** Serializable version Id. */
+    private static final long serialVersionUID = -6024911025449780478L;
+
+    /**
+     * Default constructor.
+     */
+    public NullArgumentException() {
+        super(LocalizedFormats.NULL_NOT_ALLOWED);
+    }
+    /**
+     * @param specific Message pattern providing the specific context of
+     * the error.
+     */
+    public NullArgumentException(Localizable specific) {
+        super(specific, LocalizedFormats.NULL_NOT_ALLOWED);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/exception/NumberIsTooLargeException.java b/src/main/java/org/apache/commons/math/exception/NumberIsTooLargeException.java
new file mode 100644
index 0000000..9e1559b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/exception/NumberIsTooLargeException.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.math.exception;
+
+import org.apache.commons.math.exception.util.Localizable;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * Exception to be thrown when a number is too large.
+ *
+ * @since 2.2
+ * @version $Revision$ $Date$
+ */
+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(null, wrong, max, boundIsAllowed);
+    }
+    /**
+     * Construct the exception with a specific context.
+     *
+     * @param specific Specific contexte 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,
+              boundIsAllowed ?
+              LocalizedFormats.NUMBER_TOO_LARGE :
+              LocalizedFormats.NUMBER_TOO_LARGE_BOUND_EXCLUDED,
+              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/math/exception/NumberIsTooSmallException.java b/src/main/java/org/apache/commons/math/exception/NumberIsTooSmallException.java
new file mode 100644
index 0000000..349ffd1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/exception/NumberIsTooSmallException.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.math.exception;
+
+import org.apache.commons.math.exception.util.Localizable;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * Exception to be thrown when a number is too small.
+ *
+ * @since 2.2
+ * @version $Revision$ $Date$
+ */
+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(null, wrong, min, boundIsAllowed);
+    }
+
+    /**
+     * Construct the exception with a specific context.
+     *
+     * @param specific Specific contexte 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,
+              boundIsAllowed ?
+              LocalizedFormats.NUMBER_TOO_SMALL :
+              LocalizedFormats.NUMBER_TOO_SMALL_BOUND_EXCLUDED,
+              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/math/exception/OutOfRangeException.java b/src/main/java/org/apache/commons/math/exception/OutOfRangeException.java
new file mode 100644
index 0000000..268fcb5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/exception/OutOfRangeException.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.math.exception;
+
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * Exception to be thrown when some argument is out of range.
+ *
+ * @since 2.2
+ * @version $Revision$ $Date$
+ */
+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) {
+        super(LocalizedFormats.OUT_OF_RANGE_SIMPLE, 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/math/exception/ZeroException.java b/src/main/java/org/apache/commons/math/exception/ZeroException.java
new file mode 100644
index 0000000..bd14365
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/exception/ZeroException.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.math.exception;
+
+import org.apache.commons.math.exception.util.Localizable;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * Exception to be thrown when zero is provided where it is not allowed.
+ *
+ * @since 2.2
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ */
+public class ZeroException extends MathIllegalNumberException {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -1960874856936000015L;
+
+    /**
+     * Construct the exception.
+     */
+    public ZeroException() {
+        this(null);
+    }
+
+    /**
+     * Construct the exception with a specific context.
+     *
+     * @param specific Specific contexte pattern .
+     */
+    public ZeroException(Localizable specific) {
+        super(specific, LocalizedFormats.ZERO_NOT_ALLOWED, 0);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/exception/package.html b/src/main/java/org/apache/commons/math/exception/package.html
new file mode 100644
index 0000000..301f866
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/exception/package.html
@@ -0,0 +1,23 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 981404 $ $Date: 2010-08-02 10:10:39 +0200 (lun. 02 août 2010) $ -->
+    <body>
+     Specialized exceptions for algorithms errors. The exceptions can be localized
+     using simple java properties.
+    </body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/exception/util/ArgUtils.java b/src/main/java/org/apache/commons/math/exception/util/ArgUtils.java
new file mode 100644
index 0000000..d91134c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/exception/util/ArgUtils.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.math.exception.util;
+
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Utility class for transforming the list of arguments passed to
+ * constructors of exceptions.
+ *
+ * @version $Revision$ $Date$
+ * @since 2.2
+ */
+public class ArgUtils {
+    /**
+     * Private constructor
+     */
+    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/math/exception/util/DummyLocalizable.java b/src/main/java/org/apache/commons/math/exception/util/DummyLocalizable.java
new file mode 100644
index 0000000..449ae75
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/exception/util/DummyLocalizable.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.math.exception.util;
+
+import java.util.Locale;
+
+/**
+ * Dummy implementation of the {@link Localizable} interface, without localization.
+ *
+ * @version $Revision: 1003892 $ $Date: 2010-10-02 23:28:56 +0200 (sam. 02 oct. 2010) $
+ * @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/math/exception/util/Localizable.java b/src/main/java/org/apache/commons/math/exception/util/Localizable.java
new file mode 100644
index 0000000..47efe91
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/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.math.exception.util;
+
+import java.io.Serializable;
+import java.util.Locale;
+
+/**
+ * Interface for localizable strings.
+ *
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ * @since 2.2
+ */
+public interface Localizable extends Serializable {
+
+    /**
+     * Get the source (non-localized) string.
+     * @return source string
+     */
+    String getSourceString();
+
+    /**
+     * Get the localized string.
+     * @param locale locale into which to get the string
+     * @return 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/math/exception/util/LocalizedFormats.java b/src/main/java/org/apache/commons/math/exception/util/LocalizedFormats.java
new file mode 100644
index 0000000..5c417f3
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/exception/util/LocalizedFormats.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.math.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
+ * @version $Revision: 1073165 $ $Date: 2011-02-21 23:04:14 +0100 (lun. 21 févr. 2011) $
+ */
+public enum LocalizedFormats implements Localizable {
+
+    // CHECKSTYLE: stop MultipleVariableDeclarations
+    // CHECKSTYLE: stop JavadocVariable
+
+    ARGUMENT_OUTSIDE_DOMAIN("Argument {0} outside domain [{1} ; {2}]"),
+    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_OUT_OF_INTERVAL("bandwidth must be in the interval [0,1], but got {0}"),
+    BINOMIAL_INVALID_PARAMETERS_ORDER("must have n >= k for binomial coefficient (n,k), got n = {0}, k = {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_CONVERT_OBJECT_TO_FRACTION("cannot convert given object to a fraction number: {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"),
+    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}]"),
+    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"),
+    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}"),
+    DIGEST_NOT_INITIALIZED("digest not initialized"),
+    DIMENSIONS_MISMATCH_2x2("dimensions mismatch: got {0}x{1} but expected {2}x{3}"),
+    DIMENSIONS_MISMATCH_SIMPLE("dimensions mismatch {0} != {1}"), /* keep */
+    DISCRETE_CUMULATIVE_PROBABILITY_RETURNED_NAN("Discrete cumulative probability function returned NaN for argument {0}"),
+    DISTRIBUTION_NOT_LOADED("distribution not loaded"),
+    DUPLICATED_ABSCISSA("Abscissa {0} is duplicated at both indices {1} and {2}"),
+    EMPTY_CLUSTER_IN_K_MEANS("empty cluster in k-means"),
+    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_FAILED("evaluation failed for argument = {0}"),
+    EXPANSION_FACTOR_SMALLER_THAN_ONE("expansion factor smaller than one ({0})"),
+    FACTORIAL_NEGATIVE_PARAMETER("must have n >= 0 for n!, got n = {0}"),
+    FAILED_BRACKETING("number of iterations={0}, maximum iterations={1}, initial={2}, lower bound={3}, upper bound={4}, final a value={5}, final b value={6}, f(a)={7}, f(b)={8}"),
+    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"),
+    IDENTICAL_ABSCISSAS_DIVISION_BY_ZERO("identical abscissas x[{0}] == x[{1}] == {2} cause division by zero"),
+    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}]"),
+    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 {0} after final column {1}"),
+    INITIAL_ROW_AFTER_FINAL_ROW("initial row {0} after final row {1}"),
+    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_FOR_T_STATISTIC("insufficient data for t statistic, needs at least 2, got {0}"),
+    INSUFFICIENT_DIMENSION("insufficient dimension {0}, must be at least {1}"),
+    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_ONE_PREVIOUS_POINT("{0} method needs at least one previous point"),
+    INTERNAL_ERROR("internal error, please fill a bug report at {0}"),
+    INVALID_BRACKETING_PARAMETERS("invalid bracketing parameters:  lower bound={0},  initial={1}, upper bound={2}"),
+    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}"),
+    INVALID_REGRESSION_ARRAY("input data array length = {0} does not match the number of observations = {1} and the number of regressors = {2}"),
+    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"),
+    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"),
+    MAX_EVALUATIONS_EXCEEDED("maximal number of evaluations ({0}) exceeded"),
+    MAX_ITERATIONS_EXCEEDED("maximal number of iterations ({0}) exceeded"),
+    MINIMAL_STEPSIZE_REACHED_DURING_INTEGRATION("minimal step size ({0,number,0.00E00}) reached, integration needs {1,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"),
+    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})"),
+    NEGATIVE_NUMBER_OF_TRIALS("number of trials must be non-negative ({0})"),
+    NEGATIVE_ROBUSTNESS_ITERATIONS("the number of robustness iterations must be non-negative, but got {0}"),
+    START_POSITION("start position ({0})"), /* keep */
+    NON_CONVERGENT_CONTINUED_FRACTION("Continued fraction convergents failed to converge for value {0}"),
+    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("a {0}x{1} matrix was provided instead of a square matrix"),
+    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_OVERRIDEN("method not overriden"),
+    NOT_POSITIVE_ALPHA("alpha must be positive ({0})"),
+    NOT_POSITIVE_BETA("beta must be positive ({0})"),
+    NOT_POSITIVE_COLUMNDIMENSION("invalid column dimension: {0} (must be positive)"),
+    NOT_POSITIVE_DEFINITE_MATRIX("not positive definite matrix"),
+    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)"),
+    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})"),
+    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})"),
+    NOT_POSITIVE_SHAPE("shape must be positive ({0})"),
+    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_SYMMETRIC_MATRIX("not symmetric matrix"),
+    NO_BIN_SELECTED("no bin selected"),
+    NO_CONVERGENCE_WITH_ANY_START_POINT("none of the {0} start points lead to convergence"),
+    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"),
+    NO_RESULT_AVAILABLE("no result available"),
+    NO_SUCH_MATRIX_ENTRY("no entry at indices ({0}, {1}) in a {2}x{3} matrix"),
+    NULL_NOT_ALLOWED("null is not allowed"), /* keep */
+    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}"),
+    OUT_OF_BOUNDS_QUANTILE_VALUE("out of bounds quantile value: {0}, must be in (0, 100]"),
+    OUT_OF_BOUND_SIGNIFICANCE_LEVEL("out of bounds significance level {0}, must be between {1} and {2}"),
+    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_RANGE_ROOT_OF_UNITY_INDEX("out of range root of unity index {0} (must be in [{1};{2}])"),
+    OUT_OF_RANGE_SIMPLE("{0} out of [{1}, {2}] range"), /* 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}"),
+    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_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"),
+    POSITION_SIZE_MISMATCH_INPUT_ARRAY("position {0} and size {1} don't fit to the size of the input array {2}"),
+    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}]"),
+    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"),
+    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_MANY_ELEMENTS_TO_DISCARD_FROM_ARRAY("cannot discard {0} elements from a {1} elements array"),
+    TOO_SMALL_BANDWIDTH("the bandwidth must be large enough to accomodate at least 2 points. There are {0}  data points, and bandwidth must be at least {1}  but it is only {2}"),
+    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"),
+    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})"),
+    UNPARSEABLE_3D_VECTOR("unparseable 3D vector: \"{0}\""),
+    UNPARSEABLE_COMPLEX_NUMBER("unparseable complex number: \"{0}\""),
+    UNPARSEABLE_FRACTION_NUMBER("unparseable fraction 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 */
+    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"),
+    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
+     */
+    private LocalizedFormats(final String sourceFormat) {
+        this.sourceFormat = sourceFormat;
+    }
+
+    /** {@inheritDoc} */
+    public String getSourceString() {
+        return sourceFormat;
+    }
+
+    /** {@inheritDoc} */
+    public String getLocalizedString(final Locale locale) {
+        try {
+            ResourceBundle bundle =
+                    ResourceBundle.getBundle("META-INF/localization/LocalizedFormats", locale);
+            if (bundle.getLocale().getLanguage().equals(locale.getLanguage())) {
+                // the value of the resource is the translated format
+                return bundle.getString(toString());
+            }
+
+        } catch (MissingResourceException mre) {
+            // 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/math/exception/util/MessageFactory.java b/src/main/java/org/apache/commons/math/exception/util/MessageFactory.java
new file mode 100644
index 0000000..0e8cf14
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/exception/util/MessageFactory.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.math.exception.util;
+
+import java.text.MessageFormat;
+import java.util.Locale;
+
+/**
+ * Class for constructing localized messages.
+ *
+ * @since 2.2
+ * @version $Revision$ $Date$
+ */
+public class MessageFactory {
+    /**
+     * Class contains only static methods.
+     */
+    private MessageFactory() {}
+
+    /**
+     * Builds a message string by from a pattern and its arguments.
+     *
+     * @param locale Locale in which the message should be translated.
+     * @param pattern Format specifier.
+     * @param arguments Format arguments.
+     * @return a localized message string.
+     */
+    public static String buildMessage(Locale locale,
+                                      Localizable pattern,
+                                      Object ... arguments) {
+        return buildMessage(locale, null, pattern, arguments);
+    }
+
+    /**
+     * Builds a message string by from two patterns (specific and general) and
+     * an argument list.
+     *
+     * @param locale Locale in which the message should be translated.
+     * @param specific Format specifier (may be null).
+     * @param general Format specifier (may be null).
+     * @param arguments Format arguments. They will be substituted in
+     * <em>both</em> the {@code general} and {@code specific} format specifiers.
+     * @return a localized message string.
+     */
+    public static String buildMessage(Locale locale,
+                                      Localizable specific,
+                                      Localizable general,
+                                      Object ... arguments) {
+        final StringBuilder sb = new StringBuilder();
+        if (general != null) {
+            final MessageFormat fmt = new MessageFormat(general.getLocalizedString(locale), locale);
+            sb.append(fmt.format(arguments));
+        }
+        if (specific != null) {
+            if (general != null) {
+                sb.append(": ");
+            }
+            final MessageFormat fmt = new MessageFormat(specific.getLocalizedString(locale), locale);
+            sb.append(fmt.format(arguments));
+        }
+
+        return sb.toString();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/exception/util/package.html b/src/main/java/org/apache/commons/math/exception/util/package.html
new file mode 100644
index 0000000..456a88a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/exception/util/package.html
@@ -0,0 +1,22 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 1054315 $ $Date: 2011-01-02 00:22:24 +0100 (dim. 02 janv. 2011) $ -->
+  <body>
+    Classes supporting exception localization.
+  </body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/fraction/AbstractFormat.java b/src/main/java/org/apache/commons/math/fraction/AbstractFormat.java
new file mode 100644
index 0000000..c409702
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/fraction/AbstractFormat.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.math.fraction;
+
+import java.io.Serializable;
+import java.text.FieldPosition;
+import java.text.NumberFormat;
+import java.text.ParsePosition;
+import java.util.Locale;
+
+import org.apache.commons.math.exception.NullArgumentException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * Common part shared by both {@link FractionFormat} and {@link BigFractionFormat}.
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ * @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. */
+    protected NumberFormat denominatorFormat;
+
+    /** The format used for the numerator. */
+    protected 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)} with the only
+     * customizing 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)} with the only
+     * customizing 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/ouput 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/ouput 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/math/fraction/BigFraction.java b/src/main/java/org/apache/commons/math/fraction/BigFraction.java
new file mode 100644
index 0000000..98fa676
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/fraction/BigFraction.java
@@ -0,0 +1,1129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.fraction;
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import org.apache.commons.math.FieldElement;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.util.MathUtils;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Representation of a rational number without any overflow. This class is
+ * immutable.
+ *
+ * @version $Revision: 1073687 $ $Date: 2011-02-23 11:39:25 +0100 (mer. 23 févr. 2011) $
+ * @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_DOUBLE = BigInteger.valueOf(100);
+
+    /** The numerator. */
+    private final BigInteger numerator;
+
+    /** The denominator. */
+    private final BigInteger denominator;
+
+    /**
+     * <p>
+     * Create a {@link BigFraction} equivalent to the passed <tt>BigInteger</tt>, ie
+     * "num / 1".
+     * </p>
+     *
+     * @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 ArithmeticException if the denominator is zero.
+     */
+    public BigFraction(BigInteger num, BigInteger den) {
+        if (num == null) {
+            throw new NullPointerException(LocalizedFormats.NUMERATOR.getSourceString());
+        }
+        if (den == null) {
+            throw new NullPointerException(LocalizedFormats.DENOMINATOR.getSourceString());
+        }
+        if (BigInteger.ZERO.equals(den)) {
+            throw MathRuntimeException.createArithmeticException(LocalizedFormats.ZERO_DENOMINATOR);
+        }
+        if (BigInteger.ZERO.equals(num)) {
+            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 (BigInteger.ZERO.compareTo(den) > 0) {
+                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 does work for all values except NaN and infinities and does
+     * not requires any loop or convergence threshold.
+     * </p>
+     * <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).
+     * </p>
+     * @see #BigFraction(double, double, int)
+     * @param value the double value to convert to a fraction.
+     * @exception IllegalArgumentException if value is NaN or infinite
+     */
+    public BigFraction(final double value) throws IllegalArgumentException {
+        if (Double.isNaN(value)) {
+            throw MathRuntimeException.createIllegalArgumentException(LocalizedFormats.NAN_VALUE_CONVERSION);
+        }
+        if (Double.isInfinite(value)) {
+            throw MathRuntimeException.createIllegalArgumentException(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 = 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)</li>
+     * </ul>
+     * </p>
+     *
+     * @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>
+     * <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.
+     * </p>
+     *
+     * See JIRA issue ticket MATH-181 for more details:
+     *
+     * 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 (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)) {
+                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)</li>
+     * </ul>
+     * </p>
+     *
+     * @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);
+    }
+
+    /**
+     * <p>
+     * Create a {@link BigFraction} equivalent to the passed <tt>int</tt>, ie
+     * "num / 1".
+     * </p>
+     *
+     * @param num
+     *            the numerator.
+     */
+    public BigFraction(final int num) {
+        this(BigInteger.valueOf(num), BigInteger.ONE);
+    }
+
+    /**
+     * <p>
+     * Create a {@link BigFraction} given the numerator and denominator as simple
+     * <tt>int</tt>. The {@link BigFraction} is reduced to lowest terms.
+     * </p>
+     *
+     * @param num
+     *            the numerator.
+     * @param den
+     *            the denominator.
+     */
+    public BigFraction(final int num, final int den) {
+        this(BigInteger.valueOf(num), BigInteger.valueOf(den));
+    }
+
+    /**
+     * <p>
+     * Create a {@link BigFraction} equivalent to the passed long, ie "num / 1".
+     * </p>
+     *
+     * @param num
+     *            the numerator.
+     */
+    public BigFraction(final long num) {
+        this(BigInteger.valueOf(num), BigInteger.ONE);
+    }
+
+    /**
+     * <p>
+     * Create a {@link BigFraction} given the numerator and denominator as simple
+     * <tt>long</tt>. The {@link BigFraction} is reduced to lowest terms.
+     * </p>
+     *
+     * @param num
+     *            the numerator.
+     * @param den
+     *            the denominator.
+     */
+    public BigFraction(final long num, final long den) {
+        this(BigInteger.valueOf(num), BigInteger.valueOf(den));
+    }
+
+    /**
+     * <p>
+     * Creates a <code>BigFraction</code> instance with the 2 parts of a fraction
+     * Y/Z.
+     * </p>
+     *
+     * <p>
+     * Any negative signs are resolved to be on the numerator.
+     * </p>
+     *
+     * @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);
+    }
+
+    /**
+     * <p>
+     * Returns the absolute value of this {@link BigFraction}.
+     * </p>
+     *
+     * @return the absolute value as a {@link BigFraction}.
+     */
+    public BigFraction abs() {
+        return (BigInteger.ZERO.compareTo(numerator) <= 0) ? this : negate();
+    }
+
+    /**
+     * <p>
+     * Adds the value of this fraction to the passed {@link BigInteger},
+     * returning the result in reduced form.
+     * </p>
+     *
+     * @param bg
+     *            the {@link BigInteger} to add, must'nt be <code>null</code>.
+     * @return a <code>BigFraction</code> instance with the resulting values.
+     * @throws NullPointerException
+     *             if the {@link BigInteger} is <code>null</code>.
+     */
+    public BigFraction add(final BigInteger bg) {
+        return new BigFraction(numerator.add(denominator.multiply(bg)), denominator);
+    }
+
+    /**
+     * <p>
+     * Adds the value of this fraction to the passed <tt>integer</tt>, returning
+     * the result in reduced form.
+     * </p>
+     *
+     * @param i
+     *            the <tt>integer</tt> to add.
+     * @return a <code>BigFraction</code> instance with the resulting values.
+     */
+    public BigFraction add(final int i) {
+        return add(BigInteger.valueOf(i));
+    }
+
+    /**
+     * <p>
+     * Adds the value of this fraction to the passed <tt>long</tt>, returning
+     * the result in reduced form.
+     * </p>
+     *
+     * @param l
+     *            the <tt>long</tt> to add.
+     * @return a <code>BigFraction</code> instance with the resulting values.
+     */
+    public BigFraction add(final long l) {
+        return add(BigInteger.valueOf(l));
+    }
+
+    /**
+     * <p>
+     * Adds the value of this fraction to another, returning the result in
+     * reduced form.
+     * </p>
+     *
+     * @param fraction
+     *            the {@link BigFraction} to add, must not be <code>null</code>.
+     * @return a {@link BigFraction} instance with the resulting values.
+     * @throws NullPointerException if the {@link BigFraction} is {@code null}.
+     */
+    public BigFraction add(final BigFraction fraction) {
+        if (fraction == null) {
+            throw new NullPointerException(LocalizedFormats.FRACTION.getSourceString());
+        }
+        if (ZERO.equals(fraction)) {
+            return this;
+        }
+
+        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);
+        }
+        return new BigFraction(num, den);
+
+    }
+
+    /**
+     * <p>
+     * Gets the fraction as a <code>BigDecimal</code>. This calculates the
+     * fraction as the numerator divided by denominator.
+     * </p>
+     *
+     * @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));
+    }
+
+    /**
+     * <p>
+     * Gets the fraction as a <code>BigDecimal</code> following the passed
+     * rounding mode. This calculates the fraction as the numerator divided by
+     * denominator.
+     * </p>
+     *
+     * @param roundingMode
+     *            rounding mode to apply. see {@link BigDecimal} constants.
+     * @return the fraction as a <code>BigDecimal</code>.
+     * @throws IllegalArgumentException
+     *             if <tt>roundingMode</tt> does not represent a valid rounding
+     *             mode.
+     * @see BigDecimal
+     */
+    public BigDecimal bigDecimalValue(final int roundingMode) {
+        return new BigDecimal(numerator).divide(new BigDecimal(denominator), roundingMode);
+    }
+
+    /**
+     * <p>
+     * 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.
+     * </p>
+     *
+     * @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);
+    }
+
+    /**
+     * <p>
+     * Compares this object to another based on size.
+     * </p>
+     *
+     * @param object
+     *            the object to compare to, must not be <code>null</code>.
+     * @return -1 if this is less than <tt>object</tt>, +1 if this is greater
+     *         than <tt>object</tt>, 0 if they are equal.
+     * @see java.lang.Comparable#compareTo(java.lang.Object)
+     */
+    public int compareTo(final BigFraction object) {
+        BigInteger nOd = numerator.multiply(object.denominator);
+        BigInteger dOn = denominator.multiply(object.numerator);
+        return nOd.compareTo(dOn);
+    }
+
+    /**
+     * <p>
+     * Divide the value of this fraction by the passed <code>BigInteger</code>,
+     * ie "this * 1 / bg", returning the result in reduced form.
+     * </p>
+     *
+     * @param bg
+     *            the <code>BigInteger</code> to divide by, must not be
+     *            <code>null</code>.
+     * @return a {@link BigFraction} instance with the resulting values.
+     * @throws NullPointerException if the {@code BigInteger} is {@code null}.
+     * @throws ArithmeticException
+     *             if the fraction to divide by is zero.
+     */
+    public BigFraction divide(final BigInteger bg) {
+        if (BigInteger.ZERO.equals(bg)) {
+            throw MathRuntimeException.createArithmeticException(LocalizedFormats.ZERO_DENOMINATOR);
+        }
+        return new BigFraction(numerator, denominator.multiply(bg));
+    }
+
+    /**
+     * <p>
+     * Divide the value of this fraction by the passed <tt>int</tt>, ie
+     * "this * 1 / i", returning the result in reduced form.
+     * </p>
+     *
+     * @param i
+     *            the <tt>int</tt> to divide by.
+     * @return a {@link BigFraction} instance with the resulting values.
+     * @throws ArithmeticException
+     *             if the fraction to divide by is zero.
+     */
+    public BigFraction divide(final int i) {
+        return divide(BigInteger.valueOf(i));
+    }
+
+    /**
+     * <p>
+     * Divide the value of this fraction by the passed <tt>long</tt>, ie
+     * "this * 1 / l", returning the result in reduced form.
+     * </p>
+     *
+     * @param l
+     *            the <tt>long</tt> to divide by.
+     * @return a {@link BigFraction} instance with the resulting values.
+     * @throws ArithmeticException
+     *             if the fraction to divide by is zero.
+     */
+    public BigFraction divide(final long l) {
+        return divide(BigInteger.valueOf(l));
+    }
+
+    /**
+     * <p>
+     * Divide the value of this fraction by another, returning the result in
+     * reduced form.
+     * </p>
+     *
+     * @param fraction Fraction to divide by, must not be {@code null}.
+     * @return a {@link BigFraction} instance with the resulting values.
+     * @throws NullPointerException if the {@code fraction} is {@code null}.
+     * @throws ArithmeticException if the fraction to divide by is zero.
+     */
+    public BigFraction divide(final BigFraction fraction) {
+        if (fraction == null) {
+            throw new NullPointerException(LocalizedFormats.FRACTION.getSourceString());
+        }
+        if (BigInteger.ZERO.equals(fraction.numerator)) {
+            throw MathRuntimeException.createArithmeticException(LocalizedFormats.ZERO_DENOMINATOR);
+        }
+
+        return multiply(fraction.reciprocal());
+    }
+
+    /**
+     * <p>
+     * Gets the fraction as a <tt>double</tt>. This calculates the fraction as
+     * the numerator divided by denominator.
+     * </p>
+     *
+     * @return the fraction as a <tt>double</tt>
+     * @see java.lang.Number#doubleValue()
+     */
+    @Override
+    public double doubleValue() {
+        return numerator.doubleValue() / denominator.doubleValue();
+    }
+
+    /**
+     * <p>
+     * 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.
+     * </p>
+     *
+     * @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;
+    }
+
+    /**
+     * <p>
+     * Gets the fraction as a <tt>float</tt>. This calculates the fraction as
+     * the numerator divided by denominator.
+     * </p>
+     *
+     * @return the fraction as a <tt>float</tt>.
+     * @see java.lang.Number#floatValue()
+     */
+    @Override
+    public float floatValue() {
+        return numerator.floatValue() / denominator.floatValue();
+    }
+
+    /**
+     * <p>
+     * Access the denominator as a <code>BigInteger</code>.
+     * </p>
+     *
+     * @return the denominator as a <code>BigInteger</code>.
+     */
+    public BigInteger getDenominator() {
+        return denominator;
+    }
+
+    /**
+     * <p>
+     * Access the denominator as a <tt>int</tt>.
+     * </p>
+     *
+     * @return the denominator as a <tt>int</tt>.
+     */
+    public int getDenominatorAsInt() {
+        return denominator.intValue();
+    }
+
+    /**
+     * <p>
+     * Access the denominator as a <tt>long</tt>.
+     * </p>
+     *
+     * @return the denominator as a <tt>long</tt>.
+     */
+    public long getDenominatorAsLong() {
+        return denominator.longValue();
+    }
+
+    /**
+     * <p>
+     * Access the numerator as a <code>BigInteger</code>.
+     * </p>
+     *
+     * @return the numerator as a <code>BigInteger</code>.
+     */
+    public BigInteger getNumerator() {
+        return numerator;
+    }
+
+    /**
+     * <p>
+     * Access the numerator as a <tt>int</tt>.
+     * </p>
+     *
+     * @return the numerator as a <tt>int</tt>.
+     */
+    public int getNumeratorAsInt() {
+        return numerator.intValue();
+    }
+
+    /**
+     * <p>
+     * Access the numerator as a <tt>long</tt>.
+     * </p>
+     *
+     * @return the numerator as a <tt>long</tt>.
+     */
+    public long getNumeratorAsLong() {
+        return numerator.longValue();
+    }
+
+    /**
+     * <p>
+     * Gets a hashCode for the fraction.
+     * </p>
+     *
+     * @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();
+    }
+
+    /**
+     * <p>
+     * Gets the fraction as an <tt>int</tt>. This returns the whole number part
+     * of the fraction.
+     * </p>
+     *
+     * @return the whole number fraction part.
+     * @see java.lang.Number#intValue()
+     */
+    @Override
+    public int intValue() {
+        return numerator.divide(denominator).intValue();
+    }
+
+    /**
+     * <p>
+     * Gets the fraction as a <tt>long</tt>. This returns the whole number part
+     * of the fraction.
+     * </p>
+     *
+     * @return the whole number fraction part.
+     * @see java.lang.Number#longValue()
+     */
+    @Override
+    public long longValue() {
+        return numerator.divide(denominator).longValue();
+    }
+
+    /**
+     * <p>
+     * Multiplies the value of this fraction by the passed
+     * <code>BigInteger</code>, returning the result in reduced form.
+     * </p>
+     *
+     * @param bg the {@code BigInteger} to multiply by.
+     * @return a {@code BigFraction} instance with the resulting values.
+     * @throws NullPointerException if {@code bg} is {@code null}.
+     */
+    public BigFraction multiply(final BigInteger bg) {
+        if (bg == null) {
+            throw new NullPointerException();
+        }
+        return new BigFraction(bg.multiply(numerator), denominator);
+    }
+
+    /**
+     * <p>
+     * Multiply the value of this fraction by the passed <tt>int</tt>, returning
+     * the result in reduced form.
+     * </p>
+     *
+     * @param i
+     *            the <tt>int</tt> to multiply by.
+     * @return a {@link BigFraction} instance with the resulting values.
+     */
+    public BigFraction multiply(final int i) {
+        return multiply(BigInteger.valueOf(i));
+    }
+
+    /**
+     * <p>
+     * Multiply the value of this fraction by the passed <tt>long</tt>,
+     * returning the result in reduced form.
+     * </p>
+     *
+     * @param l
+     *            the <tt>long</tt> to multiply by.
+     * @return a {@link BigFraction} instance with the resulting values.
+     */
+    public BigFraction multiply(final long l) {
+        return multiply(BigInteger.valueOf(l));
+    }
+
+    /**
+     * <p>
+     * Multiplies the value of this fraction by another, returning the result in
+     * reduced form.
+     * </p>
+     *
+     * @param fraction Fraction to multiply by, must not be {@code null}.
+     * @return a {@link BigFraction} instance with the resulting values.
+     * @throws NullPointerException if {@code fraction} is {@code null}.
+     */
+    public BigFraction multiply(final BigFraction fraction) {
+        if (fraction == null) {
+            throw new NullPointerException(LocalizedFormats.FRACTION.getSourceString());
+        }
+        if (numerator.equals(BigInteger.ZERO) ||
+            fraction.numerator.equals(BigInteger.ZERO)) {
+            return ZERO;
+        }
+        return new BigFraction(numerator.multiply(fraction.numerator),
+                               denominator.multiply(fraction.denominator));
+    }
+
+    /**
+     * <p>
+     * Return the additive inverse of this fraction, returning the result in
+     * reduced form.
+     * </p>
+     *
+     * @return the negation of this fraction.
+     */
+    public BigFraction negate() {
+        return new BigFraction(numerator.negate(), denominator);
+    }
+
+    /**
+     * <p>
+     * Gets the fraction percentage as a <tt>double</tt>. This calculates the
+     * fraction as the numerator divided by denominator multiplied by 100.
+     * </p>
+     *
+     * @return the fraction percentage as a <tt>double</tt>.
+     */
+    public double percentageValue() {
+        return (numerator.divide(denominator)).multiply(ONE_HUNDRED_DOUBLE).doubleValue();
+    }
+
+    /**
+     * <p>
+     * Returns a <tt>integer</tt> whose value is
+     * <tt>(this<sup>exponent</sup>)</tt>, returning the result in reduced form.
+     * </p>
+     *
+     * @param exponent
+     *            exponent to which this <code>BigInteger</code> is to be
+     *            raised.
+     * @return <tt>this<sup>exponent</sup></tt>.
+     */
+    public BigFraction pow(final int exponent) {
+        if (exponent < 0) {
+            return new BigFraction(denominator.pow(-exponent), numerator.pow(-exponent));
+        }
+        return new BigFraction(numerator.pow(exponent), denominator.pow(exponent));
+    }
+
+    /**
+     * <p>
+     * Returns a <code>BigFraction</code> whose value is
+     * <tt>(this<sup>exponent</sup>)</tt>, returning the result in reduced form.
+     * </p>
+     *
+     * @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 new BigFraction(MathUtils.pow(denominator, -exponent),
+                                   MathUtils.pow(numerator,   -exponent));
+        }
+        return new BigFraction(MathUtils.pow(numerator,   exponent),
+                               MathUtils.pow(denominator, exponent));
+    }
+
+    /**
+     * <p>
+     * Returns a <code>BigFraction</code> whose value is
+     * <tt>(this<sup>exponent</sup>)</tt>, returning the result in reduced form.
+     * </p>
+     *
+     * @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.compareTo(BigInteger.ZERO) < 0) {
+            final BigInteger eNeg = exponent.negate();
+            return new BigFraction(MathUtils.pow(denominator, eNeg),
+                                   MathUtils.pow(numerator,   eNeg));
+        }
+        return new BigFraction(MathUtils.pow(numerator,   exponent),
+                               MathUtils.pow(denominator, exponent));
+    }
+
+    /**
+     * <p>
+     * Returns a <code>double</code> whose value is
+     * <tt>(this<sup>exponent</sup>)</tt>, returning the result in reduced form.
+     * </p>
+     *
+     * @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);
+    }
+
+    /**
+     * <p>
+     * Return the multiplicative inverse of this fraction.
+     * </p>
+     *
+     * @return the reciprocal fraction.
+     */
+    public BigFraction reciprocal() {
+        return new BigFraction(denominator, numerator);
+    }
+
+    /**
+     * <p>
+     * Reduce this <code>BigFraction</code> to its lowest terms.
+     * </p>
+     *
+     * @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);
+        return new BigFraction(numerator.divide(gcd), denominator.divide(gcd));
+    }
+
+    /**
+     * <p>
+     * Subtracts the value of an {@link BigInteger} from the value of this one,
+     * returning the result in reduced form.
+     * </p>
+     *
+     * @param bg the {@link BigInteger} to subtract, cannot be {@code null}.
+     * @return a {@code BigFraction} instance with the resulting values.
+     * @throws NullPointerException if the {@link BigInteger} is {@code null}.
+     */
+    public BigFraction subtract(final BigInteger bg) {
+        if (bg == null) {
+            throw new NullPointerException();
+        }
+        return new BigFraction(numerator.subtract(denominator.multiply(bg)), denominator);
+    }
+
+    /**
+     * <p>
+     * Subtracts the value of an <tt>integer</tt> from the value of this one,
+     * returning the result in reduced form.
+     * </p>
+     *
+     * @param i
+     *            the <tt>integer</tt> to subtract.
+     * @return a <code>BigFraction</code> instance with the resulting values.
+     */
+    public BigFraction subtract(final int i) {
+        return subtract(BigInteger.valueOf(i));
+    }
+
+    /**
+     * <p>
+     * Subtracts the value of an <tt>integer</tt> from the value of this one,
+     * returning the result in reduced form.
+     * </p>
+     *
+     * @param l
+     *            the <tt>long</tt> to subtract.
+     * @return a <code>BigFraction</code> instance with the resulting values, or
+     *         this object if the <tt>long</tt> is zero.
+     */
+    public BigFraction subtract(final long l) {
+        return subtract(BigInteger.valueOf(l));
+    }
+
+    /**
+     * <p>
+     * Subtracts the value of another fraction from the value of this one,
+     * returning the result in reduced form.
+     * </p>
+     *
+     * @param fraction {@link BigFraction} to subtract, must not be {@code null}.
+     * @return a {@link BigFraction} instance with the resulting values
+     * @throws NullPointerException if the {@code fraction} is {@code null}.
+     */
+    public BigFraction subtract(final BigFraction fraction) {
+        if (fraction == null) {
+            throw new NullPointerException(LocalizedFormats.FRACTION.getSourceString());
+        }
+        if (ZERO.equals(fraction)) {
+            return this;
+        }
+
+        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);
+
+    }
+
+    /**
+     * <p>
+     * Returns the <code>String</code> representing this fraction, ie
+     * "num / dem" or just "num" if the denominator is one.
+     * </p>
+     *
+     * @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/math/fraction/BigFractionField.java b/src/main/java/org/apache/commons/math/fraction/BigFractionField.java
new file mode 100644
index 0000000..bc3a7e9
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/fraction/BigFractionField.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.math.fraction;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.Field;
+
+/**
+ * Representation of the fractional numbers  without any overflow field.
+ * <p>
+ * This class is a singleton.
+ * </p>
+ * @see Fraction
+ * @version $Revision: 811827 $ $Date: 2009-09-06 17:32:50 +0200 (dim. 06 sept. 2009) $
+ * @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;
+    }
+
+    // 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 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/math/fraction/BigFractionFormat.java b/src/main/java/org/apache/commons/math/fraction/BigFractionFormat.java
new file mode 100644
index 0000000..918e5e1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/fraction/BigFractionFormat.java
@@ -0,0 +1,291 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.fraction;
+
+import java.io.Serializable;
+import java.math.BigInteger;
+import java.text.FieldPosition;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.util.Locale;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * 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.
+ * </p>
+ *
+ * @since 2.0
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ */
+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 IllegalArgumentException is <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 MathRuntimeException.createIllegalArgumentException(
+                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 ParseException if the beginning of the specified string
+     *            cannot be parsed.
+     */
+    @Override
+    public BigFraction parse(final String source) throws ParseException {
+        final ParsePosition parsePosition = new ParsePosition(0);
+        final BigFraction result = parse(source, parsePosition);
+        if (parsePosition.getIndex() == 0) {
+            throw MathRuntimeException.createParseException(
+                    parsePosition.getErrorIndex(),
+                    LocalizedFormats.UNPARSEABLE_FRACTION_NUMBER, source);
+        }
+        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/ouput 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/ouput 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/math/fraction/Fraction.java b/src/main/java/org/apache/commons/math/fraction/Fraction.java
new file mode 100644
index 0000000..82ecf63
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/fraction/Fraction.java
@@ -0,0 +1,655 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.fraction;
+
+import java.io.Serializable;
+import java.math.BigInteger;
+
+import org.apache.commons.math.FieldElement;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.exception.NullArgumentException;
+import org.apache.commons.math.util.MathUtils;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Representation of a rational number.
+ *
+ * implements Serializable since 2.0
+ *
+ * @since 1.1
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ */
+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 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, 1.0e-5, 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)</li>
+     * </ul>
+     * </p>
+     * @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.
+     */
+    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)</li>
+     * </ul>
+     * </p>
+     * @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><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.
+     * </p>
+     *
+     * See JIRA issue ticket MATH-181 for more details:
+     *
+     *     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 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 (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 ((p2 > overflow) || (q2 > overflow)) {
+                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 ArithmeticException if the denominator is <code>zero</code>
+     */
+    public Fraction(int num, int den) {
+        if (den == 0) {
+            throw MathRuntimeException.createArithmeticException(
+                  LocalizedFormats.ZERO_DENOMINATOR_IN_FRACTION, num, den);
+        }
+        if (den < 0) {
+            if (num == Integer.MIN_VALUE || den == Integer.MIN_VALUE) {
+                throw MathRuntimeException.createArithmeticException(
+                      LocalizedFormats.OVERFLOW_IN_FRACTION, num, den);
+            }
+            num = -num;
+            den = -den;
+        }
+        // reduce numerator and denominator by greatest common denominator.
+        final int d = MathUtils.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 <tt>object</tt>, +1 if this is greater
+     *         than <tt>object</tt>, 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 <tt>double</tt>. This calculates the fraction as
+     * the numerator divided by denominator.
+     * @return the fraction as a <tt>double</tt>
+     */
+    @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
+     *         <tt>null</tt>, 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 <tt>float</tt>. This calculates the fraction as
+     * the numerator divided by denominator.
+     * @return the fraction as a <tt>float</tt>
+     */
+    @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 <tt>int</tt>. 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 <tt>long</tt>. 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 MathRuntimeException.createArithmeticException(
+                  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);
+    }
+
+    /**
+     * <p>Adds the value of this fraction to another, returning the result in reduced form.
+     * The algorithm follows Knuth, 4.5.1.</p>
+     *
+     * @param fraction  the fraction to add, must not be <code>null</code>
+     * @return a <code>Fraction</code> instance with the resulting values
+     * @throws IllegalArgumentException if the fraction is <code>null</code>
+     * @throws ArithmeticException if the resulting numerator or denominator exceeds
+     *  <code>Integer.MAX_VALUE</code>
+     */
+    public Fraction add(Fraction fraction) {
+        return addSub(fraction, true /* add */);
+    }
+
+    /**
+     * Add an integer to the fraction.
+     * @param i the <tt>integer</tt> to add.
+     * @return this + i
+     */
+    public Fraction add(final int i) {
+        return new Fraction(numerator + i * denominator, denominator);
+    }
+
+    /**
+     * <p>Subtracts the value of another fraction from the value of this one,
+     * returning the result in reduced form.</p>
+     *
+     * @param fraction  the fraction to subtract, must not be <code>null</code>
+     * @return a <code>Fraction</code> instance with the resulting values
+     * @throws IllegalArgumentException if the fraction is <code>null</code>
+     * @throws ArithmeticException if the resulting numerator or denominator
+     *   cannot be represented in an <code>int</code>.
+     */
+    public Fraction subtract(Fraction fraction) {
+        return addSub(fraction, false /* subtract */);
+    }
+
+    /**
+     * Subtract an integer from the fraction.
+     * @param i the <tt>integer</tt> 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</code>
+     * @param isAdd true to add, false to subtract
+     * @return a <code>Fraction</code> instance with the resulting values
+     * @throws IllegalArgumentException if the fraction is <code>null</code>
+     * @throws ArithmeticException if the resulting numerator or denominator
+     *   cannot be represented in an <code>int</code>.
+     */
+    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 = MathUtils.gcd(denominator, fraction.denominator);
+        if (d1==1) {
+            // result is ( (u*v' +/- u'v) / u'v')
+            int uvp = MathUtils.mulAndCheck(numerator, fraction.denominator);
+            int upv = MathUtils.mulAndCheck(fraction.numerator, denominator);
+            return new Fraction
+                (isAdd ? MathUtils.addAndCheck(uvp, upv) :
+                 MathUtils.subAndCheck(uvp, upv),
+                 MathUtils.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:MathUtils.gcd(tmodd1, d1);
+
+        // result is (t/d2) / (u'/d1)(v'/d2)
+        BigInteger w = t.divide(BigInteger.valueOf(d2));
+        if (w.bitLength() > 31) {
+            throw MathRuntimeException.createArithmeticException(LocalizedFormats.NUMERATOR_OVERFLOW_AFTER_MULTIPLY,
+                                                                 w);
+        }
+        return new Fraction (w.intValue(),
+                MathUtils.mulAndCheck(denominator/d1,
+                        fraction.denominator/d2));
+    }
+
+    /**
+     * <p>Multiplies the value of this fraction by another, returning the
+     * result in reduced form.</p>
+     *
+     * @param fraction  the fraction to multiply by, must not be <code>null</code>
+     * @return a <code>Fraction</code> instance with the resulting values
+     * @throws IllegalArgumentException if the fraction is <code>null</code>
+     * @throws ArithmeticException if the resulting numerator or denominator exceeds
+     *  <code>Integer.MAX_VALUE</code>
+     */
+    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 = MathUtils.gcd(numerator, fraction.denominator);
+        int d2 = MathUtils.gcd(fraction.numerator, denominator);
+        return getReducedFraction
+        (MathUtils.mulAndCheck(numerator/d1, fraction.numerator/d2),
+                MathUtils.mulAndCheck(denominator/d2, fraction.denominator/d1));
+    }
+
+    /**
+     * Multiply the fraction by an integer.
+     * @param i the <tt>integer</tt> to multiply by.
+     * @return this * i
+     */
+    public Fraction multiply(final int i) {
+        return new Fraction(numerator * i, denominator);
+    }
+
+    /**
+     * <p>Divide the value of this fraction by another.</p>
+     *
+     * @param fraction  the fraction to divide by, must not be <code>null</code>
+     * @return a <code>Fraction</code> instance with the resulting values
+     * @throws IllegalArgumentException if the fraction is <code>null</code>
+     * @throws ArithmeticException if the fraction to divide by is zero
+     * @throws ArithmeticException if the resulting numerator or denominator exceeds
+     *  <code>Integer.MAX_VALUE</code>
+     */
+    public Fraction divide(Fraction fraction) {
+        if (fraction == null) {
+            throw new NullArgumentException(LocalizedFormats.FRACTION);
+        }
+        if (fraction.numerator == 0) {
+            throw MathRuntimeException.createArithmeticException(
+                    LocalizedFormats.ZERO_FRACTION_TO_DIVIDE_BY,
+                    fraction.numerator, fraction.denominator);
+        }
+        return multiply(fraction.reciprocal());
+    }
+
+    /**
+     * Divide the fraction by an integer.
+     * @param i the <tt>integer</tt> to divide by.
+     * @return this * i
+     */
+    public Fraction divide(final int i) {
+        return new Fraction(numerator, denominator * i);
+    }
+
+    /**
+     * <p>Creates a <code>Fraction</code> instance with the 2 parts
+     * of a fraction Y/Z.</p>
+     *
+     * <p>Any negative signs are resolved to be on the numerator.</p>
+     *
+     * @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 Fraction getReducedFraction(int numerator, int denominator) {
+        if (denominator == 0) {
+            throw MathRuntimeException.createArithmeticException(
+                  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 MathRuntimeException.createArithmeticException(
+                      LocalizedFormats.OVERFLOW_IN_FRACTION, numerator, denominator);
+            }
+            numerator = -numerator;
+            denominator = -denominator;
+        }
+        // simplify fraction.
+        int gcd = MathUtils.gcd(numerator, denominator);
+        numerator /= gcd;
+        denominator /= gcd;
+        return new Fraction(numerator, denominator);
+    }
+
+    /**
+     * <p>
+     * Returns the <code>String</code> representing this fraction, ie
+     * "num / dem" or just "num" if the denominator is one.
+     * </p>
+     *
+     * @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/math/fraction/FractionConversionException.java b/src/main/java/org/apache/commons/math/fraction/FractionConversionException.java
new file mode 100644
index 0000000..9c99fcd
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/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.math.fraction;
+
+import org.apache.commons.math.ConvergenceException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * Error thrown when a double value cannot be converted to a fraction
+ * in the allowed number of iterations.
+ *
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ * @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/math/fraction/FractionField.java b/src/main/java/org/apache/commons/math/fraction/FractionField.java
new file mode 100644
index 0000000..e6d7c47
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/fraction/FractionField.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.math.fraction;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.Field;
+
+/**
+ * Representation of the fractional numbers field.
+ * <p>
+ * This class is a singleton.
+ * </p>
+ * @see Fraction
+ * @version $Revision: 811827 $ $Date: 2009-09-06 17:32:50 +0200 (dim. 06 sept. 2009) $
+ * @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;
+    }
+
+    // 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 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/math/fraction/FractionFormat.java b/src/main/java/org/apache/commons/math/fraction/FractionFormat.java
new file mode 100644
index 0000000..b84f7cd
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/fraction/FractionFormat.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.math.fraction;
+
+import java.text.FieldPosition;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.util.Locale;
+
+import org.apache.commons.math.ConvergenceException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * 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
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ */
+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 IllegalArgumentException is <code>obj</code> is not a valid type.
+     */
+    @Override
+    public StringBuffer format(final Object obj,
+                               final StringBuffer toAppendTo, final FieldPosition pos) {
+        StringBuffer ret = null;
+
+        if (obj instanceof Fraction) {
+            ret = format((Fraction) obj, toAppendTo, pos);
+        } else if (obj instanceof Number) {
+            try {
+                ret = format(new Fraction(((Number) obj).doubleValue()),
+                             toAppendTo, pos);
+            } catch (ConvergenceException ex) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.CANNOT_CONVERT_OBJECT_TO_FRACTION,
+                    ex.getLocalizedMessage());
+            }
+        } else {
+            throw MathRuntimeException.createIllegalArgumentException(
+                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 ParseException if the beginning of the specified string
+     *            cannot be parsed.
+     */
+    @Override
+    public Fraction parse(final String source) throws ParseException {
+        final ParsePosition parsePosition = new ParsePosition(0);
+        final Fraction result = parse(source, parsePosition);
+        if (parsePosition.getIndex() == 0) {
+            throw MathRuntimeException.createParseException(
+                    parsePosition.getErrorIndex(),
+                    LocalizedFormats.UNPARSEABLE_FRACTION_NUMBER, source);
+        }
+        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/ouput 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/math/fraction/ProperBigFractionFormat.java b/src/main/java/org/apache/commons/math/fraction/ProperBigFractionFormat.java
new file mode 100644
index 0000000..398f565
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/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.math.fraction;
+
+import java.math.BigInteger;
+import java.text.FieldPosition;
+import java.text.NumberFormat;
+import java.text.ParsePosition;
+
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.exception.NullArgumentException;
+
+/**
+ * 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>.</p>
+ *
+ * @since 1.1
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ */
+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>.</p>
+     *
+     * @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/math/fraction/ProperFractionFormat.java b/src/main/java/org/apache/commons/math/fraction/ProperFractionFormat.java
new file mode 100644
index 0000000..a70925d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/fraction/ProperFractionFormat.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.math.fraction;
+
+import java.text.FieldPosition;
+import java.text.NumberFormat;
+import java.text.ParsePosition;
+
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.exception.NullArgumentException;
+import org.apache.commons.math.util.MathUtils;
+
+/**
+ * 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>.</p>
+ *
+ * @since 1.1
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ */
+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 = num % den;
+
+        if (whole != 0) {
+            getWholeFormat().format(whole, toAppendTo, pos);
+            toAppendTo.append(' ');
+            num = Math.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>.</p>
+     *
+     * @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(((Math.abs(w) * d) + n) * MathUtils.sign(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/math/fraction/package.html b/src/main/java/org/apache/commons/math/fraction/package.html
new file mode 100644
index 0000000..201ae20
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/fraction/package.html
@@ -0,0 +1,22 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $ -->
+  <body>
+    Fraction number type and fraction number formatting.
+  </body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/genetics/AbstractListChromosome.java b/src/main/java/org/apache/commons/math/genetics/AbstractListChromosome.java
new file mode 100644
index 0000000..c618dff
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/genetics/AbstractListChromosome.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.math.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
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ * @since 2.0
+ */
+public abstract class AbstractListChromosome<T> extends Chromosome {
+
+    /** List representing the chromosome */
+    private final List<T> representation;
+
+    /**
+     * Constructor.
+     * @param representation inner representation of the chromosome
+     */
+    public AbstractListChromosome(final List<T> representation) {
+        try {
+            checkValidity(representation);
+        } catch (InvalidRepresentationException e) {
+            throw new IllegalArgumentException(String.format("Invalid representation for %s", getClass().getSimpleName()), e);
+        }
+        this.representation = Collections.unmodifiableList(new ArrayList<T> (representation));
+    }
+
+    /**
+     * Constructor.
+     * @param representation inner representation of the chromosome
+     */
+    public AbstractListChromosome(final T[] representation) {
+        this(Arrays.asList(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.
+     *
+     * 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/math/genetics/BinaryChromosome.java b/src/main/java/org/apache/commons/math/genetics/BinaryChromosome.java
new file mode 100644
index 0000000..19dab38
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/genetics/BinaryChromosome.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.math.genetics;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * Chromosome represented by a vector of 0s and 1s.
+ *
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ * @since 2.0
+ */
+public abstract class BinaryChromosome extends AbstractListChromosome<Integer> {
+
+    /**
+     * Constructor.
+     * @param representation list of {0,1} values representing the chromosome
+     */
+    public BinaryChromosome(List<Integer> representation) {
+        super(representation);
+    }
+
+    /**
+     * Constructor.
+     * @param representation array of {0,1} values representing the chromosome
+     */
+    public BinaryChromosome(Integer[] representation) {
+        super(representation);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void checkValidity(List<Integer> chromosomeRepresentation) throws InvalidRepresentationException {
+        for (int i : chromosomeRepresentation) {
+            if (i < 0 || i >1)
+                throw new InvalidRepresentationException("Elements can be only 0 or 1.");
+        }
+    }
+
+    /**
+     * 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/math/genetics/BinaryMutation.java b/src/main/java/org/apache/commons/math/genetics/BinaryMutation.java
new file mode 100644
index 0000000..f762f89
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/genetics/BinaryMutation.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.math.genetics;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Mutation for {@link BinaryChromosome}s. Randomly changes one gene.
+ *
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ * @since 2.0
+ */
+public class BinaryMutation implements MutationPolicy {
+
+    /**
+     * Mutate the given chromosome. Randomly changes one gene.
+     * @param original the original chromosome.
+     * @return the mutated chromomsome.
+     */
+    public Chromosome mutate(Chromosome original) {
+        if (!(original instanceof BinaryChromosome)) {
+            throw new IllegalArgumentException("Binary mutation works on BinaryChromosome only.");
+        }
+
+        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/math/genetics/Chromosome.java b/src/main/java/org/apache/commons/math/genetics/Chromosome.java
new file mode 100644
index 0000000..5641a5b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/genetics/Chromosome.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.math.genetics;
+
+/**
+ * Individual in a population. Chromosomes are compared based on their fitness.
+ *
+ * The chromosomes are IMMUTABLE, and so their fitness is also immutable and
+ * therefore it can be cached.
+ *
+ * @since 2.0
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+public abstract class Chromosome implements Comparable<Chromosome>,Fitness {
+
+    /**
+     * Cached value of the fitness of this chromosome.
+     */
+    private double fitness = Double.MIN_VALUE;
+
+    /**
+     * Access the fitness of this chromosome. The bigger the fitness, the better
+     * the chromosome.
+     *
+     * Computation of fitness is usually very time-consuming task, therefore the
+     * fitness is cached.
+     *
+     * @return the fitness.
+     */
+    public double getFitness() {
+        if (this.fitness == Double.MIN_VALUE) {
+            // 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>
+     *     <li>1 if <code>another</code> is worse than <code>this</code></li>
+     *     <li>0 if the two chromosomes have the same fitness</li>
+     * </ul>
+     */
+    public int compareTo(Chromosome another) {
+        return ((Double)this.getFitness()).compareTo(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(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(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(Population population) {
+        Chromosome sameChromosome = findSameChromosome(population);
+        if (sameChromosome != null) {
+            fitness = sameChromosome.getFitness();
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/genetics/ChromosomePair.java b/src/main/java/org/apache/commons/math/genetics/ChromosomePair.java
new file mode 100644
index 0000000..82b048f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/genetics/ChromosomePair.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.math.genetics;
+
+/**
+ * A pair of {@link Chromosome} objects.
+ * @since 2.0
+ *
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+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/math/genetics/CrossoverPolicy.java b/src/main/java/org/apache/commons/math/genetics/CrossoverPolicy.java
new file mode 100644
index 0000000..8742dac
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/genetics/CrossoverPolicy.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.math.genetics;
+
+/**
+ * Policy used to create a pair of new chromosomes by performing a crossover
+ * operation on a source pair of chromosomes.
+ *
+ * @since 2.0
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+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.
+     */
+    ChromosomePair crossover(Chromosome first, Chromosome second);
+}
diff --git a/src/main/java/org/apache/commons/math/genetics/ElitisticListPopulation.java b/src/main/java/org/apache/commons/math/genetics/ElitisticListPopulation.java
new file mode 100644
index 0000000..045632a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/genetics/ElitisticListPopulation.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.math.genetics;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Population of chromosomes which uses elitism (certain percentace of the best
+ * chromosomes is directly copied to the next generation).
+ *
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ * @since 2.0
+ */
+public class ElitisticListPopulation extends ListPopulation {
+
+    /** percentage of chromosomes copied to the next generation */
+    private double elitismRate = 0.9;
+
+    /**
+     * Creates a new 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 %]
+     */
+    public ElitisticListPopulation(List<Chromosome> chromosomes, int populationLimit, double elitismRate) {
+        super(chromosomes, populationLimit);
+        this.elitismRate = elitismRate;
+    }
+
+    /**
+     * Creates a new ListPopulation 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 %]
+     */
+    public ElitisticListPopulation(int populationLimit, double elitismRate) {
+        super(populationLimit);
+        this.elitismRate = 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(this.getPopulationLimit(), this.getElitismRate());
+
+        List<Chromosome> oldChromosomes = this.getChromosomes();
+        Collections.sort(oldChromosomes);
+
+        // index of the last "not good enough" chromosome
+        int boundIndex = (int) FastMath.ceil((1.0 - this.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 %]
+     */
+    public void setElitismRate(double elitismRate) {
+        if (elitismRate < 0 || elitismRate > 1)
+            throw new IllegalArgumentException("Elitism rate has to be in [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/math/genetics/Fitness.java b/src/main/java/org/apache/commons/math/genetics/Fitness.java
new file mode 100644
index 0000000..40d674c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/genetics/Fitness.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.math.genetics;
+
+/**
+ * Fitness of a chromosome.
+ *
+ * @version $Revision: 811786 $ $Date: 2009-09-06 11:36:08 +0200 (dim. 06 sept. 2009) $
+ * @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/math/genetics/FixedGenerationCount.java b/src/main/java/org/apache/commons/math/genetics/FixedGenerationCount.java
new file mode 100644
index 0000000..337c5c0
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/genetics/FixedGenerationCount.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.math.genetics;
+
+/**
+ * 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.
+ *
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ * @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
+     */
+    public FixedGenerationCount(int maxGenerations) {
+        if (maxGenerations <= 0)
+            throw new IllegalArgumentException("The number of generations has to be >= 0");
+        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(Population population) {
+        if (this.numGenerations < this.maxGenerations) {
+            numGenerations++;
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * @return the number of generations that have passed
+     */
+    public int getNumGenerations() {
+        return numGenerations;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/genetics/GeneticAlgorithm.java b/src/main/java/org/apache/commons/math/genetics/GeneticAlgorithm.java
new file mode 100644
index 0000000..fc666ac
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/genetics/GeneticAlgorithm.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.math.genetics;
+
+import org.apache.commons.math.random.RandomGenerator;
+import org.apache.commons.math.random.JDKRandomGenerator;
+
+/**
+ * Implementation of a genetic algorithm. All factors that govern the operation
+ * of the algorithm can be configured for a specific problem.
+ *
+ * @since 2.0
+ * @version $Revision: 925812 $ $Date: 2010-03-21 16:49:31 +0100 (dim. 21 mars 2010) $
+ */
+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;
+
+    /**
+     * @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}
+     */
+    public GeneticAlgorithm(
+            CrossoverPolicy crossoverPolicy, double crossoverRate,
+            MutationPolicy mutationPolicy, double mutationRate,
+            SelectionPolicy selectionPolicy) {
+        if (crossoverRate < 0 || crossoverRate > 1) {
+            throw new IllegalArgumentException("crossoverRate must be between 0 and 1");
+        }
+        if (mutationRate < 0 || mutationRate > 1) {
+            throw new IllegalArgumentException("mutationRate must be between 0 and 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(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(Population initial, StoppingCondition condition) {
+        Population current = initial;
+        generationsEvolved = 0;
+        while (!condition.isSatisfied(current)) {
+            current = nextGeneration(current);
+            generationsEvolved++;
+        }
+        return current;
+    }
+
+    /**
+     * <p>Evolve the given population into the next generation.</p>
+     * <p><ol>
+     *    <li>Get nextGeneration population to fill from <code>current</code>
+     *        generation, using its nextGeneration method</li>
+     *    <li>Loop until new generation is filled:</li>
+     *    <ul><li>Apply configured SelectionPolicy to select a pair of parents
+     *            from <code>current</code></li>
+     *        <li>With probability = {@link #getCrossoverRate()}, apply
+     *            configured {@link CrossoverPolicy} to parents</li>
+     *        <li>With probability = {@link #getMutationRate()}, apply
+     *            configured {@link MutationPolicy} to each of the offspring</li>
+     *        <li>Add offspring individually to nextGeneration,
+     *            space permitting</li>
+     *    </ul>
+     *    <li>Return nextGeneration</li>
+     *    </ol>
+     * </p>
+     *
+     * @param current the current population.
+     * @return the population for the next generation.
+     */
+    public Population nextGeneration(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/math/genetics/InvalidRepresentationException.java b/src/main/java/org/apache/commons/math/genetics/InvalidRepresentationException.java
new file mode 100644
index 0000000..b60ded8
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/genetics/InvalidRepresentationException.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.math.genetics;
+
+/**
+ * Exception indicating that the representation of a chromosome is not valid.
+ *
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ * @since 2.0
+ */
+public class InvalidRepresentationException extends Exception {
+
+    /** Serialization version id */
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Constructor
+     */
+    public InvalidRepresentationException() {
+        super();
+    }
+
+    /**
+     * Construct an InvalidRepresentationException
+     * @param arg0 exception message
+     */
+    public InvalidRepresentationException(String arg0) {
+        super(arg0);
+    }
+
+    /**
+     * Construct an InvalidRepresentationException
+     * @param arg0 cause
+     */
+    public InvalidRepresentationException(Throwable arg0) {
+        super(arg0);
+    }
+
+    /**
+     * Construct an InvalidRepresentationException
+     *
+     * @param arg0 exception message
+     * @param arg1 cause
+     */
+    public InvalidRepresentationException(String arg0, Throwable arg1) {
+        super(arg0, arg1);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/genetics/ListPopulation.java b/src/main/java/org/apache/commons/math/genetics/ListPopulation.java
new file mode 100644
index 0000000..e880b2c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/genetics/ListPopulation.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.math.genetics;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.exception.NotPositiveException;
+import org.apache.commons.math.exception.NumberIsTooLargeException;
+
+/**
+ * Population of chromosomes represented by a {@link List}.
+ *
+ * @since 2.0
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ */
+public abstract class ListPopulation implements Population {
+
+    /** List of chromosomes */
+    private List<Chromosome> chromosomes;
+
+    /** maximial size of the population */
+    private int populationLimit;
+
+
+    /**
+     * Creates a new ListPopulation instance.
+     *
+     * @param chromosomes list of chromosomes in the population
+     * @param populationLimit maximal size of the population
+     */
+    public ListPopulation (List<Chromosome> chromosomes, int populationLimit) {
+        if (chromosomes.size() > populationLimit) {
+            throw new NumberIsTooLargeException(LocalizedFormats.LIST_OF_CHROMOSOMES_BIGGER_THAN_POPULATION_SIZE,
+                                                chromosomes.size(), populationLimit, false);
+        }
+        if (populationLimit < 0) {
+            throw new NotPositiveException(LocalizedFormats.POPULATION_LIMIT_NOT_POSITIVE, populationLimit);
+        }
+
+        this.chromosomes = chromosomes;
+        this.populationLimit = populationLimit;
+    }
+
+    /**
+     * Creates a new ListPopulation instance and initializes its inner
+     * chromosome list.
+     *
+     * @param populationLimit maximal size of the population
+     */
+    public ListPopulation (int populationLimit) {
+        if (populationLimit < 0) {
+            throw new NotPositiveException(LocalizedFormats.POPULATION_LIMIT_NOT_POSITIVE, populationLimit);
+        }
+        this.populationLimit = populationLimit;
+        this.chromosomes = new ArrayList<Chromosome>(populationLimit);
+    }
+
+    /**
+     * Sets the list of chromosomes.
+     * @param chromosomes the list of chromosomes
+     */
+    public void setChromosomes(List<Chromosome> chromosomes) {
+        this.chromosomes = chromosomes;
+    }
+
+    /**
+     * Access the list of chromosomes.
+     * @return the list of chromosomes
+     */
+    public List<Chromosome> getChromosomes() {
+        return chromosomes;
+    }
+
+    /**
+     * Add the given chromosome to the population.
+     * @param chromosome the chromosome to add.
+     */
+    public void addChromosome(Chromosome chromosome) {
+        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.
+     */
+    public void setPopulationLimit(int populationLimit) {
+        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();
+    }
+
+    /**
+     * Chromosome list iterator
+     *
+     * @return chromosome iterator
+     */
+    public Iterator<Chromosome> iterator() {
+        return chromosomes.iterator();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/genetics/MutationPolicy.java b/src/main/java/org/apache/commons/math/genetics/MutationPolicy.java
new file mode 100644
index 0000000..9753db7
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/genetics/MutationPolicy.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.math.genetics;
+
+/**
+ * Algorithm used to mutate a chrommosome.
+ *
+ * @since 2.0
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+public interface MutationPolicy {
+
+    /**
+     * Mutate the given chromosome.
+     * @param original the original chromosome.
+     * @return the mutated chromomsome.
+     */
+    Chromosome mutate(Chromosome original);
+}
diff --git a/src/main/java/org/apache/commons/math/genetics/OnePointCrossover.java b/src/main/java/org/apache/commons/math/genetics/OnePointCrossover.java
new file mode 100644
index 0000000..f5f6ffd
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/genetics/OnePointCrossover.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.math.genetics;
+
+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.
+ *
+ * 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    p2 = (0 1 1 0 1 0  | 0 1 1)
+ * </pre>
+ *
+ * This policy works only on {@link AbstractListChromosome}, and therefore it
+ * is parametrized by T. Moreover, the chromosomes must have same lengths.
+ *
+ * @param <T> generic type of the {@link AbstractListChromosome}s for crossover
+ * @since 2.0
+ * @version $Revision: 903046 $ $Date: 2010-01-26 03:07:26 +0100 (mar. 26 janv. 2010) $
+ *
+ */
+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.
+     *
+     * Example:
+     * -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    p2 = (0 1 1 0 1 0  | 0 1 1)
+     *
+     * @param first first parent (p1)
+     * @param second second parent (p2)
+     * @return pair of two children (c1,c2)
+     */
+    @SuppressWarnings("unchecked") // OK because of instanceof checks
+    public ChromosomePair crossover(Chromosome first, Chromosome second) {
+        if (! (first instanceof AbstractListChromosome<?> && second instanceof AbstractListChromosome<?>)) {
+            throw new IllegalArgumentException("One point crossover works on FixedLengthChromosomes only.");
+        }
+        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.
+     */
+    private ChromosomePair crossover(AbstractListChromosome<T> first, AbstractListChromosome<T> second) {
+        int length = first.getLength();
+        if (length != second.getLength())
+            throw new IllegalArgumentException("Both chromosomes must have same lengths.");
+
+        // array representations of the parents
+        List<T> parent1Rep = first.getRepresentation();
+        List<T> parent2Rep = second.getRepresentation();
+        // and of the children
+        ArrayList<T> child1Rep = new ArrayList<T> (first.getLength());
+        ArrayList<T> child2Rep = new ArrayList<T> (second.getLength());
+
+        // select a crossover point at random (0 and length makes no sense)
+        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/math/genetics/PermutationChromosome.java b/src/main/java/org/apache/commons/math/genetics/PermutationChromosome.java
new file mode 100644
index 0000000..676b5dc
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/genetics/PermutationChromosome.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.math.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
+ * @version $Revision: 811786 $ $Date: 2009-09-06 11:36:08 +0200 (dim. 06 sept. 2009) $
+ */
+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/math/genetics/Population.java b/src/main/java/org/apache/commons/math/genetics/Population.java
new file mode 100644
index 0000000..3fc758a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/genetics/Population.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.math.genetics;
+
+/**
+ * A collection of chromosomes that facilitates generational evolution.
+ *
+ * @since 2.0
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+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.
+     */
+    void addChromosome(Chromosome chromosome);
+
+    /**
+     * Access the fittest chromosome in this population.
+     * @return the fittest chromosome.
+     */
+    Chromosome getFittestChromosome();
+}
diff --git a/src/main/java/org/apache/commons/math/genetics/RandomKey.java b/src/main/java/org/apache/commons/math/genetics/RandomKey.java
new file mode 100644
index 0000000..1cd28d3
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/genetics/RandomKey.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.math.genetics;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * <p>
+ * 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>
+ *
+ * <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>
+ *
+ * <p>
+ * With this representation, common operators like n-point crossover can be
+ * used, because any such chromosome represents a valid permutation.
+ * </p>
+ *
+ * <p>
+ * Since the chromosome (and thus its arrayRepresentation) is immutable, the
+ * array representation is sorted only once in the constructor.
+ * </p>
+ *
+ * <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>
+ * <li>Rothlauf, F.: Representations for Genetic and Evolutionary Algorithms.
+ * Volume 104 of Studies in Fuzziness and Soft Computing. Physica-Verlag,
+ * Heidelberg (2002)</li>
+ * </ul>
+ * </p>
+ *
+ * @param <T>
+ *            type of the permuted objects
+ * @since 2.0
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+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 accorting to the representation (unmodifiable).
+     */
+    private final List<Integer> baseSeqPermutation;
+
+    /**
+     * Constructor.
+     *
+     * @param representation list of [0,1] values representing the permutation
+     */
+    public RandomKey(List<Double> representation) {
+        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
+     */
+    public RandomKey(Double[] representation) {
+        this(Arrays.asList(representation));
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public List<T> decode(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
+     */
+    private static <S> List<S> decodeGeneric(List<S> sequence, List<Double> representation, List<Double> sortedRepr) {
+        int l = sequence.size();
+
+        if (representation.size() != l) {
+            throw new IllegalArgumentException(String.format("Length of sequence for decoding (%s) has to be equal to the length of the RandomKey (%s)", l, representation.size()));
+        }
+        if (representation.size() != sortedRepr.size()) {
+            throw new IllegalArgumentException(String.format("Representation and sortedRepr must have same sizes, %d != %d", representation.size(), sortedRepr.size()));
+        }
+
+        List<Double> reprCopy = new ArrayList<Double> (representation);// do not modify the orig. 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(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(java.util.List<Double> chromosomeRepresentation) throws InvalidRepresentationException {
+        for (double val : chromosomeRepresentation) {
+            if (val < 0 || val > 1) {
+                throw new InvalidRepresentationException("Values of representation must be in [0,1] interval");
+            }
+        }
+    }
+
+
+    /**
+     * 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(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(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.
+     *
+     * 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(List<S> data, 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>.
+     *
+     * 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 IllegalArgumentException iff the <code>permutedData</code> and <code>originalData</code> contains different data
+     */
+    public static <S> List<Double> inducedPermutation(List<S> originalData, List<S> permutedData) throws IllegalArgumentException {
+        if (originalData.size() != permutedData.size()) {
+            throw new IllegalArgumentException("originalData and permutedData must have same length");
+        }
+        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 IllegalArgumentException("originalData and permutedData must contain the same objects.");
+            }
+            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(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/math/genetics/RandomKeyMutation.java b/src/main/java/org/apache/commons/math/genetics/RandomKeyMutation.java
new file mode 100644
index 0000000..792eef2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/genetics/RandomKeyMutation.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.math.genetics;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * 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
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ */
+public class RandomKeyMutation implements MutationPolicy {
+
+    /**
+     * {@inheritDoc}
+     *
+     * @throws IllegalArgumentException if <code>original</code> is not a
+     * {@link RandomKey} instance
+     */
+    public Chromosome mutate(Chromosome original) {
+        if (!(original instanceof RandomKey<?>)) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    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/math/genetics/SelectionPolicy.java b/src/main/java/org/apache/commons/math/genetics/SelectionPolicy.java
new file mode 100644
index 0000000..4cf6768
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/genetics/SelectionPolicy.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.math.genetics;
+
+/**
+ * Algorithm used to select a chromosome pair from a population.
+ *
+ * @since 2.0
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+public interface SelectionPolicy {
+    /**
+     * Select two chromosomes from the population.
+     * @param population the population from which the chromosomes are choosen.
+     * @return the selected chromosomes.
+     */
+    ChromosomePair select(Population population);
+}
diff --git a/src/main/java/org/apache/commons/math/genetics/StoppingCondition.java b/src/main/java/org/apache/commons/math/genetics/StoppingCondition.java
new file mode 100644
index 0000000..0253ce9
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/genetics/StoppingCondition.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.math.genetics;
+
+/**
+ * Algorithm used to determine when to stop evolution.
+ *
+ * @since 2.0
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+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/math/genetics/TournamentSelection.java b/src/main/java/org/apache/commons/math/genetics/TournamentSelection.java
new file mode 100644
index 0000000..f1a091f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/genetics/TournamentSelection.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.math.genetics;
+
+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
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+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(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 choosen.
+     * @return the selected chromosomes.
+     */
+    public ChromosomePair select(Population population) {
+        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 choosen.
+     * @return the selected chromosome.
+     */
+    private Chromosome tournament(ListPopulation population) {
+        if (population.getPopulationSize() < this.arity)
+            throw new IllegalArgumentException("Tournament arity cannot be bigger than population size.");
+        // auxiliary population
+        ListPopulation tournamentPopulation = new ListPopulation(this.arity) {
+            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(int arity) {
+        this.arity = arity;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/genetics/package.html b/src/main/java/org/apache/commons/math/genetics/package.html
new file mode 100644
index 0000000..adcd5a9
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/genetics/package.html
@@ -0,0 +1,24 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 784604 $ -->
+<body>
+<p>
+This package provides Genetic Algorithms components and implementations.
+</p>
+</body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/geometry/CardanEulerSingularityException.java b/src/main/java/org/apache/commons/math/geometry/CardanEulerSingularityException.java
new file mode 100644
index 0000000..61d349e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/geometry/CardanEulerSingularityException.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.math.geometry;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/** This class represents exceptions thrown while extractiong Cardan
+ * or Euler angles from a rotation.
+
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ * @since 1.2
+ */
+public class CardanEulerSingularityException
+  extends MathException {
+
+    /** 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/math/geometry/NotARotationMatrixException.java b/src/main/java/org/apache/commons/math/geometry/NotARotationMatrixException.java
new file mode 100644
index 0000000..0b6ff8d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/geometry/NotARotationMatrixException.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.math.geometry;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.exception.util.Localizable;
+
+/**
+ * This class represents exceptions thrown while building rotations
+ * from matrices.
+ *
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ * @since 1.2
+ */
+
+public class NotARotationMatrixException
+  extends MathException {
+
+    /** 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)
+     * @deprecated as of 2.2 replaced by {@link #NotARotationMatrixException(Localizable, Object...)}
+     */
+    @Deprecated
+    public NotARotationMatrixException(String specifier, Object ... parts) {
+        super(specifier, parts);
+    }
+
+    /**
+     * 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/math/geometry/Rotation.java b/src/main/java/org/apache/commons/math/geometry/Rotation.java
new file mode 100644
index 0000000..ee3f4b7
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/geometry/Rotation.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.math.geometry;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * 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>
+ *
+ * @version $Revision: 1067500 $ $Date: 2011-02-05 21:11:30 +0100 (sam. 05 févr. 2011) $
+ * @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>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(Vector3D) 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 ArithmeticException if the axis norm is zero
+   */
+  public Rotation(Vector3D axis, double angle) {
+
+    double norm = axis.getNorm();
+    if (norm == 0) {
+      throw MathRuntimeException.createArithmeticException(LocalizedFormats.ZERO_NORM_FOR_ROTATION_AXIS);
+    }
+
+    double halfAngle = -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);
+    }
+
+    // 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
+      q0 = 0.5 * FastMath.sqrt(s + 1.0);
+      double inv = 0.25 / q0;
+      q1 = inv * (ort[1][2] - ort[2][1]);
+      q2 = inv * (ort[2][0] - ort[0][2]);
+      q3 = 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
+        q1 = 0.5 * FastMath.sqrt(s + 1.0);
+        double inv = 0.25 / q1;
+        q0 = inv * (ort[1][2] - ort[2][1]);
+        q2 = inv * (ort[0][1] + ort[1][0]);
+        q3 = 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
+          q2 = 0.5 * FastMath.sqrt(s + 1.0);
+          double inv = 0.25 / q2;
+          q0 = inv * (ort[2][0] - ort[0][2]);
+          q1 = inv * (ort[0][1] + ort[1][0]);
+          q3 = 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];
+          q3 = 0.5 * FastMath.sqrt(s + 1.0);
+          double inv = 0.25 / q3;
+          q0 = inv * (ort[0][1] - ort[1][0]);
+          q1 = inv * (ort[0][2] + ort[2][0]);
+          q2 = inv * (ort[2][1] + ort[1][2]);
+        }
+      }
+    }
+
+  }
+
+  /** Build the rotation that transforms a pair of vector 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 (v<sub>1</sub>,
+   * v<sub>2</sub>) 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 IllegalArgumentException if the norm of one of the vectors is zero
+   */
+  public Rotation(Vector3D u1, Vector3D u2, Vector3D v1, Vector3D v2) {
+
+  // norms computation
+  double u1u1 = Vector3D.dotProduct(u1, u1);
+  double u2u2 = Vector3D.dotProduct(u2, u2);
+  double v1v1 = Vector3D.dotProduct(v1, v1);
+  double v2v2 = Vector3D.dotProduct(v2, v2);
+  if ((u1u1 == 0) || (u2u2 == 0) || (v1v1 == 0) || (v2v2 == 0)) {
+    throw MathRuntimeException.createIllegalArgumentException(LocalizedFormats.ZERO_NORM_FOR_ROTATION_DEFINING_VECTOR);
+  }
+
+  double u1x = u1.getX();
+  double u1y = u1.getY();
+  double u1z = u1.getZ();
+
+  double u2x = u2.getX();
+  double u2y = u2.getY();
+  double u2z = u2.getZ();
+
+  // normalize v1 in order to have (v1'|v1') = (u1|u1)
+  double coeff = FastMath.sqrt (u1u1 / v1v1);
+  double v1x   = coeff * v1.getX();
+  double v1y   = coeff * v1.getY();
+  double v1z   = coeff * v1.getZ();
+  v1 = new Vector3D(v1x, v1y, v1z);
+
+  // adjust v2 in order to have (u1|u2) = (v1|v2) and (v2'|v2') = (u2|u2)
+  double u1u2   = Vector3D.dotProduct(u1, u2);
+  double v1v2   = Vector3D.dotProduct(v1, v2);
+  double coeffU = u1u2 / u1u1;
+  double coeffV = v1v2 / u1u1;
+  double beta   = FastMath.sqrt((u2u2 - u1u2 * coeffU) / (v2v2 - v1v2 * coeffV));
+  double alpha  = coeffU - beta * coeffV;
+  double v2x    = alpha * v1x + beta * v2.getX();
+  double v2y    = alpha * v1y + beta * v2.getY();
+  double v2z    = alpha * v1z + beta * v2.getZ();
+  v2 = new Vector3D(v2x, v2y, v2z);
+
+  // preliminary computation (we use explicit formulation instead
+  // of relying on the Vector3D class in order to avoid building lots
+  // of temporary objects)
+  Vector3D uRef = u1;
+  Vector3D vRef = v1;
+  double dx1 = v1x - u1.getX();
+  double dy1 = v1y - u1.getY();
+  double dz1 = v1z - u1.getZ();
+  double dx2 = v2x - u2.getX();
+  double dy2 = v2y - u2.getY();
+  double dz2 = v2z - u2.getZ();
+  Vector3D k = new Vector3D(dy1 * dz2 - dz1 * dy2,
+                            dz1 * dx2 - dx1 * dz2,
+                            dx1 * dy2 - dy1 * dx2);
+  double c = k.getX() * (u1y * u2z - u1z * u2y) +
+             k.getY() * (u1z * u2x - u1x * u2z) +
+             k.getZ() * (u1x * u2y - u1y * u2x);
+
+  if (c == 0) {
+    // the (q1, q2, q3) vector is in the (u1, u2) plane
+    // we try other vectors
+    Vector3D u3 = Vector3D.crossProduct(u1, u2);
+    Vector3D v3 = Vector3D.crossProduct(v1, v2);
+    double u3x  = u3.getX();
+    double u3y  = u3.getY();
+    double u3z  = u3.getZ();
+    double v3x  = v3.getX();
+    double v3y  = v3.getY();
+    double v3z  = v3.getZ();
+
+    double dx3 = v3x - u3x;
+    double dy3 = v3y - u3y;
+    double dz3 = v3z - u3z;
+    k = new Vector3D(dy1 * dz3 - dz1 * dy3,
+                     dz1 * dx3 - dx1 * dz3,
+                     dx1 * dy3 - dy1 * dx3);
+    c = k.getX() * (u1y * u3z - u1z * u3y) +
+        k.getY() * (u1z * u3x - u1x * u3z) +
+        k.getZ() * (u1x * u3y - u1y * u3x);
+
+    if (c == 0) {
+      // the (q1, q2, q3) vector is aligned with u1:
+      // we try (u2, u3) and (v2, v3)
+      k = new Vector3D(dy2 * dz3 - dz2 * dy3,
+                       dz2 * dx3 - dx2 * dz3,
+                       dx2 * dy3 - dy2 * dx3);
+      c = k.getX() * (u2y * u3z - u2z * u3y) +
+          k.getY() * (u2z * u3x - u2x * u3z) +
+          k.getZ() * (u2x * u3y - u2y * u3x);
+
+      if (c == 0) {
+        // the (q1, q2, q3) vector is aligned with everything
+        // this is really the identity rotation
+        q0 = 1.0;
+        q1 = 0.0;
+        q2 = 0.0;
+        q3 = 0.0;
+        return;
+      }
+
+      // we will have to use u2 and v2 to compute the scalar part
+      uRef = u2;
+      vRef = v2;
+
+    }
+
+  }
+
+  // compute the vectorial part
+  c = FastMath.sqrt(c);
+  double inv = 1.0 / (c + c);
+  q1 = inv * k.getX();
+  q2 = inv * k.getY();
+  q3 = inv * k.getZ();
+
+  // compute the scalar part
+   k = new Vector3D(uRef.getY() * q3 - uRef.getZ() * q2,
+                    uRef.getZ() * q1 - uRef.getX() * q3,
+                    uRef.getX() * q2 - uRef.getY() * q1);
+   c = Vector3D.dotProduct(k, k);
+  q0 = Vector3D.dotProduct(vRef, k) / (c + c);
+
+  }
+
+  /** 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 colinear, an
+   * arbitrary rotation axis is chosen.</p>
+   *
+   * @param u origin vector
+   * @param v desired image of u by the rotation
+   * @exception IllegalArgumentException if the norm of one of the vectors is zero
+   */
+  public Rotation(Vector3D u, Vector3D v) {
+
+    double normProduct = u.getNorm() * v.getNorm();
+    if (normProduct == 0) {
+        throw MathRuntimeException.createIllegalArgumentException(LocalizedFormats.ZERO_NORM_FOR_ROTATION_DEFINING_VECTOR);
+    }
+
+    double dot = Vector3D.dotProduct(u, 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);
+      q1 = coeff * (v.getY() * u.getZ() - v.getZ() * u.getY());
+      q2 = coeff * (v.getZ() * u.getX() - v.getX() * u.getZ());
+      q3 = coeff * (v.getX() * u.getY() - v.getY() * u.getX());
+    }
+
+  }
+
+  /** 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
+   */
+  public Rotation(RotationOrder order,
+                  double alpha1, double alpha2, double alpha3) {
+    Rotation r1 = new Rotation(order.getA1(), alpha1);
+    Rotation r2 = new Rotation(order.getA2(), alpha2);
+    Rotation r3 = new Rotation(order.getA3(), alpha3);
+    Rotation composed = r1.applyTo(r2.applyTo(r3));
+    q0 = composed.q0;
+    q1 = composed.q1;
+    q2 = composed.q2;
+    q3 = composed.q3;
+  }
+
+  /** 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.
+   * @return normalized axis of the rotation
+   * @see #Rotation(Vector3D, double)
+   */
+  public Vector3D getAxis() {
+    double squaredSine = q1 * q1 + q2 * q2 + q3 * q3;
+    if (squaredSine == 0) {
+      return new Vector3D(1, 0, 0);
+    } else if (q0 < 0) {
+      double inverse = 1 / FastMath.sqrt(squaredSine);
+      return new Vector3D(q1 * inverse, q2 * inverse, q3 * inverse);
+    }
+    double inverse = -1 / 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>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
+   */
+  public double[] getAngles(RotationOrder order)
+    throws CardanEulerSingularityException {
+
+    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())
+      };
+
+    }
+
+  }
+
+  /** 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 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 instance to another rotation.
+   * Applying the instance to a rotation is computing the composition
+   * in an order compliant with the following rule : let u be any
+   * vector and v its image by r (i.e. r.applyTo(u) = v), let w be the image
+   * of v by the instance (i.e. applyTo(v) = w), then w = comp.applyTo(u),
+   * where comp = applyTo(r).
+   * @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 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.
+   * Applying the inverse of the instance to a rotation is computing
+   * the composition in an order compliant with the following rule :
+   * let u be any vector and v its image by r (i.e. r.applyTo(u) = v),
+   * let w be the inverse image of v by the instance
+   * (i.e. applyInverseTo(v) = w), then w = comp.applyTo(u), where
+   * comp = applyInverseTo(r).
+   * @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 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.applyInverseTo(r2).getAngle();
+  }
+
+}
diff --git a/src/main/java/org/apache/commons/math/geometry/RotationOrder.java b/src/main/java/org/apache/commons/math/geometry/RotationOrder.java
new file mode 100644
index 0000000..9292b14
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/geometry/RotationOrder.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.math.geometry;
+
+/**
+ * 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.
+ *
+ * @version $Revision: 811827 $ $Date: 2009-09-06 17:32:50 +0200 (dim. 06 sept. 2009) $
+ * @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/math/geometry/Vector3D.java b/src/main/java/org/apache/commons/math/geometry/Vector3D.java
new file mode 100644
index 0000000..db18795
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/geometry/Vector3D.java
@@ -0,0 +1,534 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.geometry;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.util.MathUtils;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * This class implements vectors in a three-dimensional space.
+ * <p>Instance of this class are guaranteed to be immutable.</p>
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ * @since 1.2
+ */
+
+public class Vector3D
+  implements Serializable {
+
+  /** 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);
+
+  /** Default format. */
+  private static final Vector3DFormat DEFAULT_FORMAT =
+      Vector3DFormat.getInstance();
+
+  /** Serializable version identifier. */
+  private static final long serialVersionUID = 5133268763396045979L;
+
+  /** 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 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 = a1 * u1.x + a2 * u2.x;
+    this.y = a1 * u1.y + a2 * u2.y;
+    this.z = 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 = a1 * u1.x + a2 * u2.x + a3 * u3.x;
+    this.y = a1 * u1.y + a2 * u2.y + a3 * u3.y;
+    this.z = 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 = 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;
+    this.z = 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 L<sub>1</sub> norm for the vector.
+   * @return L<sub>1</sub> norm for the vector
+   */
+  public double getNorm1() {
+    return FastMath.abs(x) + FastMath.abs(y) + FastMath.abs(z);
+  }
+
+  /** Get the L<sub>2</sub> norm for the vector.
+   * @return euclidian norm for the vector
+   */
+  public double getNorm() {
+    return FastMath.sqrt (x * x + y * y + z * z);
+  }
+
+  /** Get the square of the norm for the vector.
+   * @return square of the euclidian norm for the vector
+   */
+  public double getNormSq() {
+    return x * x + y * y + z * z;
+  }
+
+  /** Get the L<sub>&infin;</sub> norm for the vector.
+   * @return L<sub>&infin;</sub> norm for the vector
+   */
+  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());
+  }
+
+  /** Add a vector to the instance.
+   * @param v vector to add
+   * @return a new vector
+   */
+  public Vector3D add(Vector3D v) {
+    return new Vector3D(x + v.x, y + v.y, z + v.z);
+  }
+
+  /** 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 Vector3D add(double factor, Vector3D v) {
+    return new Vector3D(x + factor * v.x, y + factor * v.y, z + factor * v.z);
+  }
+
+  /** Subtract a vector from the instance.
+   * @param v vector to subtract
+   * @return a new vector
+   */
+  public Vector3D subtract(Vector3D v) {
+    return new Vector3D(x - v.x, y - v.y, z - v.z);
+  }
+
+  /** 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 Vector3D subtract(double factor, Vector3D v) {
+    return new Vector3D(x - factor * v.x, y - factor * v.y, z - factor * v.z);
+  }
+
+  /** Get a normalized vector aligned with the instance.
+   * @return a new normalized vector
+   * @exception ArithmeticException if the norm is zero
+   */
+  public Vector3D normalize() {
+    double s = getNorm();
+    if (s == 0) {
+      throw MathRuntimeException.createArithmeticException(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 ArithmeticException if the norm of the instance is null
+   */
+  public Vector3D orthogonal() {
+
+    double threshold = 0.6 * getNorm();
+    if (threshold == 0) {
+      throw MathRuntimeException.createArithmeticException(LocalizedFormats.ZERO_NORM);
+    }
+
+    if ((x >= -threshold) && (x <= threshold)) {
+      double inverse  = 1 / FastMath.sqrt(y * y + z * z);
+      return new Vector3D(0, inverse * z, -inverse * y);
+    } else if ((y >= -threshold) && (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 ArithmeticException if either vector has a null norm
+   */
+  public static double angle(Vector3D v1, Vector3D v2) {
+
+    double normProduct = v1.getNorm() * v2.getNorm();
+    if (normProduct == 0) {
+      throw MathRuntimeException.createArithmeticException(LocalizedFormats.ZERO_NORM);
+    }
+
+    double dot = dotProduct(v1, 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);
+
+  }
+
+  /** Get the opposite of the instance.
+   * @return a new vector which is opposite to the instance
+   */
+  public Vector3D negate() {
+    return new Vector3D(-x, -y, -z);
+  }
+
+  /** Multiply the instance by a scalar
+   * @param a scalar
+   * @return a new vector
+   */
+  public Vector3D scalarMultiply(double a) {
+    return new Vector3D(a * x, a * y, a * z);
+  }
+
+  /**
+   * 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) || Double.isNaN(y) || Double.isNaN(z);
+  }
+
+  /**
+   * 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) || 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 8;
+      }
+      return 31 * (23 * MathUtils.hash(x) +  19 * MathUtils.hash(y) +  MathUtils.hash(z));
+  }
+
+  /** 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.x * v2.x + v1.y * v2.y + v1.z * v2.z;
+  }
+
+  /** 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(Vector3D v1, Vector3D v2) {
+    return new Vector3D(v1.y * v2.z - v1.z * v2.y,
+                        v1.z * v2.x - v1.x * v2.z,
+                        v1.x * v2.y - v1.y * 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
+   * @return the distance between v1 and v2 according to the L<sub>1</sub> norm
+   */
+  public static double distance1(Vector3D v1, Vector3D v2) {
+    final double dx = FastMath.abs(v2.x - v1.x);
+    final double dy = FastMath.abs(v2.y - v1.y);
+    final double dz = FastMath.abs(v2.z - v1.z);
+    return dx + dy + dz;
+  }
+
+  /** 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) {
+    final double dx = v2.x - v1.x;
+    final double dy = v2.y - v1.y;
+    final double dz = v2.z - v1.z;
+    return FastMath.sqrt(dx * dx + dy * dy + dz * dz);
+  }
+
+  /** 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) {
+    final double dx = FastMath.abs(v2.x - v1.x);
+    final double dy = FastMath.abs(v2.y - v1.y);
+    final double dz = FastMath.abs(v2.z - v1.z);
+    return FastMath.max(FastMath.max(dx, dy), dz);
+  }
+
+  /** 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) {
+    final double dx = v2.x - v1.x;
+    final double dy = v2.y - v1.y;
+    final double dz = v2.z - v1.z;
+    return dx * dx + dy * dy + dz * dz;
+  }
+
+  /** Get a string representation of this vector.
+   * @return a string representation of this vector
+   */
+  @Override
+  public String toString() {
+      return DEFAULT_FORMAT.format(this);
+  }
+
+}
diff --git a/src/main/java/org/apache/commons/math/geometry/Vector3DFormat.java b/src/main/java/org/apache/commons/math/geometry/Vector3DFormat.java
new file mode 100644
index 0000000..7400e20
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/geometry/Vector3DFormat.java
@@ -0,0 +1,343 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.geometry;
+
+import java.text.FieldPosition;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.util.Locale;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.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>
+ *
+ * @version $Revision: 1003886 $ $Date: 2010-10-02 23:04:44 +0200 (sam. 02 oct. 2010) $
+ */
+public class Vector3DFormat extends CompositeFormat {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -5447606608652576301L;
+
+    /** 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.</p>
+     */
+    public Vector3DFormat() {
+        this(DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_SEPARATOR, getDefaultNumberFormat());
+    }
+
+    /**
+     * Create an instance with a custom number format for components.
+     * @param format the custom format for components.
+     */
+    public Vector3DFormat(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 Vector3DFormat(final String prefix, final String suffix,
+                          final String separator) {
+        this(prefix, suffix, separator, 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) {
+        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 3D vectors formats are available.
+     * <p>This is the same set as the {@link NumberFormat} set.</p>
+     * @return available 3D 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 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(getDefaultNumberFormat(locale));
+    }
+
+    /**
+     * This static method calls {@link #format(Object)} on a default instance of
+     * Vector3DFormat.
+     *
+     * @param v Vector3D object to format
+     * @return A formatted vector
+     */
+    public static String formatVector3D(Vector3D v) {
+        return getInstance().format(v);
+    }
+
+    /**
+     * 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.
+     */
+    public StringBuffer format(Vector3D vector, StringBuffer toAppendTo,
+                               FieldPosition pos) {
+
+        pos.setBeginIndex(0);
+        pos.setEndIndex(0);
+
+        // format prefix
+        toAppendTo.append(prefix);
+
+        // format components
+        formatDouble(vector.getX(), format, toAppendTo, pos);
+        toAppendTo.append(separator);
+        formatDouble(vector.getY(), format, toAppendTo, pos);
+        toAppendTo.append(separator);
+        formatDouble(vector.getZ(), format, toAppendTo, pos);
+
+        // format suffix
+        toAppendTo.append(suffix);
+
+        return toAppendTo;
+
+    }
+
+    /**
+     * Formats a object to produce a string.
+     * <p><code>obj</code> must be a  {@link Vector3D} object. Any other type of
+     * object will result in an {@link IllegalArgumentException} being thrown.</p>
+     * @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 IllegalArgumentException is <code>obj</code> is not a valid type.
+     */
+    @Override
+    public StringBuffer format(Object obj, StringBuffer toAppendTo,
+                               FieldPosition pos) {
+
+        if (obj instanceof Vector3D) {
+            return format( (Vector3D)obj, toAppendTo, pos);
+        }
+
+        throw MathRuntimeException.createIllegalArgumentException(LocalizedFormats.CANNOT_FORMAT_INSTANCE_AS_3D_VECTOR,
+                                                                  obj.getClass().getName());
+
+    }
+
+    /**
+     * Parses a string to produce a {@link Vector3D} object.
+     * @param source the string to parse
+     * @return the parsed {@link Vector3D} object.
+     * @exception ParseException if the beginning of the specified string
+     *            cannot be parsed.
+     */
+    public Vector3D parse(String source) throws ParseException {
+        ParsePosition parsePosition = new ParsePosition(0);
+        Vector3D result = parse(source, parsePosition);
+        if (parsePosition.getIndex() == 0) {
+            throw MathRuntimeException.createParseException(
+                    parsePosition.getErrorIndex(),
+                    LocalizedFormats.UNPARSEABLE_3D_VECTOR, source);
+        }
+        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.
+     */
+    public Vector3D parse(String source, ParsePosition pos) {
+        int initialIndex = pos.getIndex();
+
+        // parse prefix
+        parseAndIgnoreWhitespace(source, pos);
+        if (!parseFixedstring(source, trimmedPrefix, pos)) {
+            return null;
+        }
+
+        // parse X component
+        parseAndIgnoreWhitespace(source, pos);
+        Number x = parseNumber(source, format, pos);
+        if (x == null) {
+            // invalid abscissa
+            // set index back to initial, error index should already be set
+            pos.setIndex(initialIndex);
+            return null;
+        }
+
+        // parse Y component
+        parseAndIgnoreWhitespace(source, pos);
+        if (!parseFixedstring(source, trimmedSeparator, pos)) {
+            return null;
+        }
+        parseAndIgnoreWhitespace(source, pos);
+        Number y = parseNumber(source, format, pos);
+        if (y == null) {
+            // invalid ordinate
+            // set index back to initial, error index should already be set
+            pos.setIndex(initialIndex);
+            return null;
+        }
+
+        // parse Z component
+        parseAndIgnoreWhitespace(source, pos);
+        if (!parseFixedstring(source, trimmedSeparator, pos)) {
+            return null;
+        }
+        parseAndIgnoreWhitespace(source, pos);
+        Number z = parseNumber(source, format, pos);
+        if (z == null) {
+            // invalid height
+            // set index back to initial, error index should already be set
+            pos.setIndex(initialIndex);
+            return null;
+        }
+
+        // parse suffix
+        parseAndIgnoreWhitespace(source, pos);
+        if (!parseFixedstring(source, trimmedSuffix, pos)) {
+            return null;
+        }
+
+        return new Vector3D(x.doubleValue(), y.doubleValue(), z.doubleValue());
+
+    }
+
+    /**
+     * Parses a string to produce a object.
+     * @param source the string to parse
+     * @param pos input/ouput parsing parameter.
+     * @return the parsed object.
+     * @see java.text.Format#parseObject(java.lang.String, java.text.ParsePosition)
+     */
+    @Override
+    public Object parseObject(String source, ParsePosition pos) {
+        return parse(source, pos);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/geometry/package.html b/src/main/java/org/apache/commons/math/geometry/package.html
new file mode 100644
index 0000000..d528d4a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/geometry/package.html
@@ -0,0 +1,24 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 613610 $ -->
+<body>
+<p>
+This package provides basic 3D geometry components.
+</p>
+</body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/linear/AbstractFieldMatrix.java b/src/main/java/org/apache/commons/math/linear/AbstractFieldMatrix.java
new file mode 100644
index 0000000..d35f25f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/AbstractFieldMatrix.java
@@ -0,0 +1,1139 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.linear;
+
+import java.lang.reflect.Array;
+import java.util.Arrays;
+
+import org.apache.commons.math.Field;
+import org.apache.commons.math.FieldElement;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.linear.MatrixVisitorException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * 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. </p>
+ *
+ * @param <T> the type of the field elements
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @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  the number of rows in the new matrix
+     * @param columnDimension  the number of columns in the new matrix
+     * @throws IllegalArgumentException if row or column dimension is not positive
+     */
+    protected AbstractFieldMatrix(final Field<T> field,
+                                  final int rowDimension, final int columnDimension)
+        throws IllegalArgumentException {
+        if (rowDimension < 1 ) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.INSUFFICIENT_DIMENSION, rowDimension, 1);
+        }
+        if (columnDimension < 1) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.INSUFFICIENT_DIMENSION, columnDimension, 1);
+        }
+        this.field = field;
+    }
+
+    /**
+     * Get the elements type from an array.
+     * @param <T> the type of the field elements
+     * @param d data array
+     * @return field to which array elements belong
+     * @exception IllegalArgumentException if array is empty
+     */
+    protected static <T extends FieldElement<T>> Field<T> extractField(final T[][] d)
+        throws IllegalArgumentException {
+        if (d.length == 0) {
+            throw MathRuntimeException.createIllegalArgumentException(LocalizedFormats.AT_LEAST_ONE_ROW);
+        }
+        if (d[0].length == 0) {
+            throw MathRuntimeException.createIllegalArgumentException(LocalizedFormats.AT_LEAST_ONE_COLUMN);
+        }
+        return d[0][0].getField();
+    }
+
+    /**
+     * Get the elements type from an array.
+     * @param <T> the type of the field elements
+     * @param d data array
+     * @return field to which array elements belong
+     * @exception IllegalArgumentException if array is empty
+     */
+    protected static <T extends FieldElement<T>> Field<T> extractField(final T[] d)
+        throws IllegalArgumentException {
+        if (d.length == 0) {
+            throw MathRuntimeException.createIllegalArgumentException(LocalizedFormats.AT_LEAST_ONE_ROW);
+        }
+        return d[0].getField();
+    }
+
+    /** Build an array of elements.
+     * <p>
+     * Complete arrays are filled with field.getZero()
+     * </p>
+     * @param <T> the 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
+     */
+    @SuppressWarnings("unchecked")
+    protected static <T extends FieldElement<T>> T[][] buildArray(final Field<T> field,
+                                                                  final int rows,
+                                                                  final int columns) {
+        if (columns < 0) {
+            T[] dummyRow = (T[]) Array.newInstance(field.getZero().getClass(), 0);
+            return (T[][]) Array.newInstance(dummyRow.getClass(), rows);
+        }
+        T[][] array =
+            (T[][]) Array.newInstance(field.getZero().getClass(), new int[] { rows, columns });
+        for (int i = 0; i < array.length; ++i) {
+            Arrays.fill(array[i], field.getZero());
+        }
+        return array;
+    }
+
+    /** Build an array of elements.
+     * <p>
+     * Arrays are filled with field.getZero()
+     * </p>
+     * @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
+     */
+    protected static <T extends FieldElement<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.getZero().getClass(), length);
+        Arrays.fill(array, field.getZero());
+        return array;
+    }
+
+    /** {@inheritDoc} */
+    public Field<T> getField() {
+        return field;
+    }
+
+    /** {@inheritDoc} */
+    public abstract FieldMatrix<T> createMatrix(final int rowDimension, final int columnDimension)
+        throws IllegalArgumentException;
+
+    /** {@inheritDoc} */
+    public abstract FieldMatrix<T> copy();
+
+    /** {@inheritDoc} */
+    public FieldMatrix<T> add(FieldMatrix<T> m) throws IllegalArgumentException {
+
+        // 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 IllegalArgumentException {
+
+        // 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 IllegalArgumentException {
+
+        // 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 IllegalArgumentException {
+        return m.multiply(this);
+    }
+
+    /** {@inheritDoc} */
+    public T[][] getData() {
+
+        final T[][] data = 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 MatrixIndexException {
+
+        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 MatrixIndexException {
+
+        // 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 MatrixIndexException, IllegalArgumentException {
+
+        // 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 MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.DIMENSIONS_MISMATCH_2x2,
+                    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 MatrixIndexException, IllegalArgumentException {
+
+        // safety checks
+        checkSubMatrixIndex(selectedRows, selectedColumns);
+        if ((destination.length < selectedRows.length) ||
+            (destination[0].length < selectedColumns.length)) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.DIMENSIONS_MISMATCH_2x2,
+                    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 MatrixIndexException {
+
+        final int nRows = subMatrix.length;
+        if (nRows == 0) {
+            throw MathRuntimeException.createIllegalArgumentException(LocalizedFormats.AT_LEAST_ONE_ROW);
+        }
+
+        final int nCols = subMatrix[0].length;
+        if (nCols == 0) {
+            throw MathRuntimeException.createIllegalArgumentException(LocalizedFormats.AT_LEAST_ONE_COLUMN);
+        }
+
+        for (int r = 1; r < nRows; ++r) {
+            if (subMatrix[r].length != nCols) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                        LocalizedFormats.DIFFERENT_ROWS_LENGTHS,
+                        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 MatrixIndexException {
+
+        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 MatrixIndexException, InvalidMatrixException {
+
+        checkRowIndex(row);
+        final int nCols = getColumnDimension();
+        if ((matrix.getRowDimension() != 1) ||
+            (matrix.getColumnDimension() != nCols)) {
+            throw new InvalidMatrixException(
+                    LocalizedFormats.DIMENSIONS_MISMATCH_2x2,
+                    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 MatrixIndexException {
+
+        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 MatrixIndexException, InvalidMatrixException {
+
+        checkColumnIndex(column);
+        final int nRows = getRowDimension();
+        if ((matrix.getRowDimension() != nRows) ||
+            (matrix.getColumnDimension() != 1)) {
+            throw new InvalidMatrixException(
+                    LocalizedFormats.DIMENSIONS_MISMATCH_2x2,
+                    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 MatrixIndexException {
+        return new ArrayFieldVector<T>(getRow(row), false);
+    }
+
+    /** {@inheritDoc} */
+    public void setRowVector(final int row, final FieldVector<T> vector)
+        throws MatrixIndexException, InvalidMatrixException {
+
+        checkRowIndex(row);
+        final int nCols = getColumnDimension();
+        if (vector.getDimension() != nCols) {
+            throw new InvalidMatrixException(
+                    LocalizedFormats.DIMENSIONS_MISMATCH_2x2,
+                    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 MatrixIndexException {
+        return new ArrayFieldVector<T>(getColumn(column), false);
+    }
+
+    /** {@inheritDoc} */
+    public void setColumnVector(final int column, final FieldVector<T> vector)
+        throws MatrixIndexException, InvalidMatrixException {
+
+        checkColumnIndex(column);
+        final int nRows = getRowDimension();
+        if (vector.getDimension() != nRows) {
+            throw new InvalidMatrixException(
+                    LocalizedFormats.DIMENSIONS_MISMATCH_2x2,
+                    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 MatrixIndexException {
+
+        checkRowIndex(row);
+        final int nCols = getColumnDimension();
+        final T[] out = 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 MatrixIndexException, InvalidMatrixException {
+
+        checkRowIndex(row);
+        final int nCols = getColumnDimension();
+        if (array.length != nCols) {
+            throw new InvalidMatrixException(
+                    LocalizedFormats.DIMENSIONS_MISMATCH_2x2,
+                    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 MatrixIndexException {
+
+        checkColumnIndex(column);
+        final int nRows = getRowDimension();
+        final T[] out = 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 MatrixIndexException, InvalidMatrixException {
+
+        checkColumnIndex(column);
+        final int nRows = getRowDimension();
+        if (array.length != nRows) {
+            throw new InvalidMatrixException(
+                    LocalizedFormats.DIMENSIONS_MISMATCH_2x2,
+                    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 MatrixIndexException;
+
+    /** {@inheritDoc} */
+    public abstract void setEntry(int row, int column, T value)
+        throws MatrixIndexException;
+
+    /** {@inheritDoc} */
+    public abstract void addToEntry(int row, int column, T increment)
+        throws MatrixIndexException;
+
+    /** {@inheritDoc} */
+    public abstract void multiplyEntry(int row, int column, T factor)
+        throws MatrixIndexException;
+
+    /** {@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 IllegalArgumentException {
+
+        final int nRows = getRowDimension();
+        final int nCols = getColumnDimension();
+        if (v.length != nCols) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.VECTOR_LENGTH_MISMATCH,
+                    v.length, nCols);
+        }
+
+        final T[] out = 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 IllegalArgumentException {
+        try {
+            return new ArrayFieldVector<T>(operate(((ArrayFieldVector<T>) v).getDataRef()), false);
+        } catch (ClassCastException cce) {
+            final int nRows = getRowDimension();
+            final int nCols = getColumnDimension();
+            if (v.getDimension() != nCols) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                        LocalizedFormats.VECTOR_LENGTH_MISMATCH,
+                        v.getDimension(), nCols);
+            }
+
+            final T[] out = 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>(out, false);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public T[] preMultiply(final T[] v)
+        throws IllegalArgumentException {
+
+        final int nRows = getRowDimension();
+        final int nCols = getColumnDimension();
+        if (v.length != nRows) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.VECTOR_LENGTH_MISMATCH,
+                    v.length, nRows);
+        }
+
+        final T[] out = 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 IllegalArgumentException {
+        try {
+            return new ArrayFieldVector<T>(preMultiply(((ArrayFieldVector<T>) v).getDataRef()), false);
+        } catch (ClassCastException cce) {
+
+            final int nRows = getRowDimension();
+            final int nCols = getColumnDimension();
+            if (v.getDimension() != nRows) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                        LocalizedFormats.VECTOR_LENGTH_MISMATCH,
+                        v.getDimension(), nRows);
+            }
+
+            final T[] out = 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>(out);
+
+        }
+    }
+
+    /** {@inheritDoc} */
+    public T walkInRowOrder(final FieldMatrixChangingVisitor<T> visitor)
+        throws MatrixVisitorException {
+        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)
+        throws MatrixVisitorException {
+        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 MatrixIndexException, MatrixVisitorException {
+        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 MatrixIndexException, MatrixVisitorException {
+        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)
+        throws MatrixVisitorException {
+        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)
+        throws MatrixVisitorException {
+        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 MatrixIndexException, MatrixVisitorException {
+        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 MatrixIndexException, MatrixVisitorException {
+        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)
+        throws MatrixVisitorException {
+        return walkInRowOrder(visitor);
+    }
+
+    /** {@inheritDoc} */
+    public T walkInOptimizedOrder(final FieldMatrixPreservingVisitor<T> visitor)
+        throws MatrixVisitorException {
+        return walkInRowOrder(visitor);
+    }
+
+    /** {@inheritDoc} */
+    public T walkInOptimizedOrder(final FieldMatrixChangingVisitor<T> visitor,
+                                       final int startRow, final int endRow,
+                                       final int startColumn, final int endColumn)
+        throws MatrixIndexException, MatrixVisitorException {
+        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 MatrixIndexException, MatrixVisitorException {
+        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 StringBuilder res = new StringBuilder();
+        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
+     * @exception MatrixIndexException if index is not valid
+     */
+    protected void checkRowIndex(final int row) {
+        if (row < 0 || row >= getRowDimension()) {
+            throw new MatrixIndexException(LocalizedFormats.ROW_INDEX_OUT_OF_RANGE,
+                                           row, 0, getRowDimension() - 1);
+        }
+    }
+
+    /**
+     * Check if a column index is valid.
+     * @param column column index to check
+     * @exception MatrixIndexException if index is not valid
+     */
+    protected void checkColumnIndex(final int column)
+        throws MatrixIndexException {
+        if (column < 0 || column >= getColumnDimension()) {
+            throw new MatrixIndexException(LocalizedFormats.COLUMN_INDEX_OUT_OF_RANGE,
+                                           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
+     * @exception MatrixIndexException  if the indices are not valid
+     */
+    protected void checkSubMatrixIndex(final int startRow, final int endRow,
+                                       final int startColumn, final int endColumn) {
+        checkRowIndex(startRow);
+        checkRowIndex(endRow);
+        if (startRow > endRow) {
+            throw new MatrixIndexException(LocalizedFormats.INITIAL_ROW_AFTER_FINAL_ROW,
+                                           startRow, endRow);
+        }
+
+        checkColumnIndex(startColumn);
+        checkColumnIndex(endColumn);
+        if (startColumn > endColumn) {
+            throw new MatrixIndexException(LocalizedFormats.INITIAL_COLUMN_AFTER_FINAL_COLUMN,
+                                           startColumn, endColumn);
+        }
+
+
+    }
+
+    /**
+     * 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.
+     * @exception MatrixIndexException if row or column selections are not valid
+     */
+    protected void checkSubMatrixIndex(final int[] selectedRows, final int[] selectedColumns) {
+        if (selectedRows.length * selectedColumns.length == 0) {
+            if (selectedRows.length == 0) {
+                throw new MatrixIndexException(LocalizedFormats.EMPTY_SELECTED_ROW_INDEX_ARRAY);
+            }
+            throw new MatrixIndexException(LocalizedFormats.EMPTY_SELECTED_COLUMN_INDEX_ARRAY);
+        }
+
+        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
+     * @exception IllegalArgumentException if matrix is not addition compatible with instance
+     */
+    protected void checkAdditionCompatible(final FieldMatrix<T> m) {
+        if ((getRowDimension()    != m.getRowDimension()) ||
+            (getColumnDimension() != m.getColumnDimension())) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.NOT_ADDITION_COMPATIBLE_MATRICES,
+                    getRowDimension(), getColumnDimension(),
+                    m.getRowDimension(), m.getColumnDimension());
+        }
+    }
+
+    /**
+     * Check if a matrix is subtraction compatible with the instance
+     * @param m matrix to check
+     * @exception IllegalArgumentException if matrix is not subtraction compatible with instance
+     */
+    protected void checkSubtractionCompatible(final FieldMatrix<T> m) {
+        if ((getRowDimension()    != m.getRowDimension()) ||
+            (getColumnDimension() != m.getColumnDimension())) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.NOT_SUBTRACTION_COMPATIBLE_MATRICES,
+                    getRowDimension(), getColumnDimension(),
+                    m.getRowDimension(), m.getColumnDimension());
+        }
+    }
+
+    /**
+     * Check if a matrix is multiplication compatible with the instance
+     * @param m matrix to check
+     * @exception IllegalArgumentException if matrix is not multiplication compatible with instance
+     */
+    protected void checkMultiplicationCompatible(final FieldMatrix<T> m) {
+        if (getColumnDimension() != m.getRowDimension()) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.NOT_MULTIPLICATION_COMPATIBLE_MATRICES,
+                    getRowDimension(), getColumnDimension(),
+                    m.getRowDimension(), m.getColumnDimension());
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/AbstractRealMatrix.java b/src/main/java/org/apache/commons/math/linear/AbstractRealMatrix.java
new file mode 100644
index 0000000..4bf7a82
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/AbstractRealMatrix.java
@@ -0,0 +1,1071 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.linear;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.linear.MatrixVisitorException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.util.MathUtils;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * 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. </p>
+ *
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @since 2.0
+ */
+public abstract class AbstractRealMatrix implements RealMatrix {
+
+
+    /** Cached LU solver.
+     * @deprecated as of release 2.0, since all methods using this are deprecated
+     */
+    @Deprecated
+    private DecompositionSolver lu;
+
+    /**
+     * Creates a matrix with no data
+     */
+    protected AbstractRealMatrix() {
+        lu = null;
+    }
+
+    /**
+     * 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 IllegalArgumentException if row or column dimension is not positive
+     */
+    protected AbstractRealMatrix(final int rowDimension, final int columnDimension)
+        throws IllegalArgumentException {
+        if (rowDimension < 1 ) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.INSUFFICIENT_DIMENSION, rowDimension, 1);
+        }
+        if (columnDimension <= 0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.INSUFFICIENT_DIMENSION, columnDimension, 1);
+        }
+        lu = null;
+    }
+
+    /** {@inheritDoc} */
+    public abstract RealMatrix createMatrix(final int rowDimension, final int columnDimension)
+        throws IllegalArgumentException;
+
+    /** {@inheritDoc} */
+    public abstract RealMatrix copy();
+
+    /** {@inheritDoc} */
+    public RealMatrix add(RealMatrix m) throws IllegalArgumentException {
+
+        // safety check
+        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 IllegalArgumentException {
+
+        // safety check
+        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 IllegalArgumentException {
+
+        // safety check
+        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 IllegalArgumentException {
+        return m.multiply(this);
+    }
+
+    /** {@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 MatrixIndexException {
+
+        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 MatrixIndexException {
+
+        // safety checks
+        MatrixUtils.checkSubMatrixIndex(this, selectedRows, selectedColumns);
+
+        // copy entries
+        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 MatrixIndexException, IllegalArgumentException {
+
+        // safety checks
+        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 MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.DIMENSIONS_MISMATCH_2x2,
+                    destination.length, destination[0].length,
+                    rowsCount, columnsCount);
+        }
+
+        // copy entries
+        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 MatrixIndexException, IllegalArgumentException {
+
+        // safety checks
+        MatrixUtils.checkSubMatrixIndex(this, selectedRows, selectedColumns);
+        if ((destination.length < selectedRows.length) ||
+            (destination[0].length < selectedColumns.length)) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.DIMENSIONS_MISMATCH_2x2,
+                    destination.length, destination[0].length,
+                    selectedRows.length, selectedColumns.length);
+        }
+
+        // copy entries
+        for (int i = 0; i < selectedRows.length; i++) {
+            final double[] destinationI = destination[i];
+            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 MatrixIndexException {
+
+        final int nRows = subMatrix.length;
+        if (nRows == 0) {
+            throw MathRuntimeException.createIllegalArgumentException(LocalizedFormats.AT_LEAST_ONE_ROW);
+        }
+
+        final int nCols = subMatrix[0].length;
+        if (nCols == 0) {
+            throw MathRuntimeException.createIllegalArgumentException(LocalizedFormats.AT_LEAST_ONE_COLUMN);
+        }
+
+        for (int r = 1; r < nRows; ++r) {
+            if (subMatrix[r].length != nCols) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                        LocalizedFormats.DIFFERENT_ROWS_LENGTHS,
+                        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]);
+            }
+        }
+
+        lu = null;
+
+    }
+
+    /** {@inheritDoc} */
+    public RealMatrix getRowMatrix(final int row)
+        throws MatrixIndexException {
+
+        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 MatrixIndexException, InvalidMatrixException {
+
+        MatrixUtils.checkRowIndex(this, row);
+        final int nCols = getColumnDimension();
+        if ((matrix.getRowDimension() != 1) ||
+            (matrix.getColumnDimension() != nCols)) {
+            throw new InvalidMatrixException(
+                    LocalizedFormats.DIMENSIONS_MISMATCH_2x2,
+                    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 MatrixIndexException {
+
+        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 MatrixIndexException, InvalidMatrixException {
+
+        MatrixUtils.checkColumnIndex(this, column);
+        final int nRows = getRowDimension();
+        if ((matrix.getRowDimension() != nRows) ||
+            (matrix.getColumnDimension() != 1)) {
+            throw new InvalidMatrixException(
+                    LocalizedFormats.DIMENSIONS_MISMATCH_2x2,
+                    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 MatrixIndexException {
+        return new ArrayRealVector(getRow(row), false);
+    }
+
+    /** {@inheritDoc} */
+    public void setRowVector(final int row, final RealVector vector)
+        throws MatrixIndexException, InvalidMatrixException {
+
+        MatrixUtils.checkRowIndex(this, row);
+        final int nCols = getColumnDimension();
+        if (vector.getDimension() != nCols) {
+            throw new InvalidMatrixException(
+                    LocalizedFormats.DIMENSIONS_MISMATCH_2x2,
+                    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 MatrixIndexException {
+        return new ArrayRealVector(getColumn(column), false);
+    }
+
+    /** {@inheritDoc} */
+    public void setColumnVector(final int column, final RealVector vector)
+        throws MatrixIndexException, InvalidMatrixException {
+
+        MatrixUtils.checkColumnIndex(this, column);
+        final int nRows = getRowDimension();
+        if (vector.getDimension() != nRows) {
+            throw new InvalidMatrixException(
+                    LocalizedFormats.DIMENSIONS_MISMATCH_2x2,
+                    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 MatrixIndexException {
+
+        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 MatrixIndexException, InvalidMatrixException {
+
+        MatrixUtils.checkRowIndex(this, row);
+        final int nCols = getColumnDimension();
+        if (array.length != nCols) {
+            throw new InvalidMatrixException(
+                    LocalizedFormats.DIMENSIONS_MISMATCH_2x2,
+                    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 MatrixIndexException {
+
+        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 MatrixIndexException, InvalidMatrixException {
+
+        MatrixUtils.checkColumnIndex(this, column);
+        final int nRows = getRowDimension();
+        if (array.length != nRows) {
+            throw new InvalidMatrixException(
+                    LocalizedFormats.DIMENSIONS_MISMATCH_2x2,
+                    array.length, 1, nRows, 1);
+        }
+        for (int i = 0; i < nRows; ++i) {
+            setEntry(i, column, array[i]);
+        }
+
+    }
+
+    /** {@inheritDoc} */
+    public abstract double getEntry(int row, int column)
+        throws MatrixIndexException;
+
+    /** {@inheritDoc} */
+    public abstract void setEntry(int row, int column, double value)
+        throws MatrixIndexException;
+
+    /** {@inheritDoc} */
+    public abstract void addToEntry(int row, int column, double increment)
+        throws MatrixIndexException;
+
+    /** {@inheritDoc} */
+    public abstract void multiplyEntry(int row, int column, double factor)
+        throws MatrixIndexException;
+
+    /** {@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} */
+    @Deprecated
+    public RealMatrix inverse()
+        throws InvalidMatrixException {
+        if (lu == null) {
+            lu = new LUDecompositionImpl(this, MathUtils.SAFE_MIN).getSolver();
+        }
+        return lu.getInverse();
+    }
+
+    /** {@inheritDoc} */
+    @Deprecated
+    public double getDeterminant()
+        throws InvalidMatrixException {
+        return new LUDecompositionImpl(this, MathUtils.SAFE_MIN).getDeterminant();
+    }
+
+    /** {@inheritDoc} */
+    public boolean isSquare() {
+        return getColumnDimension() == getRowDimension();
+    }
+
+    /** {@inheritDoc} */
+    @Deprecated
+    public boolean isSingular() {
+        if (lu == null) {
+            lu = new LUDecompositionImpl(this, MathUtils.SAFE_MIN).getSolver();
+       }
+        return !lu.isNonSingular();
+    }
+
+    /** {@inheritDoc} */
+    public abstract int getRowDimension();
+
+    /** {@inheritDoc} */
+    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 IllegalArgumentException {
+
+        final int nRows = getRowDimension();
+        final int nCols = getColumnDimension();
+        if (v.length != nCols) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.VECTOR_LENGTH_MISMATCH,
+                    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} */
+    public RealVector operate(final RealVector v)
+        throws IllegalArgumentException {
+        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 MathRuntimeException.createIllegalArgumentException(
+                        LocalizedFormats.VECTOR_LENGTH_MISMATCH,
+                        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 IllegalArgumentException {
+
+        final int nRows = getRowDimension();
+        final int nCols = getColumnDimension();
+        if (v.length != nRows) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.VECTOR_LENGTH_MISMATCH,
+                    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 IllegalArgumentException {
+        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 MathRuntimeException.createIllegalArgumentException(
+                        LocalizedFormats.VECTOR_LENGTH_MISMATCH,
+                        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);
+
+        }
+    }
+
+    /** {@inheritDoc} */
+    public double walkInRowOrder(final RealMatrixChangingVisitor visitor)
+        throws MatrixVisitorException {
+        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);
+            }
+        }
+        lu = null;
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    public double walkInRowOrder(final RealMatrixPreservingVisitor visitor)
+        throws MatrixVisitorException {
+        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 MatrixIndexException, MatrixVisitorException {
+        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);
+            }
+        }
+        lu = null;
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    public double walkInRowOrder(final RealMatrixPreservingVisitor visitor,
+                                 final int startRow, final int endRow,
+                                 final int startColumn, final int endColumn)
+        throws MatrixIndexException, MatrixVisitorException {
+        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)
+        throws MatrixVisitorException {
+        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);
+            }
+        }
+        lu = null;
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    public double walkInColumnOrder(final RealMatrixPreservingVisitor visitor)
+        throws MatrixVisitorException {
+        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 MatrixIndexException, MatrixVisitorException {
+        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);
+            }
+        }
+        lu = null;
+        return visitor.end();
+    }
+
+    /** {@inheritDoc} */
+    public double walkInColumnOrder(final RealMatrixPreservingVisitor visitor,
+                                    final int startRow, final int endRow,
+                                    final int startColumn, final int endColumn)
+    throws MatrixIndexException, MatrixVisitorException {
+        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)
+        throws MatrixVisitorException {
+        return walkInRowOrder(visitor);
+    }
+
+    /** {@inheritDoc} */
+    public double walkInOptimizedOrder(final RealMatrixPreservingVisitor visitor)
+        throws MatrixVisitorException {
+        return walkInRowOrder(visitor);
+    }
+
+    /** {@inheritDoc} */
+    public double walkInOptimizedOrder(final RealMatrixChangingVisitor visitor,
+                                       final int startRow, final int endRow,
+                                       final int startColumn, final int endColumn)
+        throws MatrixIndexException, MatrixVisitorException {
+        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 MatrixIndexException, MatrixVisitorException {
+        return walkInRowOrder(visitor, startRow, endRow, startColumn, endColumn);
+    }
+
+    /** {@inheritDoc} */
+    @Deprecated
+    public double[] solve(final double[] b)
+        throws IllegalArgumentException, InvalidMatrixException {
+        if (lu == null) {
+            lu = new LUDecompositionImpl(this, MathUtils.SAFE_MIN).getSolver();
+        }
+        return lu.solve(b);
+    }
+
+    /** {@inheritDoc} */
+    @Deprecated
+    public RealMatrix solve(final RealMatrix b)
+        throws IllegalArgumentException, InvalidMatrixException  {
+        if (lu == null) {
+            lu = new LUDecompositionImpl(this, MathUtils.SAFE_MIN).getSolver();
+        }
+        return lu.solve(b);
+    }
+
+    /**
+     * Computes a new
+     * <a href="http://www.math.gatech.edu/~bourbaki/math2601/Web-notes/2num.pdf">
+     * LU decomposition</a> for this matrix, storing the result for use by other methods.
+     * <p>
+     * <strong>Implementation Note</strong>:<br>
+     * Uses <a href="http://www.damtp.cam.ac.uk/user/fdl/people/sd/lectures/nummeth98/linear.htm">
+     * Crout's algorithm</a>, with partial pivoting.</p>
+     * <p>
+     * <strong>Usage Note</strong>:<br>
+     * This method should rarely be invoked directly. Its only use is
+     * to force recomputation of the LU decomposition when changes have been
+     * made to the underlying data using direct array references. Changes
+     * made using setXxx methods will trigger recomputation when needed
+     * automatically.</p>
+     *
+     * @throws InvalidMatrixException if the matrix is non-square or singular.
+     * @deprecated as of release 2.0, replaced by {@link LUDecomposition}
+     */
+    @Deprecated
+    public void luDecompose()
+        throws InvalidMatrixException {
+        if (lu == null) {
+            lu = new LUDecompositionImpl(this, MathUtils.SAFE_MIN).getSolver();
+        }
+    }
+
+    /**
+     * 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 StringBuilder res = new StringBuilder();
+        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>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;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/AbstractRealVector.java b/src/main/java/org/apache/commons/math/linear/AbstractRealVector.java
new file mode 100644
index 0000000..e39c9ce
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/AbstractRealVector.java
@@ -0,0 +1,932 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.linear;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.exception.MathUnsupportedOperationException;
+import org.apache.commons.math.exception.DimensionMismatchException;
+import org.apache.commons.math.analysis.BinaryFunction;
+import org.apache.commons.math.analysis.ComposableFunction;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * This class provides default basic implementations for many methods in the
+ * {@link RealVector} interface.
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ * @since 2.1
+ */
+public abstract class AbstractRealVector implements RealVector {
+
+    /**
+     * 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 dimension
+     */
+    protected void checkVectorDimensions(RealVector v) {
+        checkVectorDimensions(v.getDimension());
+    }
+
+    /**
+     * Check if instance dimension is equal to some expected value.
+     *
+     * @param n expected dimension.
+     * @exception DimensionMismatchException if the dimension is
+     * inconsistent with 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 MatrixIndexException if index is not valid
+     */
+    protected void checkIndex(final int index)
+        throws MatrixIndexException {
+        if (index < 0 || index >= getDimension()) {
+            throw new MatrixIndexException(LocalizedFormats.INDEX_OUT_OF_RANGE,
+                                           index, 0, getDimension() - 1);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void setSubVector(int index, RealVector v) throws MatrixIndexException {
+        checkIndex(index);
+        checkIndex(index + v.getDimension() - 1);
+        setSubVector(index, v.getData());
+    }
+
+    /** {@inheritDoc} */
+    public void setSubVector(int index, double[] v) throws MatrixIndexException {
+        checkIndex(index);
+        checkIndex(index + v.length - 1);
+        for (int i = 0; i < v.length; i++) {
+            setEntry(i + index, v[i]);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public RealVector add(double[] v) throws IllegalArgumentException {
+        double[] result = v.clone();
+        Iterator<Entry> it = sparseIterator();
+        Entry e;
+        while (it.hasNext() && (e = it.next()) != null) {
+            result[e.getIndex()] += e.getValue();
+        }
+        return new ArrayRealVector(result, false);
+    }
+
+    /** {@inheritDoc} */
+    public RealVector add(RealVector v) throws IllegalArgumentException {
+        if (v instanceof ArrayRealVector) {
+            double[] values = ((ArrayRealVector)v).getDataRef();
+            return add(values);
+        }
+        RealVector result = v.copy();
+        Iterator<Entry> it = sparseIterator();
+        Entry e;
+        while (it.hasNext() && (e = it.next()) != null) {
+            final int index = e.getIndex();
+            result.setEntry(index, e.getValue() + result.getEntry(index));
+        }
+        return result;
+    }
+
+    /** {@inheritDoc} */
+    public RealVector subtract(double[] v) throws IllegalArgumentException {
+        double[] result = v.clone();
+        Iterator<Entry> it = sparseIterator();
+        Entry e;
+        while (it.hasNext() && (e = it.next()) != null) {
+            final int index = e.getIndex();
+            result[index] = e.getValue() - result[index];
+        }
+        return new ArrayRealVector(result, false);
+    }
+
+    /** {@inheritDoc} */
+    public RealVector subtract(RealVector v) throws IllegalArgumentException {
+        if (v instanceof ArrayRealVector) {
+            double[] values = ((ArrayRealVector)v).getDataRef();
+            return add(values);
+        }
+        RealVector result = v.copy();
+        Iterator<Entry> it = sparseIterator();
+        Entry e;
+        while (it.hasNext() && (e = it.next()) != null) {
+            final int index = e.getIndex();
+            v.setEntry(index, e.getValue() - result.getEntry(index));
+        }
+        return result;
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapAdd(double d) {
+        return copy().mapAddToSelf(d);
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapAddToSelf(double d) {
+        if (d != 0) {
+            try {
+                return mapToSelf(BinaryFunction.ADD.fix1stArgument(d));
+            } catch (FunctionEvaluationException e) {
+                throw new IllegalArgumentException(e);
+            }
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    public abstract AbstractRealVector copy();
+
+    /** {@inheritDoc} */
+    public double dotProduct(double[] v) throws IllegalArgumentException {
+        return dotProduct(new ArrayRealVector(v, false));
+    }
+
+    /** {@inheritDoc} */
+    public double dotProduct(RealVector v) throws IllegalArgumentException {
+        checkVectorDimensions(v);
+        double d = 0;
+        Iterator<Entry> it = sparseIterator();
+        Entry e;
+        while (it.hasNext() && (e = it.next()) != null) {
+            d += e.getValue() * v.getEntry(e.getIndex());
+        }
+        return d;
+    }
+
+    /** {@inheritDoc} */
+    public RealVector ebeDivide(double[] v) throws IllegalArgumentException {
+        return ebeDivide(new ArrayRealVector(v, false));
+    }
+
+    /** {@inheritDoc} */
+    public RealVector ebeMultiply(double[] v) throws IllegalArgumentException {
+        return ebeMultiply(new ArrayRealVector(v, false));
+    }
+
+    /** {@inheritDoc} */
+    public double getDistance(RealVector v) throws IllegalArgumentException {
+        checkVectorDimensions(v);
+        double d = 0;
+        Iterator<Entry> it = iterator();
+        Entry e;
+        while (it.hasNext() && (e = it.next()) != null) {
+            final double diff = e.getValue() - v.getEntry(e.getIndex());
+            d += diff * diff;
+        }
+        return FastMath.sqrt(d);
+    }
+
+    /** {@inheritDoc} */
+    public double getNorm() {
+        double sum = 0;
+        Iterator<Entry> it = sparseIterator();
+        Entry e;
+        while (it.hasNext() && (e = it.next()) != null) {
+            final double value = e.getValue();
+            sum += value * value;
+        }
+        return FastMath.sqrt(sum);
+    }
+
+    /** {@inheritDoc} */
+    public double getL1Norm() {
+        double norm = 0;
+        Iterator<Entry> it = sparseIterator();
+        Entry e;
+        while (it.hasNext() && (e = it.next()) != null) {
+            norm += FastMath.abs(e.getValue());
+        }
+        return norm;
+    }
+
+    /** {@inheritDoc} */
+    public double getLInfNorm() {
+        double norm = 0;
+        Iterator<Entry> it = sparseIterator();
+        Entry e;
+        while (it.hasNext() && (e = it.next()) != null) {
+            norm = FastMath.max(norm, FastMath.abs(e.getValue()));
+        }
+        return norm;
+    }
+
+    /** {@inheritDoc} */
+    public double getDistance(double[] v) throws IllegalArgumentException {
+        return getDistance(new ArrayRealVector(v,false));
+    }
+
+    /** {@inheritDoc} */
+    public double getL1Distance(RealVector v) throws IllegalArgumentException {
+        checkVectorDimensions(v);
+        double d = 0;
+        Iterator<Entry> it = iterator();
+        Entry e;
+        while (it.hasNext() && (e = it.next()) != null) {
+            d += FastMath.abs(e.getValue() - v.getEntry(e.getIndex()));
+        }
+        return d;
+    }
+
+    /** {@inheritDoc} */
+    public double getL1Distance(double[] v) throws IllegalArgumentException {
+        checkVectorDimensions(v.length);
+        double d = 0;
+        Iterator<Entry> it = iterator();
+        Entry e;
+        while (it.hasNext() && (e = it.next()) != null) {
+            d += FastMath.abs(e.getValue() - v[e.getIndex()]);
+        }
+        return d;
+    }
+
+    /** {@inheritDoc} */
+    public double getLInfDistance(RealVector v) throws IllegalArgumentException {
+        checkVectorDimensions(v);
+        double d = 0;
+        Iterator<Entry> it = iterator();
+        Entry e;
+        while (it.hasNext() && (e = it.next()) != null) {
+            d = FastMath.max(FastMath.abs(e.getValue() - v.getEntry(e.getIndex())), d);
+        }
+        return d;
+    }
+
+    /** {@inheritDoc} */
+    public double getLInfDistance(double[] v) throws IllegalArgumentException {
+        checkVectorDimensions(v.length);
+        double d = 0;
+        Iterator<Entry> it = iterator();
+        Entry e;
+        while (it.hasNext() && (e = it.next()) != null) {
+            d = FastMath.max(FastMath.abs(e.getValue() - v[e.getIndex()]), d);
+        }
+        return d;
+    }
+
+    /** Get the index of the minimum entry.
+     * @return index of the minimum entry or -1 if vector length is 0
+     * or all entries are 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 value of the minimum entry or NaN if all entries are NaN
+     */
+    public double getMinValue() {
+        final int minIndex = getMinIndex();
+        return minIndex < 0 ? Double.NaN : getEntry(minIndex);
+    }
+
+    /** Get the index of the maximum entry.
+     * @return index of the maximum entry or -1 if vector length is 0
+     * or all entries are 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 value of the maximum entry or NaN if all entries are NaN
+     */
+    public double getMaxValue() {
+        final int maxIndex = getMaxIndex();
+        return maxIndex < 0 ? Double.NaN : getEntry(maxIndex);
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapAbs() {
+        return copy().mapAbsToSelf();
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapAbsToSelf() {
+        try {
+            return mapToSelf(ComposableFunction.ABS);
+        } catch (FunctionEvaluationException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapAcos() {
+        return copy().mapAcosToSelf();
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapAcosToSelf() {
+        try {
+            return mapToSelf(ComposableFunction.ACOS);
+        } catch (FunctionEvaluationException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapAsin() {
+        return copy().mapAsinToSelf();
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapAsinToSelf() {
+        try {
+            return mapToSelf(ComposableFunction.ASIN);
+        } catch (FunctionEvaluationException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapAtan() {
+        return copy().mapAtanToSelf();
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapAtanToSelf() {
+        try {
+            return mapToSelf(ComposableFunction.ATAN);
+        } catch (FunctionEvaluationException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapCbrt() {
+        return copy().mapCbrtToSelf();
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapCbrtToSelf() {
+        try {
+            return mapToSelf(ComposableFunction.CBRT);
+        } catch (FunctionEvaluationException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapCeil() {
+        return copy().mapCeilToSelf();
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapCeilToSelf() {
+        try {
+            return mapToSelf(ComposableFunction.CEIL);
+        } catch (FunctionEvaluationException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapCos() {
+        return copy().mapCosToSelf();
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapCosToSelf() {
+        try {
+            return mapToSelf(ComposableFunction.COS);
+        } catch (FunctionEvaluationException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapCosh() {
+        return copy().mapCoshToSelf();
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapCoshToSelf() {
+        try {
+            return mapToSelf(ComposableFunction.COSH);
+        } catch (FunctionEvaluationException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapDivide(double d) {
+        return copy().mapDivideToSelf(d);
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapDivideToSelf(double d){
+        try {
+            return mapToSelf(BinaryFunction.DIVIDE.fix2ndArgument(d));
+        } catch (FunctionEvaluationException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapExp() {
+        return copy().mapExpToSelf();
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapExpToSelf() {
+        try {
+            return mapToSelf(ComposableFunction.EXP);
+        } catch (FunctionEvaluationException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapExpm1() {
+        return copy().mapExpm1ToSelf();
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapExpm1ToSelf() {
+        try {
+            return mapToSelf(ComposableFunction.EXPM1);
+        } catch (FunctionEvaluationException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapFloor() {
+        return copy().mapFloorToSelf();
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapFloorToSelf() {
+        try {
+            return mapToSelf(ComposableFunction.FLOOR);
+        } catch (FunctionEvaluationException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapInv() {
+        return copy().mapInvToSelf();
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapInvToSelf() {
+        try {
+            return mapToSelf(ComposableFunction.INVERT);
+        } catch (FunctionEvaluationException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapLog() {
+        return copy().mapLogToSelf();
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapLogToSelf() {
+        try {
+            return mapToSelf(ComposableFunction.LOG);
+        } catch (FunctionEvaluationException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapLog10() {
+        return copy().mapLog10ToSelf();
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapLog10ToSelf() {
+        try {
+            return mapToSelf(ComposableFunction.LOG10);
+        } catch (FunctionEvaluationException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapLog1p() {
+        return copy().mapLog1pToSelf();
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapLog1pToSelf() {
+        try {
+            return mapToSelf(ComposableFunction.LOG1P);
+        } catch (FunctionEvaluationException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapMultiply(double d) {
+        return copy().mapMultiplyToSelf(d);
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapMultiplyToSelf(double d){
+        try {
+            return mapToSelf(BinaryFunction.MULTIPLY.fix1stArgument(d));
+        } catch (FunctionEvaluationException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapPow(double d) {
+        return copy().mapPowToSelf(d);
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapPowToSelf(double d){
+        try {
+            return mapToSelf(BinaryFunction.POW.fix2ndArgument(d));
+        } catch (FunctionEvaluationException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapRint() {
+        return copy().mapRintToSelf();
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapRintToSelf() {
+        try {
+            return mapToSelf(ComposableFunction.RINT);
+        } catch (FunctionEvaluationException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapSignum() {
+        return copy().mapSignumToSelf();
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapSignumToSelf() {
+        try {
+            return mapToSelf(ComposableFunction.SIGNUM);
+        } catch (FunctionEvaluationException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapSin() {
+        return copy().mapSinToSelf();
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapSinToSelf() {
+        try {
+            return mapToSelf(ComposableFunction.SIN);
+        } catch (FunctionEvaluationException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapSinh() {
+        return copy().mapSinhToSelf();
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapSinhToSelf() {
+        try {
+            return mapToSelf(ComposableFunction.SINH);
+        } catch (FunctionEvaluationException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapSqrt() {
+        return copy().mapSqrtToSelf();
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapSqrtToSelf() {
+        try {
+            return mapToSelf(ComposableFunction.SQRT);
+        } catch (FunctionEvaluationException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapSubtract(double d) {
+        return copy().mapSubtractToSelf(d);
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapSubtractToSelf(double d){
+        return mapAddToSelf(-d);
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapTan() {
+        return copy().mapTanToSelf();
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapTanToSelf() {
+        try {
+            return mapToSelf(ComposableFunction.TAN);
+        } catch (FunctionEvaluationException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapTanh() {
+        return copy().mapTanhToSelf();
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapTanhToSelf() {
+        try {
+            return mapToSelf(ComposableFunction.TANH);
+        } catch (FunctionEvaluationException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapUlp() {
+        return copy().mapUlpToSelf();
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapUlpToSelf() {
+        try {
+            return mapToSelf(ComposableFunction.ULP);
+        } catch (FunctionEvaluationException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public RealMatrix outerProduct(RealVector v) throws IllegalArgumentException {
+        RealMatrix product;
+        if (v instanceof SparseRealVector || this instanceof SparseRealVector) {
+            product = new OpenMapRealMatrix(this.getDimension(), v.getDimension());
+        } else {
+            product = new Array2DRowRealMatrix(this.getDimension(), v.getDimension());
+        }
+        Iterator<Entry> thisIt = sparseIterator();
+        Entry thisE = null;
+        while (thisIt.hasNext() && (thisE = thisIt.next()) != null) {
+            Iterator<Entry> otherIt = v.sparseIterator();
+            Entry otherE = null;
+            while (otherIt.hasNext() && (otherE = otherIt.next()) != null) {
+                product.setEntry(thisE.getIndex(), otherE.getIndex(),
+                                 thisE.getValue() * otherE.getValue());
+            }
+        }
+
+        return product;
+
+    }
+
+    /** {@inheritDoc} */
+    public RealMatrix outerProduct(double[] v) throws IllegalArgumentException {
+        return outerProduct(new ArrayRealVector(v, false));
+    }
+
+    /** {@inheritDoc} */
+    public RealVector projection(double[] v) throws IllegalArgumentException {
+        return projection(new ArrayRealVector(v, false));
+    }
+
+    /** {@inheritDoc} */
+    public void set(double value) {
+        Iterator<Entry> it = iterator();
+        Entry e = null;
+        while (it.hasNext() && (e = it.next()) != null) {
+            e.setValue(value);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public double[] toArray() {
+        int dim = getDimension();
+        double[] values = new double[dim];
+        for (int i = 0; i < dim; i++) {
+            values[i] = getEntry(i);
+        }
+        return values;
+    }
+
+    /** {@inheritDoc} */
+    public double[] getData() {
+        return toArray();
+    }
+
+    /** {@inheritDoc} */
+    public RealVector unitVector() {
+        RealVector copy = copy();
+        copy.unitize();
+        return copy;
+    }
+
+    /** {@inheritDoc} */
+    public void unitize() {
+        mapDivideToSelf(getNorm());
+    }
+
+    /** {@inheritDoc} */
+    public Iterator<Entry> sparseIterator() {
+        return new SparseEntryIterator();
+    }
+
+    /** {@inheritDoc} */
+    public Iterator<Entry> iterator() {
+        final int dim = getDimension();
+        return new Iterator<Entry>() {
+
+            /** Current index. */
+            private int i = 0;
+
+            /** Current entry. */
+            private EntryImpl e = new EntryImpl();
+
+            /** {@inheritDoc} */
+            public boolean hasNext() {
+                return i < dim;
+            }
+
+            /** {@inheritDoc} */
+            public Entry next() {
+                e.setIndex(i++);
+                return e;
+            }
+
+            /** {@inheritDoc} */
+            public void remove() {
+                throw new MathUnsupportedOperationException();
+            }
+        };
+    }
+
+    /** {@inheritDoc} */
+    public RealVector map(UnivariateRealFunction function) throws FunctionEvaluationException {
+        return copy().mapToSelf(function);
+    }
+
+    /** {@inheritDoc} */
+    public RealVector mapToSelf(UnivariateRealFunction function) throws FunctionEvaluationException {
+        Iterator<Entry> it = (function.value(0) == 0) ? sparseIterator() : iterator();
+        Entry e;
+        while (it.hasNext() && (e = it.next()) != null) {
+            e.setValue(function.value(e.getValue()));
+        }
+        return this;
+    }
+
+    /** An entry in the vector. */
+    protected class EntryImpl extends Entry {
+
+        /** Simple constructor. */
+        public EntryImpl() {
+            setIndex(0);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public double getValue() {
+            return getEntry(getIndex());
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void setValue(double newValue) {
+            setEntry(getIndex(), newValue);
+        }
+    }
+
+    /**
+     * This class should rare be used, but is here to provide
+     * a default implementation of sparseIterator(), which is implemented
+     * by walking over the entries, skipping those whose values are the default one.
+     *
+     * Concrete subclasses which are SparseVector implementations should
+     * make their own sparse iterator, not use this one.
+     *
+     * 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 EntryImpl current;
+
+        /** Next entry for {@link #next()} to return. */
+        private EntryImpl next;
+
+        /** Simple constructor. */
+        protected SparseEntryIterator() {
+            dim = getDimension();
+            current = new EntryImpl();
+            next = new EntryImpl();
+            if (next.getValue() == 0) {
+                advance(next);
+            }
+        }
+
+        /** Advance an entry up to the next nonzero one.
+         * @param e entry to advance
+         */
+        protected void advance(EntryImpl 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} */
+        public void remove() {
+            throw new MathUnsupportedOperationException();
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/AnyMatrix.java b/src/main/java/org/apache/commons/math/linear/AnyMatrix.java
new file mode 100644
index 0000000..f435e2c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/AnyMatrix.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.math.linear;
+
+
+/**
+ * Interface defining very basic matrix operations.
+ * @version $Revision: 772119 $ $Date: 2009-05-06 11:43:28 +0200 (mer. 06 mai 2009) $
+ * @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/math/linear/Array2DRowFieldMatrix.java b/src/main/java/org/apache/commons/math/linear/Array2DRowFieldMatrix.java
new file mode 100644
index 0000000..fdbe24b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/Array2DRowFieldMatrix.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.math.linear;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.Field;
+import org.apache.commons.math.FieldElement;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.linear.MatrixVisitorException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * 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.</li></ul>
+ * </p>
+ *
+ * @param <T> the type of the field elements
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ */
+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 */
+    protected 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 FieldMatrix<T> with the supplied row and column dimensions.
+     *
+     * @param field field to which the elements belong
+     * @param rowDimension  the number of rows in the new matrix
+     * @param columnDimension  the number of columns in the new matrix
+     * @throws IllegalArgumentException if row or column dimension is not
+     *  positive
+     */
+    public Array2DRowFieldMatrix(final Field<T> field,
+                           final int rowDimension, final int columnDimension)
+        throws IllegalArgumentException {
+        super(field, rowDimension, columnDimension);
+        data = buildArray(field, rowDimension, columnDimension);
+    }
+
+    /**
+     * Create a new 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</code>.</p>
+     *
+     * @param d data for new matrix
+     * @throws IllegalArgumentException if <code>d</code> is not rectangular
+     *  (not all rows have the same length) or empty
+     * @throws NullPointerException if <code>d</code> is null
+     * @see #Array2DRowFieldMatrix(FieldElement[][], boolean)
+     */
+    public Array2DRowFieldMatrix(final T[][] d)
+        throws IllegalArgumentException, NullPointerException {
+        super(extractField(d));
+        copyIn(d);
+    }
+
+    /**
+     * Create a new 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
+     * FieldMatrix<T> and not used directly, the <code>copyArray</code> may be
+     * set to <code>false</code. This will prevent the copying and improve
+     * performance as no new array will be built and no data will be copied.</p>
+     * @param d data for new matrix
+     * @param copyArray if true, the input array will be copied, otherwise
+     * it will be referenced
+     * @throws IllegalArgumentException if <code>d</code> is not rectangular
+     *  (not all rows have the same length) or empty
+     * @throws NullPointerException if <code>d</code> is null
+     * @see #Array2DRowFieldMatrix(FieldElement[][])
+     */
+    public Array2DRowFieldMatrix(final T[][] d, final boolean copyArray)
+        throws IllegalArgumentException, NullPointerException {
+        super(extractField(d));
+        if (copyArray) {
+            copyIn(d);
+        } else {
+            if (d == null) {
+                throw new NullPointerException();
+            }
+            final int nRows = d.length;
+            if (nRows == 0) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                      LocalizedFormats.AT_LEAST_ONE_ROW);
+            }
+            final int nCols = d[0].length;
+            if (nCols == 0) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                      LocalizedFormats.AT_LEAST_ONE_COLUMN);
+            }
+            for (int r = 1; r < nRows; r++) {
+                if (d[r].length != nCols) {
+                    throw MathRuntimeException.createIllegalArgumentException(
+                          LocalizedFormats.DIFFERENT_ROWS_LENGTHS, nCols, d[r].length);
+                }
+            }
+            data = d;
+        }
+    }
+
+    /**
+     * Create a new (column) FieldMatrix<T> using <code>v</code> as the
+     * data for the unique column of the <code>v.length x 1</code> matrix
+     * created.
+     * <p>The input array is copied, not referenced.</p>
+     *
+     * @param v column vector holding data for new matrix
+     */
+    public Array2DRowFieldMatrix(final T[] v) {
+        super(extractField(v));
+        final int nRows = v.length;
+        data = 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 IllegalArgumentException {
+        return new Array2DRowFieldMatrix<T>(getField(), rowDimension, columnDimension);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public FieldMatrix<T> copy() {
+        return new Array2DRowFieldMatrix<T>(copyOut(), false);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public FieldMatrix<T> add(final FieldMatrix<T> m)
+        throws IllegalArgumentException {
+        try {
+            return add((Array2DRowFieldMatrix<T>) m);
+        } catch (ClassCastException cce) {
+            return super.add(m);
+        }
+    }
+
+    /**
+     * Compute the sum of this and <code>m</code>.
+     *
+     * @param m    matrix to be added
+     * @return     this + m
+     * @throws  IllegalArgumentException if m is not the same size as this
+     */
+    public Array2DRowFieldMatrix<T> add(final Array2DRowFieldMatrix<T> m)
+        throws IllegalArgumentException {
+
+        // safety check
+        checkAdditionCompatible(m);
+
+        final int rowCount    = getRowDimension();
+        final int columnCount = getColumnDimension();
+        final T[][] outData = 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>(outData, false);
+
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public FieldMatrix<T> subtract(final FieldMatrix<T> m)
+        throws IllegalArgumentException {
+        try {
+            return subtract((Array2DRowFieldMatrix<T>) m);
+        } catch (ClassCastException cce) {
+            return super.subtract(m);
+        }
+    }
+
+    /**
+     * Compute  this minus <code>m</code>.
+     *
+     * @param m    matrix to be subtracted
+     * @return     this + m
+     * @throws  IllegalArgumentException if m is not the same size as this
+     */
+    public Array2DRowFieldMatrix<T> subtract(final Array2DRowFieldMatrix<T> m)
+        throws IllegalArgumentException {
+
+        // safety check
+        checkSubtractionCompatible(m);
+
+        final int rowCount    = getRowDimension();
+        final int columnCount = getColumnDimension();
+        final T[][] outData = 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>(outData, false);
+
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public FieldMatrix<T> multiply(final FieldMatrix<T> m)
+        throws IllegalArgumentException {
+        try {
+            return multiply((Array2DRowFieldMatrix<T>) m);
+        } catch (ClassCastException cce) {
+            return super.multiply(m);
+        }
+    }
+
+    /**
+     * Returns the result of postmultiplying this by <code>m</code>.
+     * @param m    matrix to postmultiply by
+     * @return     this*m
+     * @throws     IllegalArgumentException
+     *             if columnDimension(this) != rowDimension(m)
+     */
+    public Array2DRowFieldMatrix<T> multiply(final Array2DRowFieldMatrix<T> m)
+        throws IllegalArgumentException {
+
+        // safety check
+        checkMultiplicationCompatible(m);
+
+        final int nRows = this.getRowDimension();
+        final int nCols = m.getColumnDimension();
+        final int nSum = this.getColumnDimension();
+        final T[][] outData = 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>(outData, false);
+
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public T[][] getData() {
+        return copyOut();
+    }
+
+    /**
+     * Returns a reference to the underlying data array.
+     * <p>
+     * Does <strong>not</strong> make a fresh copy of the underlying data.</p>
+     *
+     * @return 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 MatrixIndexException {
+        if (data == null) {
+            if (row > 0) {
+                throw MathRuntimeException.createIllegalStateException(
+                      LocalizedFormats.FIRST_ROWS_NOT_INITIALIZED_YET, row);
+            }
+            if (column > 0) {
+                throw MathRuntimeException.createIllegalStateException(
+                      LocalizedFormats.FIRST_COLUMNS_NOT_INITIALIZED_YET, column);
+            }
+            final int nRows = subMatrix.length;
+            if (nRows == 0) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                      LocalizedFormats.AT_LEAST_ONE_ROW);
+            }
+
+            final int nCols = subMatrix[0].length;
+            if (nCols == 0) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                      LocalizedFormats.AT_LEAST_ONE_COLUMN);
+            }
+            data = buildArray(getField(), subMatrix.length, nCols);
+            for (int i = 0; i < data.length; ++i) {
+                if (subMatrix[i].length != nCols) {
+                    throw MathRuntimeException.createIllegalArgumentException(
+                          LocalizedFormats.DIFFERENT_ROWS_LENGTHS, 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 MatrixIndexException {
+        try {
+            return data[row][column];
+        } catch (ArrayIndexOutOfBoundsException e) {
+            throw new MatrixIndexException(
+                      LocalizedFormats.NO_SUCH_MATRIX_ENTRY, row, column, getRowDimension(), getColumnDimension());
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setEntry(final int row, final int column, final T value)
+        throws MatrixIndexException {
+        try {
+            data[row][column] = value;
+        } catch (ArrayIndexOutOfBoundsException e) {
+            throw new MatrixIndexException(
+                      LocalizedFormats.NO_SUCH_MATRIX_ENTRY, row, column, getRowDimension(), getColumnDimension());
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addToEntry(final int row, final int column, final T increment)
+        throws MatrixIndexException {
+        try {
+            data[row][column] = data[row][column].add(increment);
+        } catch (ArrayIndexOutOfBoundsException e) {
+            throw new MatrixIndexException(
+                      LocalizedFormats.NO_SUCH_MATRIX_ENTRY, row, column, getRowDimension(), getColumnDimension());
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void multiplyEntry(final int row, final int column, final T factor)
+        throws MatrixIndexException {
+        try {
+            data[row][column] = data[row][column].multiply(factor);
+        } catch (ArrayIndexOutOfBoundsException e) {
+            throw new MatrixIndexException(
+                      LocalizedFormats.NO_SUCH_MATRIX_ENTRY, row, column, getRowDimension(), getColumnDimension());
+        }
+    }
+
+    /** {@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 IllegalArgumentException {
+        final int nRows = this.getRowDimension();
+        final int nCols = this.getColumnDimension();
+        if (v.length != nCols) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.VECTOR_LENGTH_MISMATCH, v.length, nCols);
+        }
+        final T[] out = 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 IllegalArgumentException {
+
+        final int nRows = getRowDimension();
+        final int nCols = getColumnDimension();
+        if (v.length != nRows) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.VECTOR_LENGTH_MISMATCH, v.length, nRows);
+        }
+
+        final T[] out = 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)
+        throws MatrixVisitorException {
+        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)
+        throws MatrixVisitorException {
+        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 MatrixIndexException, MatrixVisitorException {
+        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 MatrixIndexException, MatrixVisitorException {
+        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)
+        throws MatrixVisitorException {
+        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)
+        throws MatrixVisitorException {
+        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 MatrixIndexException, MatrixVisitorException {
+        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 MatrixIndexException, MatrixVisitorException {
+        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();
+    }
+
+    /**
+     * Returns 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 = 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;
+    }
+
+    /**
+     * Replaces data with a fresh copy of the input array.
+     * <p>
+     * Verifies that the input array is rectangular and non-empty.</p>
+     *
+     * @param in data to copy in
+     * @throws IllegalArgumentException if input array is empty or not
+     *    rectangular
+     * @throws NullPointerException if input array is null
+     */
+    private void copyIn(final T[][] in) {
+        setSubMatrix(in, 0, 0);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/Array2DRowRealMatrix.java b/src/main/java/org/apache/commons/math/linear/Array2DRowRealMatrix.java
new file mode 100644
index 0000000..5034a47
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/Array2DRowRealMatrix.java
@@ -0,0 +1,621 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.linear;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.linear.MatrixVisitorException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * Implementation of RealMatrix using a double[][] array to store entries and
+ * <a href="http://www.math.gatech.edu/~bourbaki/math2601/Web-notes/2num.pdf">
+ * LU decomposition</a> to support linear system
+ * solution and inverse.
+ * <p>
+ * The LU decomposition is performed as needed, to support the following operations: <ul>
+ * <li>solve</li>
+ * <li>isSingular</li>
+ * <li>getDeterminant</li>
+ * <li>inverse</li> </ul></p>
+ * <p>
+ * <strong>Usage notes</strong>:<br>
+ * <ul><li>
+ * The LU decomposition is cached and reused on subsequent calls.
+ * If data are modified via references to the underlying array obtained using
+ * <code>getDataRef()</code>, then the stored LU decomposition will not be
+ * discarded.  In this case, you need to explicitly invoke
+ * <code>LUDecompose()</code> to recompute the decomposition
+ * before using any of the methods above.</li>
+ * <li>
+ * As specified in the {@link RealMatrix} 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.</li></ul>
+ * </p>
+ *
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ */
+public class Array2DRowRealMatrix extends AbstractRealMatrix implements Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -1067294169172445528L;
+
+    /** Entries of the matrix */
+    protected double data[][];
+
+    /**
+     * Creates a matrix with no data
+     */
+    public Array2DRowRealMatrix() {
+    }
+
+    /**
+     * 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 IllegalArgumentException if row or column dimension is not
+     *  positive
+     */
+    public Array2DRowRealMatrix(final int rowDimension, final int columnDimension)
+        throws IllegalArgumentException {
+        super(rowDimension, columnDimension);
+        data = new double[rowDimension][columnDimension];
+    }
+
+    /**
+     * Create a new 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</code>.</p>
+     *
+     * @param d data for new matrix
+     * @throws IllegalArgumentException if <code>d</code> is not rectangular
+     *  (not all rows have the same length) or empty
+     * @throws NullPointerException if <code>d</code> is null
+     * @see #Array2DRowRealMatrix(double[][], boolean)
+     */
+    public Array2DRowRealMatrix(final double[][] d)
+        throws IllegalArgumentException, NullPointerException {
+        copyIn(d);
+    }
+
+    /**
+     * Create a new RealMatrix using the input array as the underlying
+     * data array.
+     * <p>If an array is built specially in order to be embedded in a
+     * RealMatrix and not used directly, the <code>copyArray</code> may be
+     * set to <code>false</code. This will prevent the copying and improve
+     * performance as no new array will be built and no data will be copied.</p>
+     * @param d data for new matrix
+     * @param copyArray if true, the input array will be copied, otherwise
+     * it will be referenced
+     * @throws IllegalArgumentException if <code>d</code> is not rectangular
+     *  (not all rows have the same length) or empty
+     * @throws NullPointerException if <code>d</code> is null
+     * @see #Array2DRowRealMatrix(double[][])
+     */
+    public Array2DRowRealMatrix(final double[][] d, final boolean copyArray)
+        throws IllegalArgumentException, NullPointerException {
+        if (copyArray) {
+            copyIn(d);
+        } else {
+            if (d == null) {
+                throw new NullPointerException();
+            }
+            final int nRows = d.length;
+            if (nRows == 0) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                      LocalizedFormats.AT_LEAST_ONE_ROW);
+            }
+            final int nCols = d[0].length;
+            if (nCols == 0) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                      LocalizedFormats.AT_LEAST_ONE_COLUMN);
+            }
+            for (int r = 1; r < nRows; r++) {
+                if (d[r].length != nCols) {
+                    throw MathRuntimeException.createIllegalArgumentException(
+                          LocalizedFormats.DIFFERENT_ROWS_LENGTHS, nCols, d[r].length);
+                }
+            }
+            data = d;
+        }
+    }
+
+    /**
+     * Create a new (column) RealMatrix using <code>v</code> as the
+     * data for the unique column of the <code>v.length x 1</code> matrix
+     * created.
+     * <p>The input array is copied, not referenced.</p>
+     *
+     * @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 IllegalArgumentException {
+        return new Array2DRowRealMatrix(rowDimension, columnDimension);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealMatrix copy() {
+        return new Array2DRowRealMatrix(copyOut(), false);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealMatrix add(final RealMatrix m)
+        throws IllegalArgumentException {
+        try {
+            return add((Array2DRowRealMatrix) m);
+        } catch (ClassCastException cce) {
+            return super.add(m);
+        }
+    }
+
+    /**
+     * Compute the sum of this and <code>m</code>.
+     *
+     * @param m    matrix to be added
+     * @return     this + m
+     * @throws  IllegalArgumentException if m is not the same size as this
+     */
+    public Array2DRowRealMatrix add(final Array2DRowRealMatrix m)
+        throws IllegalArgumentException {
+
+        // 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);
+
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealMatrix subtract(final RealMatrix m)
+        throws IllegalArgumentException {
+        try {
+            return subtract((Array2DRowRealMatrix) m);
+        } catch (ClassCastException cce) {
+            return super.subtract(m);
+        }
+    }
+
+    /**
+     * Compute  this minus <code>m</code>.
+     *
+     * @param m    matrix to be subtracted
+     * @return     this + m
+     * @throws  IllegalArgumentException if m is not the same size as this
+     */
+    public Array2DRowRealMatrix subtract(final Array2DRowRealMatrix m)
+        throws IllegalArgumentException {
+
+        // safety check
+        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);
+
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealMatrix multiply(final RealMatrix m)
+        throws IllegalArgumentException {
+        try {
+            return multiply((Array2DRowRealMatrix) m);
+        } catch (ClassCastException cce) {
+            return super.multiply(m);
+        }
+    }
+
+    /**
+     * Returns the result of postmultiplying this by <code>m</code>.
+     * @param m    matrix to postmultiply by
+     * @return     this*m
+     * @throws     IllegalArgumentException
+     *             if columnDimension(this) != rowDimension(m)
+     */
+    public Array2DRowRealMatrix multiply(final Array2DRowRealMatrix m)
+        throws IllegalArgumentException {
+
+        // safety check
+        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];
+        for (int row = 0; row < nRows; row++) {
+            final double[] dataRow    = data[row];
+            final double[] outDataRow = outData[row];
+            for (int col = 0; col < nCols; col++) {
+                double sum = 0;
+                for (int i = 0; i < nSum; i++) {
+                    sum += dataRow[i] * m.data[i][col];
+                }
+                outDataRow[col] = sum;
+            }
+        }
+
+        return new Array2DRowRealMatrix(outData, false);
+
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double[][] getData() {
+        return copyOut();
+    }
+
+    /**
+     * Returns a reference to the underlying data array.
+     * <p>
+     * Does <strong>not</strong> make a fresh copy of the underlying data.</p>
+     *
+     * @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 MatrixIndexException {
+        if (data == null) {
+            if (row > 0) {
+                throw MathRuntimeException.createIllegalStateException(
+                      LocalizedFormats.FIRST_ROWS_NOT_INITIALIZED_YET, row);
+            }
+            if (column > 0) {
+                throw MathRuntimeException.createIllegalStateException(
+                      LocalizedFormats.FIRST_COLUMNS_NOT_INITIALIZED_YET, column);
+            }
+            final int nRows = subMatrix.length;
+            if (nRows == 0) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                      LocalizedFormats.AT_LEAST_ONE_ROW);
+            }
+
+            final int nCols = subMatrix[0].length;
+            if (nCols == 0) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                      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 MathRuntimeException.createIllegalArgumentException(
+                          LocalizedFormats.DIFFERENT_ROWS_LENGTHS, nCols, subMatrix[i].length);
+                }
+                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 MatrixIndexException {
+        try {
+            return data[row][column];
+        } catch (ArrayIndexOutOfBoundsException e) {
+            throw new MatrixIndexException(
+                      LocalizedFormats.NO_SUCH_MATRIX_ENTRY, row, column, getRowDimension(), getColumnDimension());
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setEntry(final int row, final int column, final double value)
+        throws MatrixIndexException {
+        try {
+            data[row][column] = value;
+        } catch (ArrayIndexOutOfBoundsException e) {
+            throw new MatrixIndexException(
+                      LocalizedFormats.NO_SUCH_MATRIX_ENTRY, row, column, getRowDimension(), getColumnDimension());
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addToEntry(final int row, final int column, final double increment)
+        throws MatrixIndexException {
+        try {
+            data[row][column] += increment;
+        } catch (ArrayIndexOutOfBoundsException e) {
+            throw new MatrixIndexException(
+                      LocalizedFormats.NO_SUCH_MATRIX_ENTRY, row, column, getRowDimension(), getColumnDimension());
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void multiplyEntry(final int row, final int column, final double factor)
+        throws MatrixIndexException {
+        try {
+            data[row][column] *= factor;
+        } catch (ArrayIndexOutOfBoundsException e) {
+            throw new MatrixIndexException(
+                      LocalizedFormats.NO_SUCH_MATRIX_ENTRY, row, column, getRowDimension(), getColumnDimension());
+        }
+    }
+
+    /** {@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 IllegalArgumentException {
+        final int nRows = this.getRowDimension();
+        final int nCols = this.getColumnDimension();
+        if (v.length != nCols) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.VECTOR_LENGTH_MISMATCH, 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 IllegalArgumentException {
+
+        final int nRows = getRowDimension();
+        final int nCols = getColumnDimension();
+        if (v.length != nRows) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.VECTOR_LENGTH_MISMATCH, 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)
+        throws MatrixVisitorException {
+        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)
+        throws MatrixVisitorException {
+        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 MatrixIndexException, MatrixVisitorException {
+        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 MatrixIndexException, MatrixVisitorException {
+        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)
+        throws MatrixVisitorException {
+        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)
+        throws MatrixVisitorException {
+        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 MatrixIndexException, MatrixVisitorException {
+        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 MatrixIndexException, MatrixVisitorException {
+        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();
+    }
+
+    /**
+     * Returns 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;
+    }
+
+    /**
+     * Replaces data with a fresh copy of the input array.
+     * <p>
+     * Verifies that the input array is rectangular and non-empty.</p>
+     *
+     * @param in data to copy in
+     * @throws IllegalArgumentException if input array is empty or not
+     *    rectangular
+     * @throws NullPointerException if input array is null
+     */
+    private void copyIn(final double[][] in) {
+        setSubMatrix(in, 0, 0);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/ArrayFieldVector.java b/src/main/java/org/apache/commons/math/linear/ArrayFieldVector.java
new file mode 100644
index 0000000..eefce2f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/ArrayFieldVector.java
@@ -0,0 +1,869 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.linear;
+
+import java.io.Serializable;
+import java.lang.reflect.Array;
+import java.util.Arrays;
+
+import org.apache.commons.math.Field;
+import org.apache.commons.math.FieldElement;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * This class implements the {@link FieldVector} interface with a {@link FieldElement} array.
+ * @param <T> the type of the field elements
+ * @version $Revision: 1003997 $ $Date: 2010-10-03 18:45:55 +0200 (dim. 03 oct. 2010) $
+ * @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. */
+    protected T[] data;
+
+    /** Field to which the elements belong. */
+    private final Field<T> field;
+
+    /**
+     * Build a 0-length vector.
+     * <p>Zero-length vectors may be used to initialized 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</code> methods ({@link #append(FieldElement[])},
+     * {@link #add(FieldVector)}, {@link #append(ArrayFieldVector)}) to gather data
+     * into this vector.</p>
+     * @param field field to which the elements belong
+     */
+    public ArrayFieldVector(final Field<T> field) {
+        this(field, 0);
+    }
+
+    /**
+     * Construct a (size)-length vector of zeros.
+     * @param field field to which the elements belong
+     * @param size size of the vector
+     */
+    public ArrayFieldVector(Field<T> field, int size) {
+        this.field = field;
+        data = buildArray(size);
+        Arrays.fill(data, field.getZero());
+    }
+
+    /**
+     * Construct an (size)-length vector with preset values.
+     * @param size size of the vector
+     * @param preset fill the vector with this scalar 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.
+     * <p>
+     * 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.
+     * </p>
+     * @param d array of Ts.
+     * @throws IllegalArgumentException if <code>d</code> is empty
+     * @see #ArrayFieldVector(Field, FieldElement[])
+     */
+    public ArrayFieldVector(T[] d)
+        throws IllegalArgumentException {
+        try {
+            field = d[0].getField();
+            data = d.clone();
+        } catch (ArrayIndexOutOfBoundsException e) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                      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 of Ts.
+     * @see #ArrayFieldVector(FieldElement[])
+     */
+    public ArrayFieldVector(Field<T> field, T[] d) {
+        this.field = field;
+        data = d.clone();
+    }
+
+    /**
+     * Create a new ArrayFieldVector using the input array as the underlying
+     * data array.
+     * <p>If an array is built specially in order to be embedded in a
+     * ArrayFieldVector and not used directly, the <code>copyArray</code> may be
+     * set to <code>false</code. This will prevent the copying and improve
+     * performance as no new array will be built and no data will be copied.</p>
+     * <p>
+     * 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.
+     * </p>
+     * @param d data for new vector
+     * @param copyArray if true, the input array will be copied, otherwise
+     * it will be referenced
+     * @throws IllegalArgumentException if <code>d</code> is empty
+     * @throws NullPointerException if <code>d</code> is null
+     * @see #ArrayFieldVector(FieldElement[])
+     * @see #ArrayFieldVector(Field, FieldElement[], boolean)
+     */
+    public ArrayFieldVector(T[] d, boolean copyArray)
+        throws NullPointerException, IllegalArgumentException {
+        if (d.length == 0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  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.
+     * <p>If an array is built specially in order to be embedded in a
+     * ArrayFieldVector and not used directly, the <code>copyArray</code> may be
+     * set to <code>false</code. This will prevent the copying and improve
+     * performance as no new array will be built and no data will be copied.</p>
+     * @param field field to which the elements belong
+     * @param d data for new vector
+     * @param copyArray if true, the input array will be copied, otherwise
+     * it will be referenced
+     * @see #ArrayFieldVector(FieldElement[], boolean)
+     */
+    public ArrayFieldVector(Field<T> field, T[] d, boolean copyArray) {
+        this.field = field;
+        data = copyArray ? d.clone() :  d;
+    }
+
+    /**
+     * Construct a vector from part of a array.
+     * @param d array of Ts.
+     * @param pos position of first entry
+     * @param size number of entries to copy
+     */
+    public ArrayFieldVector(T[] d, int pos, int size) {
+        if (d.length < pos + size) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.POSITION_SIZE_MISMATCH_INPUT_ARRAY,
+                    pos, size, d.length);
+        }
+        field = d[0].getField();
+        data = buildArray(size);
+        System.arraycopy(d, pos, data, 0, size);
+    }
+
+    /**
+     * Construct a vector from another vector, using a deep copy.
+     * @param v vector to copy
+     */
+    public ArrayFieldVector(FieldVector<T> v) {
+        field = v.getField();
+        data = buildArray(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
+     */
+    public ArrayFieldVector(ArrayFieldVector<T> v) {
+        field = v.getField();
+        data = v.data.clone();
+    }
+
+    /**
+     * Construct a vector from another vector.
+     * @param v vector to copy
+     * @param deep if true perform a deep copy otherwise perform a shallow copy
+     */
+    public ArrayFieldVector(ArrayFieldVector<T> v, boolean deep) {
+        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)
+     */
+    public ArrayFieldVector(ArrayFieldVector<T> v1, ArrayFieldVector<T> v2) {
+        field = v1.getField();
+        data = buildArray(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 ArrayFieldVector(ArrayFieldVector<T> v1, T[] v2) {
+        field = v1.getField();
+        data = buildArray(v1.data.length + v2.length);
+        System.arraycopy(v1.data, 0, data, 0, v1.data.length);
+        System.arraycopy(v2, 0, data, v1.data.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)
+     */
+    public ArrayFieldVector(T[] v1, ArrayFieldVector<T> v2) {
+        field = v2.getField();
+        data = buildArray(v1.length + v2.data.length);
+        System.arraycopy(v1, 0, data, 0, v1.length);
+        System.arraycopy(v2.data, 0, data, v1.length, v2.data.length);
+    }
+
+    /**
+     * Construct a vector by appending one vector to another vector.
+     * <p>
+     * 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.
+     * </p>
+     * @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)
+     * @exception IllegalArgumentException if both vectors are empty
+     * @see #ArrayFieldVector(Field, FieldElement[], FieldElement[])
+     */
+    public ArrayFieldVector(T[] v1, T[] v2) {
+        try {
+            data = buildArray(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();
+        } catch (ArrayIndexOutOfBoundsException e) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                      LocalizedFormats.VECTOR_MUST_HAVE_AT_LEAST_ONE_ELEMENT);
+        }
+    }
+
+    /**
+     * 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)
+     * @see #ArrayFieldVector(FieldElement[], FieldElement[])
+     */
+    public ArrayFieldVector(Field<T> field, T[] v1, T[] v2) {
+        if (v1.length + v2.length == 0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.VECTOR_MUST_HAVE_AT_LEAST_ONE_ELEMENT);
+        }
+        data = buildArray(v1.length + v2.length);
+        System.arraycopy(v1, 0, data, 0, v1.length);
+        System.arraycopy(v2, 0, data, v1.length, v2.length);
+        this.field = data[0].getField();
+    }
+
+    /** 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.getZero().getClass(), length);
+    }
+
+    /** {@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 IllegalArgumentException {
+        try {
+            return add((ArrayFieldVector<T>) v);
+        } catch (ClassCastException cce) {
+            checkVectorDimensions(v);
+            T[] out = buildArray(data.length);
+            for (int i = 0; i < data.length; i++) {
+                out[i] = data[i].add(v.getEntry(i));
+            }
+            return new ArrayFieldVector<T>(out);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> add(T[] v) throws IllegalArgumentException {
+        checkVectorDimensions(v.length);
+        T[] out = buildArray(data.length);
+        for (int i = 0; i < data.length; i++) {
+            out[i] = data[i].add(v[i]);
+        }
+        return new ArrayFieldVector<T>(out);
+    }
+
+    /**
+     * Compute the sum of this and v.
+     * @param v vector to be added
+     * @return this + v
+     * @throws IllegalArgumentException if v is not the same size as this
+     */
+    public ArrayFieldVector<T> add(ArrayFieldVector<T> v)
+        throws IllegalArgumentException {
+        return (ArrayFieldVector<T>) add(v.data);
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> subtract(FieldVector<T> v) throws IllegalArgumentException {
+        try {
+            return subtract((ArrayFieldVector<T>) v);
+        } catch (ClassCastException cce) {
+            checkVectorDimensions(v);
+            T[] out = buildArray(data.length);
+            for (int i = 0; i < data.length; i++) {
+                out[i] = data[i].subtract(v.getEntry(i));
+            }
+            return new ArrayFieldVector<T>(out);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> subtract(T[] v) throws IllegalArgumentException {
+        checkVectorDimensions(v.length);
+        T[] out = buildArray(data.length);
+        for (int i = 0; i < data.length; i++) {
+            out[i] = data[i].subtract(v[i]);
+        }
+        return new ArrayFieldVector<T>(out);
+    }
+
+    /**
+     * Compute this minus v.
+     * @param v vector to be subtracted
+     * @return this + v
+     * @throws IllegalArgumentException if v is not the same size as this
+     */
+    public ArrayFieldVector<T> subtract(ArrayFieldVector<T> v)
+        throws IllegalArgumentException {
+        return (ArrayFieldVector<T>) subtract(v.data);
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> mapAdd(T d) {
+        T[] out = buildArray(data.length);
+        for (int i = 0; i < data.length; i++) {
+            out[i] = data[i].add(d);
+        }
+        return new ArrayFieldVector<T>(out);
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> mapAddToSelf(T d) {
+        for (int i = 0; i < data.length; i++) {
+            data[i] = data[i].add(d);
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> mapSubtract(T d) {
+        T[] out = buildArray(data.length);
+        for (int i = 0; i < data.length; i++) {
+            out[i] = data[i].subtract(d);
+        }
+        return new ArrayFieldVector<T>(out);
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> mapSubtractToSelf(T d) {
+        for (int i = 0; i < data.length; i++) {
+            data[i] = data[i].subtract(d);
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> mapMultiply(T d) {
+        T[] out = buildArray(data.length);
+        for (int i = 0; i < data.length; i++) {
+            out[i] = data[i].multiply(d);
+        }
+        return new ArrayFieldVector<T>(out);
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> mapMultiplyToSelf(T d) {
+        for (int i = 0; i < data.length; i++) {
+            data[i] = data[i].multiply(d);
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> mapDivide(T d) {
+        T[] out = buildArray(data.length);
+        for (int i = 0; i < data.length; i++) {
+            out[i] = data[i].divide(d);
+        }
+        return new ArrayFieldVector<T>(out);
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> mapDivideToSelf(T d) {
+        for (int i = 0; i < data.length; i++) {
+            data[i] = data[i].divide(d);
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> mapInv() {
+        T[] out = buildArray(data.length);
+        final T one = field.getOne();
+        for (int i = 0; i < data.length; i++) {
+            out[i] = one.divide(data[i]);
+        }
+        return new ArrayFieldVector<T>(out);
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> mapInvToSelf() {
+        final T one = field.getOne();
+        for (int i = 0; i < data.length; i++) {
+            data[i] = one.divide(data[i]);
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> ebeMultiply(FieldVector<T> v)
+        throws IllegalArgumentException {
+        try {
+            return ebeMultiply((ArrayFieldVector<T>) v);
+        } catch (ClassCastException cce) {
+            checkVectorDimensions(v);
+            T[] out = buildArray(data.length);
+            for (int i = 0; i < data.length; i++) {
+                out[i] = data[i].multiply(v.getEntry(i));
+            }
+            return new ArrayFieldVector<T>(out);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> ebeMultiply(T[] v)
+        throws IllegalArgumentException {
+        checkVectorDimensions(v.length);
+        T[] out = buildArray(data.length);
+        for (int i = 0; i < data.length; i++) {
+            out[i] = data[i].multiply(v[i]);
+        }
+        return new ArrayFieldVector<T>(out);
+    }
+
+    /**
+     * 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
+     * @exception IllegalArgumentException if v is not the same size as this
+     */
+    public ArrayFieldVector<T> ebeMultiply(ArrayFieldVector<T> v)
+        throws IllegalArgumentException {
+        return (ArrayFieldVector<T>) ebeMultiply(v.data);
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> ebeDivide(FieldVector<T> v)
+        throws IllegalArgumentException {
+        try {
+            return ebeDivide((ArrayFieldVector<T>) v);
+        } catch (ClassCastException cce) {
+            checkVectorDimensions(v);
+            T[] out = buildArray(data.length);
+            for (int i = 0; i < data.length; i++) {
+                out[i] = data[i].divide(v.getEntry(i));
+            }
+            return new ArrayFieldVector<T>(out);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> ebeDivide(T[] v)
+        throws IllegalArgumentException {
+        checkVectorDimensions(v.length);
+        T[] out = buildArray(data.length);
+        for (int i = 0; i < data.length; i++) {
+                out[i] = data[i].divide(v[i]);
+        }
+        return new ArrayFieldVector<T>(out);
+    }
+
+    /**
+     * 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 IllegalArgumentException if v is not the same size as this
+     */
+    public ArrayFieldVector<T> ebeDivide(ArrayFieldVector<T> v)
+        throws IllegalArgumentException {
+        return (ArrayFieldVector<T>) ebeDivide(v.data);
+    }
+
+    /** {@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.</p>
+     * @return array of entries
+     */
+    public T[] getDataRef() {
+        return data;
+    }
+
+    /** {@inheritDoc} */
+    public T dotProduct(FieldVector<T> v)
+        throws IllegalArgumentException {
+        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;
+        }
+    }
+
+    /** {@inheritDoc} */
+    public T dotProduct(T[] v)
+        throws IllegalArgumentException {
+        checkVectorDimensions(v.length);
+        T dot = field.getZero();
+        for (int i = 0; i < data.length; i++) {
+            dot = dot.add(data[i].multiply(v[i]));
+        }
+        return dot;
+    }
+
+    /**
+     * Compute the dot product.
+     * @param v vector with which dot product should be computed
+     * @return the scalar dot product between instance and v
+     * @exception IllegalArgumentException if v is not the same size as this
+     */
+    public T dotProduct(ArrayFieldVector<T> v)
+        throws IllegalArgumentException {
+        return dotProduct(v.data);
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> projection(FieldVector<T> v) {
+        return v.mapMultiply(dotProduct(v).divide(v.dotProduct(v)));
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> projection(T[] v) {
+        return projection(new ArrayFieldVector<T>(v, false));
+    }
+
+   /** 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 v
+     * @throws IllegalArgumentException if v is not the same size as this
+     */
+    public ArrayFieldVector<T> projection(ArrayFieldVector<T> v) {
+        return (ArrayFieldVector<T>) v.mapMultiply(dotProduct(v).divide(v.dotProduct(v)));
+    }
+
+    /** {@inheritDoc} */
+    public FieldMatrix<T> outerProduct(FieldVector<T> v)
+        throws IllegalArgumentException {
+        try {
+            return outerProduct((ArrayFieldVector<T>) v);
+        } catch (ClassCastException cce) {
+            checkVectorDimensions(v);
+            final int m = data.length;
+            final FieldMatrix<T> out = new Array2DRowFieldMatrix<T>(field, m, m);
+            for (int i = 0; i < data.length; i++) {
+                for (int j = 0; j < data.length; 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 square matrix outer product between instance and v
+     * @exception IllegalArgumentException if v is not the same size as this
+     */
+    public FieldMatrix<T> outerProduct(ArrayFieldVector<T> v)
+        throws IllegalArgumentException {
+        return outerProduct(v.data);
+    }
+
+    /** {@inheritDoc} */
+    public FieldMatrix<T> outerProduct(T[] v)
+        throws IllegalArgumentException {
+        checkVectorDimensions(v.length);
+        final int m = data.length;
+        final FieldMatrix<T> out = new Array2DRowFieldMatrix<T>(field, m, m);
+        for (int i = 0; i < data.length; i++) {
+            for (int j = 0; j < data.length; j++) {
+                out.setEntry(i, j, data[i].multiply(v[j]));
+            }
+        }
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    public T getEntry(int index) throws MatrixIndexException {
+        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 = buildArray(data.length + 1);
+        System.arraycopy(data, 0, out, 0, data.length);
+        out[data.length] = in;
+        return new ArrayFieldVector<T>(out);
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> append(T[] in) {
+        return new ArrayFieldVector<T>(this, in);
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> getSubVector(int index, int 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) {
+        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);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void setSubVector(int index, T[] v) {
+        try {
+            System.arraycopy(v, 0, data, index, v.length);
+        } catch (IndexOutOfBoundsException e) {
+            checkIndex(index);
+            checkIndex(index + v.length - 1);
+        }
+    }
+
+    /**
+     * Set a set of consecutive elements.
+     *
+     * @param index index of first element to be set.
+     * @param v vector containing the values to set.
+     * @exception MatrixIndexException if the index is
+     * inconsistent with vector size
+     */
+    public void set(int index, ArrayFieldVector<T> v)
+        throws MatrixIndexException {
+        setSubVector(index, v.data);
+    }
+
+    /** {@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 IllegalArgumentException if the vectors do not
+     * have the same dimension
+     */
+    protected void checkVectorDimensions(FieldVector<T> v)
+        throws IllegalArgumentException {
+        checkVectorDimensions(v.getDimension());
+    }
+
+    /**
+     * Check if instance dimension is equal to some expected value.
+     *
+     * @param n expected dimension.
+     * @exception IllegalArgumentException if the dimension is
+     * inconsistent with vector size
+     */
+    protected void checkVectorDimensions(int n)
+        throws IllegalArgumentException {
+        if (data.length != n) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.VECTOR_LENGTH_MISMATCH,
+                    data.length, n);
+        }
+    }
+
+    /**
+     * Test for the equality of two real vectors.
+     * <p>
+     * If all coordinates of two real vectors are exactly the same, and none are
+     * <code>Double.NaN</code>, the two real 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
+     * real vector are equal to <code>Double.NaN</code>, the real vector is equal to
+     * a vector with all <code>Double.NaN</code> coordinates.
+     * </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 == 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.</p>
+     * @return a hash code value for this object
+     */
+    @Override
+    public int hashCode() {
+        int h = 3542;
+        for (final T a : data) {
+            h = h ^ a.hashCode();
+        }
+        return h;
+    }
+
+    /**
+     * Check if an index is valid.
+     * @param index index to check
+     * @exception MatrixIndexException if index is not valid
+     */
+    private void checkIndex(final int index)
+        throws MatrixIndexException {
+        if (index < 0 || index >= getDimension()) {
+            throw new MatrixIndexException(LocalizedFormats.INDEX_OUT_OF_RANGE,
+                                           index, 0, getDimension() - 1);
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/ArrayRealVector.java b/src/main/java/org/apache/commons/math/linear/ArrayRealVector.java
new file mode 100644
index 0000000..9f15a6e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/ArrayRealVector.java
@@ -0,0 +1,1228 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.linear;
+
+import java.io.Serializable;
+import java.util.Arrays;
+import java.util.Iterator;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.util.MathUtils;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * This class implements the {@link RealVector} interface with a double array.
+ * @version $Revision: 1003993 $ $Date: 2010-10-03 18:39:16 +0200 (dim. 03 oct. 2010) $
+ * @since 2.0
+ */
+public class ArrayRealVector extends AbstractRealVector 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. */
+    protected double data[];
+
+    /**
+     * Build a 0-length vector.
+     * <p>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</code> method ({@link #append(double)}, {@link
+     * #append(double[])}, {@link #append(ArrayRealVector)}) to gather data
+     * into this vector.</p>
+     */
+    public ArrayRealVector() {
+        data = new double[0];
+    }
+
+    /**
+     * Construct a (size)-length vector of zeros.
+     * @param size size of the vector
+     */
+    public ArrayRealVector(int size) {
+        data = new double[size];
+    }
+
+    /**
+     * Construct an (size)-length vector with preset values.
+     * @param size size of the vector
+     * @param preset fill the vector with this scalar 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 of doubles.
+     */
+    public ArrayRealVector(double[] d) {
+        data = d.clone();
+    }
+
+    /**
+     * Create a new ArrayRealVector using the input array as the underlying
+     * data array.
+     * <p>If an array is built specially in order to be embedded in a
+     * ArrayRealVector and not used directly, the <code>copyArray</code> may be
+     * set to <code>false</code. This will prevent the copying and improve
+     * performance as no new array will be built and no data will be copied.</p>
+     * @param d data for new vector
+     * @param copyArray if true, the input array will be copied, otherwise
+     * it will be referenced
+     * @see #ArrayRealVector(double[])
+     */
+    public ArrayRealVector(double[] d, boolean copyArray) {
+        data = copyArray ? d.clone() :  d;
+    }
+
+    /**
+     * Construct a vector from part of a array.
+     * @param d array of doubles.
+     * @param pos position of first entry
+     * @param size number of entries to copy
+     */
+    public ArrayRealVector(double[] d, int pos, int size) {
+        if (d.length < pos + size) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.POSITION_SIZE_MISMATCH_INPUT_ARRAY, pos, size, d.length);
+        }
+        data = new double[size];
+        System.arraycopy(d, pos, data, 0, size);
+    }
+
+    /**
+     * Construct a vector from an array.
+     * @param d array of Doubles.
+     */
+    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 a Double array
+     * @param d array of Doubles.
+     * @param pos position of first entry
+     * @param size number of entries to copy
+     */
+    public ArrayRealVector(Double[] d, int pos, int size) {
+        if (d.length < pos + size) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.POSITION_SIZE_MISMATCH_INPUT_ARRAY, pos, size, d.length);
+        }
+        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
+     */
+    public ArrayRealVector(RealVector v) {
+        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
+     */
+    public ArrayRealVector(ArrayRealVector v) {
+        this(v, true);
+    }
+
+    /**
+     * Construct a vector from another vector.
+     * @param v vector to copy
+     * @param deep if 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 AbstractRealVector copy() {
+        return new ArrayRealVector(this, true);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector add(RealVector v)
+        throws IllegalArgumentException {
+        if (v instanceof ArrayRealVector) {
+            return add((ArrayRealVector) v);
+        } else {
+            checkVectorDimensions(v);
+            double[] out = data.clone();
+            Iterator<Entry> it = v.sparseIterator();
+            Entry e;
+            while (it.hasNext() && (e = it.next()) != null) {
+                out[e.getIndex()] += e.getValue();
+            }
+            return new ArrayRealVector(out, false);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector add(double[] v)
+        throws IllegalArgumentException {
+        checkVectorDimensions(v.length);
+        double[] out = data.clone();
+        for (int i = 0; i < data.length; i++) {
+            out[i] += v[i];
+        }
+        return new ArrayRealVector(out, false);
+    }
+
+    /**
+     * Compute the sum of this and v.
+     * @param v vector to be added
+     * @return this + v
+     * @throws IllegalArgumentException if v is not the same size as this
+     */
+    public ArrayRealVector add(ArrayRealVector v)
+        throws IllegalArgumentException {
+        return (ArrayRealVector) add(v.data);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector subtract(RealVector v)
+        throws IllegalArgumentException {
+        if (v instanceof ArrayRealVector) {
+            return subtract((ArrayRealVector) v);
+        } else {
+            checkVectorDimensions(v);
+            double[] out = data.clone();
+            Iterator<Entry> it = v.sparseIterator();
+            Entry e;
+            while(it.hasNext() && (e = it.next()) != null) {
+                out[e.getIndex()] -= e.getValue();
+            }
+            return new ArrayRealVector(out, false);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector subtract(double[] v)
+        throws IllegalArgumentException {
+        checkVectorDimensions(v.length);
+        double[] out = data.clone();
+        for (int i = 0; i < data.length; i++) {
+            out[i] -= v[i];
+        }
+        return new ArrayRealVector(out, false);
+    }
+
+    /**
+     * Compute this minus v.
+     * @param v vector to be subtracted
+     * @return this + v
+     * @throws IllegalArgumentException if v is not the same size as this
+     */
+    public ArrayRealVector subtract(ArrayRealVector v)
+        throws IllegalArgumentException {
+        return (ArrayRealVector) subtract(v.data);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector mapAddToSelf(double d) {
+        for (int i = 0; i < data.length; i++) {
+            data[i] = data[i] + d;
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector mapSubtractToSelf(double d) {
+        for (int i = 0; i < data.length; i++) {
+            data[i] = data[i] - d;
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector mapMultiplyToSelf(double d) {
+        for (int i = 0; i < data.length; i++) {
+            data[i] = data[i] * d;
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector mapDivideToSelf(double d) {
+        for (int i = 0; i < data.length; i++) {
+            data[i] = data[i] / d;
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector mapPowToSelf(double d) {
+        for (int i = 0; i < data.length; i++) {
+            data[i] = FastMath.pow(data[i], d);
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector mapExpToSelf() {
+        for (int i = 0; i < data.length; i++) {
+            data[i] = FastMath.exp(data[i]);
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector mapExpm1ToSelf() {
+        for (int i = 0; i < data.length; i++) {
+            data[i] = FastMath.expm1(data[i]);
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector mapLogToSelf() {
+        for (int i = 0; i < data.length; i++) {
+            data[i] = FastMath.log(data[i]);
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector mapLog10ToSelf() {
+        for (int i = 0; i < data.length; i++) {
+            data[i] = FastMath.log10(data[i]);
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector mapLog1pToSelf() {
+        for (int i = 0; i < data.length; i++) {
+            data[i] = FastMath.log1p(data[i]);
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector mapCoshToSelf() {
+        for (int i = 0; i < data.length; i++) {
+            data[i] = FastMath.cosh(data[i]);
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector mapSinhToSelf() {
+        for (int i = 0; i < data.length; i++) {
+            data[i] = FastMath.sinh(data[i]);
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector mapTanhToSelf() {
+        for (int i = 0; i < data.length; i++) {
+            data[i] = FastMath.tanh(data[i]);
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector mapCosToSelf() {
+        for (int i = 0; i < data.length; i++) {
+            data[i] = FastMath.cos(data[i]);
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector mapSinToSelf() {
+        for (int i = 0; i < data.length; i++) {
+            data[i] = FastMath.sin(data[i]);
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector mapTanToSelf() {
+        for (int i = 0; i < data.length; i++) {
+            data[i] = FastMath.tan(data[i]);
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector mapAcosToSelf() {
+        for (int i = 0; i < data.length; i++) {
+            data[i] = FastMath.acos(data[i]);
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector mapAsinToSelf() {
+        for (int i = 0; i < data.length; i++) {
+            data[i] = FastMath.asin(data[i]);
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector mapAtanToSelf() {
+        for (int i = 0; i < data.length; i++) {
+            data[i] = FastMath.atan(data[i]);
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector mapInvToSelf() {
+        for (int i = 0; i < data.length; i++) {
+            data[i] = 1.0 / data[i];
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector mapAbsToSelf() {
+        for (int i = 0; i < data.length; i++) {
+            data[i] = FastMath.abs(data[i]);
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector mapSqrtToSelf() {
+        for (int i = 0; i < data.length; i++) {
+            data[i] = FastMath.sqrt(data[i]);
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector mapCbrtToSelf() {
+        for (int i = 0; i < data.length; i++) {
+            data[i] = FastMath.cbrt(data[i]);
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector mapCeilToSelf() {
+        for (int i = 0; i < data.length; i++) {
+            data[i] = FastMath.ceil(data[i]);
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector mapFloorToSelf() {
+        for (int i = 0; i < data.length; i++) {
+            data[i] = FastMath.floor(data[i]);
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector mapRintToSelf() {
+        for (int i = 0; i < data.length; i++) {
+            data[i] = FastMath.rint(data[i]);
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector mapSignumToSelf() {
+        for (int i = 0; i < data.length; i++) {
+            data[i] = FastMath.signum(data[i]);
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector mapUlpToSelf() {
+        for (int i = 0; i < data.length; i++) {
+            data[i] = FastMath.ulp(data[i]);
+        }
+        return this;
+    }
+
+    /** {@inheritDoc} */
+    public RealVector ebeMultiply(RealVector v)
+        throws IllegalArgumentException {
+        if (v instanceof ArrayRealVector) {
+            return ebeMultiply((ArrayRealVector) v);
+        } 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 RealVector ebeMultiply(double[] v)
+        throws IllegalArgumentException {
+        checkVectorDimensions(v.length);
+        double[] out = data.clone();
+        for (int i = 0; i < data.length; i++) {
+            out[i] *= v[i];
+        }
+        return new ArrayRealVector(out, false);
+    }
+
+    /**
+     * 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
+     * @exception IllegalArgumentException if v is not the same size as this
+     */
+    public ArrayRealVector ebeMultiply(ArrayRealVector v)
+        throws IllegalArgumentException {
+        return (ArrayRealVector) ebeMultiply(v.data);
+    }
+
+    /** {@inheritDoc} */
+    public RealVector ebeDivide(RealVector v)
+        throws IllegalArgumentException {
+        if (v instanceof ArrayRealVector) {
+            return ebeDivide((ArrayRealVector) v);
+        } 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 RealVector ebeDivide(double[] v)
+        throws IllegalArgumentException {
+        checkVectorDimensions(v.length);
+        double[] out = data.clone();
+        for (int i = 0; i < data.length; i++) {
+                out[i] /= v[i];
+        }
+        return new ArrayRealVector(out, false);
+    }
+
+    /**
+     * 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 IllegalArgumentException if v is not the same size as this
+     */
+    public ArrayRealVector ebeDivide(ArrayRealVector v)
+        throws IllegalArgumentException {
+        return (ArrayRealVector) ebeDivide(v.data);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double[] getData() {
+        return data.clone();
+    }
+
+    /**
+     * Returns a reference to the underlying data array.
+     * <p>Does not make a fresh copy of the underlying data.</p>
+     * @return array of entries
+     */
+    public double[] getDataRef() {
+        return data;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double dotProduct(RealVector v)
+        throws IllegalArgumentException {
+        if (v instanceof ArrayRealVector) {
+            return dotProduct((ArrayRealVector) v);
+        } else {
+            checkVectorDimensions(v);
+            double dot = 0;
+            Iterator<Entry> it = v.sparseIterator();
+            Entry e;
+            while(it.hasNext() && (e = it.next()) != null) {
+                dot += data[e.getIndex()] * e.getValue();
+            }
+            return dot;
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double dotProduct(double[] v)
+        throws IllegalArgumentException {
+        checkVectorDimensions(v.length);
+        double dot = 0;
+        for (int i = 0; i < data.length; i++) {
+            dot += data[i] * v[i];
+        }
+        return dot;
+    }
+
+    /**
+     * Compute the dot product.
+     * @param v vector with which dot product should be computed
+     * @return the scalar dot product between instance and v
+     * @exception IllegalArgumentException if v is not the same size as this
+     */
+    public double dotProduct(ArrayRealVector v)
+        throws IllegalArgumentException {
+        return dotProduct(v.data);
+    }
+
+    /** {@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 IllegalArgumentException {
+        if (v instanceof ArrayRealVector) {
+            return getDistance((ArrayRealVector) v);
+        } 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 getDistance(double[] v)
+        throws IllegalArgumentException {
+        checkVectorDimensions(v.length);
+        double sum = 0;
+        for (int i = 0; i < data.length; ++i) {
+            final double delta = data[i] - v[i];
+            sum += delta * delta;
+        }
+        return FastMath.sqrt(sum);
+    }
+
+   /**
+     * 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
+     * elements differences, or euclidian distance.</p>
+     * @param v vector to which distance is requested
+     * @return distance between two vectors.
+     * @exception IllegalArgumentException if v is not the same size as this
+     * @see #getDistance(RealVector)
+     * @see #getL1Distance(ArrayRealVector)
+     * @see #getLInfDistance(ArrayRealVector)
+     * @see #getNorm()
+     */
+    public double getDistance(ArrayRealVector v)
+        throws IllegalArgumentException {
+        return getDistance(v.data);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getL1Distance(RealVector v)
+        throws IllegalArgumentException {
+        if (v instanceof ArrayRealVector) {
+            return getL1Distance((ArrayRealVector) v);
+        } 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 getL1Distance(double[] v)
+        throws IllegalArgumentException {
+        checkVectorDimensions(v.length);
+        double sum = 0;
+        for (int i = 0; i < data.length; ++i) {
+            final double delta = data[i] - v[i];
+            sum += FastMath.abs(delta);
+        }
+        return sum;
+    }
+
+    /**
+     * 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
+     * elements differences.</p>
+     * @param v vector to which distance is requested
+     * @return distance between two vectors.
+     * @exception IllegalArgumentException if v is not the same size as this
+     * @see #getDistance(RealVector)
+     * @see #getL1Distance(ArrayRealVector)
+     * @see #getLInfDistance(ArrayRealVector)
+     * @see #getNorm()
+     */
+    public double getL1Distance(ArrayRealVector v)
+        throws IllegalArgumentException {
+        return getL1Distance(v.data);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getLInfDistance(RealVector v)
+        throws IllegalArgumentException {
+        if (v instanceof ArrayRealVector) {
+            return getLInfDistance((ArrayRealVector) v);
+        } 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 double getLInfDistance(double[] v)
+        throws IllegalArgumentException {
+        checkVectorDimensions(v.length);
+        double max = 0;
+        for (int i = 0; i < data.length; ++i) {
+            final double delta = data[i] - v[i];
+            max = FastMath.max(max, FastMath.abs(delta));
+        }
+        return max;
+    }
+
+    /**
+     * 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
+     * elements differences.</p>
+     * @param v vector to which distance is requested
+     * @return distance between two vectors.
+     * @exception IllegalArgumentException if v is not the same size as this
+     * @see #getDistance(RealVector)
+     * @see #getL1Distance(ArrayRealVector)
+     * @see #getLInfDistance(ArrayRealVector)
+     * @see #getNorm()
+     */
+    public double getLInfDistance(ArrayRealVector v)
+        throws IllegalArgumentException {
+        return getLInfDistance(v.data);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector unitVector() throws ArithmeticException {
+        final double norm = getNorm();
+        if (norm == 0) {
+            throw MathRuntimeException.createArithmeticException(LocalizedFormats.ZERO_NORM);
+        }
+        return mapDivide(norm);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void unitize() throws ArithmeticException {
+        final double norm = getNorm();
+        if (norm == 0) {
+            throw MathRuntimeException.createArithmeticException(LocalizedFormats.CANNOT_NORMALIZE_A_ZERO_NORM_VECTOR);
+        }
+        mapDivideToSelf(norm);
+    }
+
+    /** {@inheritDoc} */
+    public RealVector projection(RealVector v) {
+        return v.mapMultiply(dotProduct(v) / v.dotProduct(v));
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector projection(double[] v) {
+        return projection(new ArrayRealVector(v, false));
+    }
+
+   /** 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 v
+     * @throws IllegalArgumentException if v is not the same size as this
+     */
+    public ArrayRealVector projection(ArrayRealVector v) {
+        return (ArrayRealVector) v.mapMultiply(dotProduct(v) / v.dotProduct(v));
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealMatrix outerProduct(RealVector v)
+        throws IllegalArgumentException {
+        if (v instanceof ArrayRealVector) {
+            return outerProduct((ArrayRealVector) v);
+        } else {
+            checkVectorDimensions(v);
+            final int m = data.length;
+            final RealMatrix out = MatrixUtils.createRealMatrix(m, m);
+            for (int i = 0; i < data.length; i++) {
+                for (int j = 0; j < data.length; j++) {
+                    out.setEntry(i, j, data[i] * v.getEntry(j));
+                }
+            }
+            return out;
+        }
+    }
+
+    /**
+     * Compute the outer product.
+     * @param v vector with which outer product should be computed
+     * @return the square matrix outer product between instance and v
+     * @exception IllegalArgumentException if v is not the same size as this
+     */
+    public RealMatrix outerProduct(ArrayRealVector v)
+        throws IllegalArgumentException {
+        return outerProduct(v.data);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealMatrix outerProduct(double[] v)
+        throws IllegalArgumentException {
+        checkVectorDimensions(v.length);
+        final int m = data.length;
+        final RealMatrix out = MatrixUtils.createRealMatrix(m, m);
+        for (int i = 0; i < data.length; i++) {
+            for (int j = 0; j < data.length; j++) {
+                out.setEntry(i, j, data[i] * v[j]);
+            }
+        }
+        return out;
+    }
+
+    /** {@inheritDoc} */
+    public double getEntry(int index) throws MatrixIndexException {
+        return data[index];
+    }
+
+    /** {@inheritDoc} */
+    public int getDimension() {
+        return data.length;
+    }
+
+    /** {@inheritDoc} */
+    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} */
+    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} */
+    public RealVector append(double[] in) {
+        return new ArrayRealVector(this, in);
+    }
+
+    /** {@inheritDoc} */
+    public RealVector getSubVector(int index, int 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} */
+    public void setEntry(int index, double value) {
+        try {
+            data[index] = value;
+        } catch (IndexOutOfBoundsException e) {
+            checkIndex(index);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setSubVector(int index, RealVector v) {
+        try {
+            try {
+                set(index, (ArrayRealVector) 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);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setSubVector(int index, double[] v) {
+        try {
+            System.arraycopy(v, 0, data, index, v.length);
+        } catch (IndexOutOfBoundsException e) {
+            checkIndex(index);
+            checkIndex(index + v.length - 1);
+        }
+    }
+
+    /**
+     * Set a set of consecutive elements.
+     *
+     * @param index index of first element to be set.
+     * @param v vector containing the values to set.
+     * @exception MatrixIndexException if the index is
+     * inconsistent with vector size
+     */
+    public void set(int index, ArrayRealVector v)
+        throws MatrixIndexException {
+        setSubVector(index, v.data);
+    }
+
+    /** {@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
+     * @exception IllegalArgumentException if the vectors do not
+     * have the same dimension
+     */
+    @Override
+    protected void checkVectorDimensions(RealVector v)
+        throws IllegalArgumentException {
+        checkVectorDimensions(v.getDimension());
+    }
+
+    /**
+     * Check if instance dimension is equal to some expected value.
+     *
+     * @param n expected dimension.
+     * @exception IllegalArgumentException if the dimension is
+     * inconsistent with vector size
+     */
+    @Override
+    protected void checkVectorDimensions(int n)
+        throws IllegalArgumentException {
+        if (data.length != n) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.VECTOR_LENGTH_MISMATCH,
+                    data.length, n);
+        }
+    }
+
+    /**
+     * 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() {
+        for (double v : data) {
+            if (Double.isNaN(v)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * 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() {
+
+        if (isNaN()) {
+            return false;
+        }
+
+        for (double v : data) {
+            if (Double.isInfinite(v)) {
+                return true;
+            }
+        }
+
+        return false;
+
+    }
+
+    /**
+     * Test for the equality of two real vectors.
+     * <p>
+     * If all coordinates of two real vectors are exactly the same, and none are
+     * <code>Double.NaN</code>, the two real 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
+     * real vector are equal to <code>Double.NaN</code>, the real vector is equal to
+     * a vector with all <code>Double.NaN</code> coordinates.
+     * </p>
+     *
+     * @param other Object to test for equality to this
+     * @return true if two vector objects are equal, false if
+     *         object is null, not an instance of RealVector, or
+     *         not equal to this RealVector instance
+     *
+     */
+    @Override
+    public boolean equals(Object other) {
+
+      if (this == other) {
+        return true;
+      }
+
+      if (other == null || !(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;
+    }
+
+    /**
+     * Get a hashCode for the real 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 9;
+        }
+        return MathUtils.hash(data);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/BiDiagonalTransformer.java b/src/main/java/org/apache/commons/math/linear/BiDiagonalTransformer.java
new file mode 100644
index 0000000..78230de
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/BiDiagonalTransformer.java
@@ -0,0 +1,381 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.linear;
+
+import org.apache.commons.math.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>
+ * <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.</p>
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ * @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.
+     */
+    public 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.</p>
+     * @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;
+            cachedU = MatrixUtils.createRealMatrix(m, m);
+
+            // fill up the part of the matrix not affected by Householder transforms
+            for (int k = m - 1; k >= p; --k) {
+                cachedU.setEntry(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];
+                cachedU.setEntry(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 -= cachedU.getEntry(i, j) * householderVectors[i][k - diagOffset];
+                        }
+                        alpha /= diagonal[k - diagOffset] * hK[k - diagOffset];
+
+                        for (int i = k; i < m; ++i) {
+                            cachedU.addToEntry(i, j, -alpha * householderVectors[i][k - diagOffset]);
+                        }
+                    }
+                }
+            }
+            if (diagOffset > 0) {
+                cachedU.setEntry(0, 0, 1);
+            }
+
+        }
+
+        // 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;
+            cachedB = MatrixUtils.createRealMatrix(m, n);
+            for (int i = 0; i < main.length; ++i) {
+                cachedB.setEntry(i, i, main[i]);
+                if (m < n) {
+                    if (i > 0) {
+                        cachedB.setEntry(i, i - 1, secondary[i - 1]);
+                    }
+                } else {
+                    if (i < main.length - 1) {
+                        cachedB.setEntry(i, i + 1, secondary[i]);
+                    }
+                }
+            }
+
+        }
+
+        // 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.</p>
+     * @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;
+            cachedV = MatrixUtils.createRealMatrix(n, n);
+
+            // fill up the part of the matrix not affected by Householder transforms
+            for (int k = n - 1; k >= p; --k) {
+                cachedV.setEntry(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];
+                cachedV.setEntry(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 -= cachedV.getEntry(i, j) * hK[i];
+                        }
+                        beta /= diagonal[k - diagOffset] * hK[k];
+
+                        for (int i = k; i < n; ++i) {
+                            cachedV.addToEntry(i, j, -beta * hK[i]);
+                        }
+                    }
+                }
+            }
+            if (diagOffset > 0) {
+                cachedV.setEntry(0, 0, 1);
+            }
+
+        }
+
+        // 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.</p>
+     * @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.</p>
+     * @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.</p>
+     * @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.</p>
+     */
+    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.</p>
+     */
+    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/math/linear/BigMatrix.java b/src/main/java/org/apache/commons/math/linear/BigMatrix.java
new file mode 100644
index 0000000..5ea3d3e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/BigMatrix.java
@@ -0,0 +1,331 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.linear;
+
+import java.math.BigDecimal;
+
+/**
+ * Interface defining a real-valued matrix with basic algebraic operations, using
+ * BigDecimal representations for the entries.
+ * <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.</p>
+ *
+ * @version $Revision: 811786 $ $Date: 2009-09-06 11:36:08 +0200 (dim. 06 sept. 2009) $
+ * @deprecated as of 2.0, replaced by {@link FieldMatrix} with a {@link
+ * org.apache.commons.math.util.BigReal} parameter
+ */
+@Deprecated
+public interface BigMatrix extends AnyMatrix {
+
+    /**
+     * Returns a (deep) copy of this.
+     *
+     * @return matrix copy
+     */
+    BigMatrix copy();
+
+    /**
+     * Compute the sum of this and m.
+     *
+     * @param m    matrix to be added
+     * @return     this + m
+     * @exception  IllegalArgumentException if m is not the same size as this
+     */
+    BigMatrix add(BigMatrix m) throws IllegalArgumentException;
+
+    /**
+     * Compute this minus m.
+     *
+     * @param m    matrix to be subtracted
+     * @return     this + m
+     * @exception  IllegalArgumentException if m is not the same size as this
+     */
+    BigMatrix subtract(BigMatrix m) throws IllegalArgumentException;
+
+     /**
+     * Returns the result of adding d to each entry of this.
+     *
+     * @param d    value to be added to each entry
+     * @return     d + this
+     */
+    BigMatrix scalarAdd(BigDecimal d);
+
+    /**
+     * Returns the result multiplying each entry of this by d.
+     *
+     * @param d    value to multiply all entries by
+     * @return     d * this
+     */
+    BigMatrix scalarMultiply(BigDecimal d);
+
+    /**
+     * Returns the result of postmultiplying this by m.
+     *
+     * @param m    matrix to postmultiply by
+     * @return     this * m
+     * @throws     IllegalArgumentException
+     *             if columnDimension(this) != rowDimension(m)
+     */
+    BigMatrix multiply(BigMatrix m) throws IllegalArgumentException;
+
+    /**
+     * Returns the result premultiplying this by <code>m</code>.
+     * @param m    matrix to premultiply by
+     * @return     m * this
+     * @throws     IllegalArgumentException
+     *             if rowDimension(this) != columnDimension(m)
+     */
+    BigMatrix preMultiply(BigMatrix m) throws IllegalArgumentException;
+
+    /**
+     * Returns matrix entries as a two-dimensional array.
+     *
+     * @return    2-dimensional array of entries
+     */
+    BigDecimal[][] getData();
+
+    /**
+     * Returns matrix entries as a two-dimensional array.
+     *
+     * @return    2-dimensional array of entries
+     */
+    double [][] getDataAsDoubleArray();
+
+    /***
+     * Gets the rounding mode
+     * @return the rounding mode
+     */
+    int getRoundingMode();
+
+    /**
+     * Returns the <a href="http://mathworld.wolfram.com/MaximumAbsoluteRowSumNorm.html">
+     * maximum absolute row sum norm</a> of the matrix.
+     *
+     * @return norm
+     */
+    BigDecimal getNorm();
+
+    /**
+     * Gets a submatrix. 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
+     * @return The subMatrix containing the data of the
+     *         specified rows and columns
+     * @exception MatrixIndexException  if the indices are not valid
+     */
+    BigMatrix getSubMatrix(int startRow, int endRow, int startColumn,
+            int endColumn) throws MatrixIndexException;
+
+    /**
+     * 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
+     * @exception MatrixIndexException if row or column selections are not valid
+     */
+    BigMatrix getSubMatrix(int[] selectedRows, int[] selectedColumns)
+    throws MatrixIndexException;
+
+    /**
+     * Returns the entries in row number <code>row</code>
+     * as a row matrix.  Row indices start at 0.
+     *
+     * @param row the row to be fetched
+     * @return row matrix
+     * @throws MatrixIndexException if the specified row index is invalid
+     */
+    BigMatrix getRowMatrix(int row) throws MatrixIndexException;
+
+    /**
+     * Returns the entries in column number <code>column</code>
+     * as a column matrix.  Column indices start at 0.
+     *
+     * @param column the column to be fetched
+     * @return column matrix
+     * @throws MatrixIndexException if the specified column index is invalid
+     */
+    BigMatrix getColumnMatrix(int column) throws MatrixIndexException;
+
+    /**
+     * Returns the entries in row number <code>row</code> as an array.
+     * <p>
+     * Row indices start at 0.  A <code>MatrixIndexException</code> is thrown
+     * unless <code>0 <= row < rowDimension.</code></p>
+     *
+     * @param row the row to be fetched
+     * @return array of entries in the row
+     * @throws MatrixIndexException if the specified row index is not valid
+     */
+    BigDecimal[] getRow(int row) throws MatrixIndexException;
+
+    /**
+     * Returns the entries in row number <code>row</code> as an array
+     * of double values.
+     * <p>
+     * Row indices start at 0.  A <code>MatrixIndexException</code> is thrown
+     * unless <code>0 <= row < rowDimension.</code></p>
+     *
+     * @param row the row to be fetched
+     * @return array of entries in the row
+     * @throws MatrixIndexException if the specified row index is not valid
+     */
+    double [] getRowAsDoubleArray(int row) throws MatrixIndexException;
+
+    /**
+     * Returns the entries in column number <code>col</code> as an array.
+     * <p>
+     * Column indices start at 0.  A <code>MatrixIndexException</code> is thrown
+     * unless <code>0 <= column < columnDimension.</code></p>
+     *
+     * @param col the column to be fetched
+     * @return array of entries in the column
+     * @throws MatrixIndexException if the specified column index is not valid
+     */
+    BigDecimal[] getColumn(int col) throws MatrixIndexException;
+
+    /**
+     * Returns the entries in column number <code>col</code> as an array
+     * of double values.
+     * <p>
+     * Column indices start at 0.  A <code>MatrixIndexException</code> is thrown
+     * unless <code>0 <= column < columnDimension.</code></p>
+     *
+     * @param col the column to be fetched
+     * @return array of entries in the column
+     * @throws MatrixIndexException if the specified column index is not valid
+     */
+    double [] getColumnAsDoubleArray(int col) throws MatrixIndexException;
+
+    /**
+     * Returns the entry in the specified row and column.
+     * <p>
+     * Row and column indices start at 0 and must satisfy
+     * <ul>
+     * <li><code>0 <= row < rowDimension</code></li>
+     * <li><code> 0 <= column < columnDimension</code></li>
+     * </ul>
+     * otherwise a <code>MatrixIndexException</code> is thrown.</p>
+     *
+     * @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 MatrixIndexException if the row or column index is not valid
+     */
+    BigDecimal getEntry(int row, int column) throws MatrixIndexException;
+
+    /**
+     * Returns the entry in the specified row and column as a double.
+     * <p>
+     * Row and column indices start at 0 and must satisfy
+     * <ul>
+     * <li><code>0 <= row < rowDimension</code></li>
+     * <li><code> 0 <= column < columnDimension</code></li>
+     * </ul>
+     * otherwise a <code>MatrixIndexException</code> is thrown.</p>
+     *
+     * @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 MatrixIndexException if the row or column index is not valid
+     */
+    double getEntryAsDouble(int row, int column) throws MatrixIndexException;
+
+    /**
+     * Returns the transpose of this matrix.
+     *
+     * @return transpose matrix
+     */
+    BigMatrix transpose();
+
+    /**
+     * Returns the inverse of this matrix.
+     *
+     * @return inverse matrix
+     * @throws org.apache.commons.math.linear.InvalidMatrixException if
+     *     this is not invertible
+     */
+    BigMatrix inverse() throws InvalidMatrixException;
+
+    /**
+     * Returns the determinant of this matrix.
+     *
+     * @return determinant
+      *@throws org.apache.commons.math.linear.InvalidMatrixException if
+      *    matrix is not square
+     */
+    BigDecimal getDeterminant() throws InvalidMatrixException;
+
+    /**
+     * 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
+     */
+    BigDecimal getTrace();
+
+    /**
+     * Returns the result of multiplying this by the vector <code>v</code>.
+     *
+     * @param v the vector to operate on
+     * @return this*v
+     * @throws IllegalArgumentException if columnDimension != v.size()
+     */
+    BigDecimal[] operate(BigDecimal[] v) throws IllegalArgumentException;
+
+    /**
+     * Returns the (row) vector result of premultiplying this by the vector <code>v</code>.
+     *
+     * @param v the row vector to premultiply by
+     * @return v*this
+     * @throws IllegalArgumentException if rowDimension != v.size()
+     */
+    BigDecimal[] preMultiply(BigDecimal[] v) throws IllegalArgumentException;
+
+    /**
+     * Returns the solution vector for a linear system with coefficient
+     * matrix = this and constant vector = <code>b</code>.
+     *
+     * @param b  constant vector
+     * @return vector of solution values to AX = b, where A is *this
+     * @throws IllegalArgumentException if this.rowDimension != b.length
+     * @throws org.apache.commons.math.linear.InvalidMatrixException if this matrix is not square or is singular
+     */
+    BigDecimal[] solve(BigDecimal[] b) throws IllegalArgumentException, InvalidMatrixException;
+
+    /**
+     * Returns a matrix of (column) solution vectors for linear systems with
+     * coefficient matrix = this and constant vectors = columns of
+     * <code>b</code>.
+     *
+     * @param b  matrix of constant vectors forming RHS of linear systems to
+     * to solve
+     * @return matrix of solution vectors
+     * @throws IllegalArgumentException if this.rowDimension != row dimension
+     * @throws org.apache.commons.math.linear.InvalidMatrixException if this matrix is not square or is singular
+     */
+    BigMatrix solve(BigMatrix b) throws IllegalArgumentException, InvalidMatrixException;
+}
+
diff --git a/src/main/java/org/apache/commons/math/linear/BigMatrixImpl.java b/src/main/java/org/apache/commons/math/linear/BigMatrixImpl.java
new file mode 100644
index 0000000..80643a0
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/BigMatrixImpl.java
@@ -0,0 +1,1505 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.linear;
+import java.io.Serializable;
+import java.math.BigDecimal;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * Implementation of {@link BigMatrix} using a BigDecimal[][] array to store entries
+ * and <a href="http://www.math.gatech.edu/~bourbaki/math2601/Web-notes/2num.pdf">
+ * LU decompostion</a> to support linear system
+ * solution and inverse.
+ * <p>
+ * The LU decompostion is performed as needed, to support the following operations: <ul>
+ * <li>solve</li>
+ * <li>isSingular</li>
+ * <li>getDeterminant</li>
+ * <li>inverse</li> </ul></p>
+ * <p>
+* <strong>Usage notes</strong>:<br>
+ * <ul><li>
+ * The LU decomposition is stored and reused on subsequent calls.  If matrix
+ * data are modified using any of the public setXxx methods, the saved
+ * decomposition is discarded.  If data are modified via references to the
+ * underlying array obtained using <code>getDataRef()</code>, then the stored
+ * LU decomposition will not be discarded.  In this case, you need to
+ * explicitly invoke <code>LUDecompose()</code> to recompute the decomposition
+ * before using any of the methods above.</li>
+ * <li>
+ * As specified in the {@link BigMatrix} 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.</li></ul></p>
+ *
+ * @deprecated as of 2.0, replaced by {@link Array2DRowFieldMatrix} with a {@link
+ * org.apache.commons.math.util.BigReal} parameter
+ * @version $Revision: 1042376 $ $Date: 2010-12-05 16:54:55 +0100 (dim. 05 déc. 2010) $
+ */
+@Deprecated
+public class BigMatrixImpl implements BigMatrix, Serializable {
+
+    /** BigDecimal 0 */
+    static final BigDecimal ZERO = new BigDecimal(0);
+
+    /** BigDecimal 1 */
+    static final BigDecimal ONE = new BigDecimal(1);
+
+    /** Bound to determine effective singularity in LU decomposition */
+    private static final BigDecimal TOO_SMALL = new BigDecimal(10E-12);
+
+    /** Serialization id */
+    private static final long serialVersionUID = -1011428905656140431L;
+
+    /** Entries of the matrix */
+    protected BigDecimal data[][] = null;
+
+    /** Entries of cached LU decomposition.
+     *  All updates to data (other than luDecompose()) *must* set this to null
+     */
+    protected BigDecimal lu[][] = null;
+
+    /** Permutation associated with LU decomposition */
+    protected int[] permutation = null;
+
+    /** Parity of the permutation associated with the LU decomposition */
+    protected int parity = 1;
+
+    /** Rounding mode for divisions **/
+    private int roundingMode = BigDecimal.ROUND_HALF_UP;
+
+    /*** BigDecimal scale ***/
+    private int scale = 64;
+
+    /**
+     * Creates a matrix with no data
+     */
+    public BigMatrixImpl() {
+    }
+
+    /**
+     * Create a new BigMatrix 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 IllegalArgumentException if row or column dimension is not
+     *  positive
+     */
+    public BigMatrixImpl(int rowDimension, int columnDimension) {
+        if (rowDimension < 1 ) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.INSUFFICIENT_DIMENSION, rowDimension, 1);
+        }
+        if (columnDimension < 1) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.INSUFFICIENT_DIMENSION, columnDimension, 1);
+        }
+        data = new BigDecimal[rowDimension][columnDimension];
+        lu = null;
+    }
+
+    /**
+     * Create a new BigMatrix using <code>d</code> as the underlying
+     * data array.
+     * <p>The input array is copied, not referenced. This constructor has
+     * the same effect as calling {@link #BigMatrixImpl(BigDecimal[][], boolean)}
+     * with the second argument set to <code>true</code>.</p>
+     *
+     * @param d data for new matrix
+     * @throws IllegalArgumentException if <code>d</code> is not rectangular
+     *  (not all rows have the same length) or empty
+     * @throws NullPointerException if <code>d</code> is null
+     */
+    public BigMatrixImpl(BigDecimal[][] d) {
+        this.copyIn(d);
+        lu = null;
+    }
+
+    /**
+     * Create a new BigMatrix using the input array as the underlying
+     * data array.
+     * <p>If an array is built specially in order to be embedded in a
+     * BigMatrix and not used directly, the <code>copyArray</code> may be
+     * set to <code>false</code. This will prevent the copying and improve
+     * performance as no new array will be built and no data will be copied.</p>
+     * @param d data for new matrix
+     * @param copyArray if true, the input array will be copied, otherwise
+     * it will be referenced
+     * @throws IllegalArgumentException if <code>d</code> is not rectangular
+     *  (not all rows have the same length) or empty
+     * @throws NullPointerException if <code>d</code> is null
+     * @see #BigMatrixImpl(BigDecimal[][])
+     */
+    public BigMatrixImpl(BigDecimal[][] d, boolean copyArray) {
+        if (copyArray) {
+            copyIn(d);
+        } else {
+            if (d == null) {
+                throw new NullPointerException();
+            }
+            final int nRows = d.length;
+            if (nRows == 0) {
+                throw MathRuntimeException.createIllegalArgumentException(LocalizedFormats.AT_LEAST_ONE_ROW);
+            }
+
+            final int nCols = d[0].length;
+            if (nCols == 0) {
+                throw MathRuntimeException.createIllegalArgumentException(LocalizedFormats.AT_LEAST_ONE_COLUMN);
+            }
+            for (int r = 1; r < nRows; r++) {
+                if (d[r].length != nCols) {
+                    throw MathRuntimeException.createIllegalArgumentException(
+                          LocalizedFormats.DIFFERENT_ROWS_LENGTHS,
+                          nCols, d[r].length);
+                }
+            }
+            data = d;
+        }
+        lu = null;
+    }
+
+    /**
+     * Create a new BigMatrix using <code>d</code> as the underlying
+     * data array.
+     * <p>Since the underlying array will hold <code>BigDecimal</code>
+     * instances, it will be created.</p>
+     *
+     * @param d data for new matrix
+     * @throws IllegalArgumentException if <code>d</code> is not rectangular
+     *  (not all rows have the same length) or empty
+     * @throws NullPointerException if <code>d</code> is null
+     */
+    public BigMatrixImpl(double[][] d) {
+        final int nRows = d.length;
+        if (nRows == 0) {
+            throw MathRuntimeException.createIllegalArgumentException(LocalizedFormats.AT_LEAST_ONE_ROW);
+        }
+
+        final int nCols = d[0].length;
+        if (nCols == 0) {
+            throw MathRuntimeException.createIllegalArgumentException(LocalizedFormats.AT_LEAST_ONE_COLUMN);
+        }
+        for (int row = 1; row < nRows; row++) {
+            if (d[row].length != nCols) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                      LocalizedFormats.DIFFERENT_ROWS_LENGTHS,
+                      nCols, d[row].length);
+            }
+        }
+        this.copyIn(d);
+        lu = null;
+    }
+
+    /**
+     * Create a new BigMatrix using the values represented by the strings in
+     * <code>d</code> as the underlying data array.
+     *
+     * @param d data for new matrix
+     * @throws IllegalArgumentException if <code>d</code> is not rectangular
+     *  (not all rows have the same length) or empty
+     * @throws NullPointerException if <code>d</code> is null
+     */
+    public BigMatrixImpl(String[][] d) {
+        final int nRows = d.length;
+        if (nRows == 0) {
+            throw MathRuntimeException.createIllegalArgumentException(LocalizedFormats.AT_LEAST_ONE_ROW);
+        }
+
+        final int nCols = d[0].length;
+        if (nCols == 0) {
+            throw MathRuntimeException.createIllegalArgumentException(LocalizedFormats.AT_LEAST_ONE_COLUMN);
+        }
+        for (int row = 1; row < nRows; row++) {
+            if (d[row].length != nCols) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                      LocalizedFormats.DIFFERENT_ROWS_LENGTHS,
+                      nCols, d[row].length);
+            }
+        }
+        this.copyIn(d);
+        lu = null;
+    }
+
+    /**
+     * Create a new (column) BigMatrix using <code>v</code> as the
+     * data for the unique column of the <code>v.length x 1</code> matrix
+     * created.
+     * <p>
+     * The input array is copied, not referenced.</p>
+     *
+     * @param v column vector holding data for new matrix
+     */
+    public BigMatrixImpl(BigDecimal[] v) {
+        final int nRows = v.length;
+        data = new BigDecimal[nRows][1];
+        for (int row = 0; row < nRows; row++) {
+            data[row][0] = v[row];
+        }
+    }
+
+    /**
+     * Create a new BigMatrix which is a copy of this.
+     *
+     * @return  the cloned matrix
+     */
+    public BigMatrix copy() {
+        return new BigMatrixImpl(this.copyOut(), false);
+    }
+
+    /**
+     * Compute the sum of this and <code>m</code>.
+     *
+     * @param m    matrix to be added
+     * @return     this + m
+     * @throws  IllegalArgumentException if m is not the same size as this
+     */
+    public BigMatrix add(BigMatrix m) throws IllegalArgumentException {
+        try {
+            return add((BigMatrixImpl) m);
+        } catch (ClassCastException cce) {
+
+            // safety check
+            MatrixUtils.checkAdditionCompatible(this, m);
+
+            final int rowCount    = getRowDimension();
+            final int columnCount = getColumnDimension();
+            final BigDecimal[][] outData = new BigDecimal[rowCount][columnCount];
+            for (int row = 0; row < rowCount; row++) {
+                final BigDecimal[] dataRow    = data[row];
+                final BigDecimal[] outDataRow = outData[row];
+                for (int col = 0; col < columnCount; col++) {
+                    outDataRow[col] = dataRow[col].add(m.getEntry(row, col));
+                }
+            }
+            return new BigMatrixImpl(outData, false);
+        }
+    }
+
+    /**
+     * Compute the sum of this and <code>m</code>.
+     *
+     * @param m    matrix to be added
+     * @return     this + m
+     * @throws  IllegalArgumentException if m is not the same size as this
+     */
+    public BigMatrixImpl add(BigMatrixImpl m) throws IllegalArgumentException {
+
+        // safety check
+        MatrixUtils.checkAdditionCompatible(this, m);
+
+        final int rowCount    = getRowDimension();
+        final int columnCount = getColumnDimension();
+        final BigDecimal[][] outData = new BigDecimal[rowCount][columnCount];
+        for (int row = 0; row < rowCount; row++) {
+            final BigDecimal[] dataRow    = data[row];
+            final BigDecimal[] mRow       = m.data[row];
+            final BigDecimal[] outDataRow = outData[row];
+            for (int col = 0; col < columnCount; col++) {
+                outDataRow[col] = dataRow[col].add(mRow[col]);
+            }
+        }
+        return new BigMatrixImpl(outData, false);
+    }
+
+    /**
+     * Compute  this minus <code>m</code>.
+     *
+     * @param m    matrix to be subtracted
+     * @return     this + m
+     * @throws  IllegalArgumentException if m is not the same size as this
+     */
+    public BigMatrix subtract(BigMatrix m) throws IllegalArgumentException {
+        try {
+            return subtract((BigMatrixImpl) m);
+        } catch (ClassCastException cce) {
+
+            // safety check
+            MatrixUtils.checkSubtractionCompatible(this, m);
+
+            final int rowCount    = getRowDimension();
+            final int columnCount = getColumnDimension();
+            final BigDecimal[][] outData = new BigDecimal[rowCount][columnCount];
+            for (int row = 0; row < rowCount; row++) {
+                final BigDecimal[] dataRow    = data[row];
+                final BigDecimal[] outDataRow = outData[row];
+                for (int col = 0; col < columnCount; col++) {
+                    outDataRow[col] = dataRow[col].subtract(getEntry(row, col));
+                }
+            }
+            return new BigMatrixImpl(outData, false);
+        }
+    }
+
+    /**
+     * Compute  this minus <code>m</code>.
+     *
+     * @param m    matrix to be subtracted
+     * @return     this + m
+     * @throws  IllegalArgumentException if m is not the same size as this
+     */
+    public BigMatrixImpl subtract(BigMatrixImpl m) throws IllegalArgumentException {
+
+        // safety check
+        MatrixUtils.checkSubtractionCompatible(this, m);
+
+        final int rowCount    = getRowDimension();
+        final int columnCount = getColumnDimension();
+        final BigDecimal[][] outData = new BigDecimal[rowCount][columnCount];
+        for (int row = 0; row < rowCount; row++) {
+            final BigDecimal[] dataRow    = data[row];
+            final BigDecimal[] mRow       = m.data[row];
+            final BigDecimal[] outDataRow = outData[row];
+            for (int col = 0; col < columnCount; col++) {
+                outDataRow[col] = dataRow[col].subtract(mRow[col]);
+            }
+        }
+        return new BigMatrixImpl(outData, false);
+    }
+
+    /**
+     * Returns the result of adding d to each entry of this.
+     *
+     * @param d    value to be added to each entry
+     * @return     d + this
+     */
+    public BigMatrix scalarAdd(BigDecimal d) {
+        final int rowCount    = getRowDimension();
+        final int columnCount = getColumnDimension();
+        final BigDecimal[][] outData = new BigDecimal[rowCount][columnCount];
+        for (int row = 0; row < rowCount; row++) {
+            final BigDecimal[] dataRow    = data[row];
+            final BigDecimal[] outDataRow = outData[row];
+            for (int col = 0; col < columnCount; col++) {
+                outDataRow[col] = dataRow[col].add(d);
+            }
+        }
+        return new BigMatrixImpl(outData, false);
+    }
+
+    /**
+     * Returns the result of multiplying each entry of this by <code>d</code>
+     * @param d  value to multiply all entries by
+     * @return d * this
+     */
+    public BigMatrix scalarMultiply(BigDecimal d) {
+        final int rowCount    = getRowDimension();
+        final int columnCount = getColumnDimension();
+        final BigDecimal[][] outData = new BigDecimal[rowCount][columnCount];
+        for (int row = 0; row < rowCount; row++) {
+            final BigDecimal[] dataRow    = data[row];
+            final BigDecimal[] outDataRow = outData[row];
+            for (int col = 0; col < columnCount; col++) {
+                outDataRow[col] = dataRow[col].multiply(d);
+            }
+        }
+        return new BigMatrixImpl(outData, false);
+    }
+
+    /**
+     * Returns the result of postmultiplying this by <code>m</code>.
+     * @param m    matrix to postmultiply by
+     * @return     this*m
+     * @throws     IllegalArgumentException
+     *             if columnDimension(this) != rowDimension(m)
+     */
+    public BigMatrix multiply(BigMatrix m) throws IllegalArgumentException {
+        try {
+            return multiply((BigMatrixImpl) m);
+        } catch (ClassCastException cce) {
+
+            // safety check
+            MatrixUtils.checkMultiplicationCompatible(this, m);
+
+            final int nRows = this.getRowDimension();
+            final int nCols = m.getColumnDimension();
+            final int nSum = this.getColumnDimension();
+            final BigDecimal[][] outData = new BigDecimal[nRows][nCols];
+            for (int row = 0; row < nRows; row++) {
+                final BigDecimal[] dataRow    = data[row];
+                final BigDecimal[] outDataRow = outData[row];
+                for (int col = 0; col < nCols; col++) {
+                    BigDecimal sum = ZERO;
+                    for (int i = 0; i < nSum; i++) {
+                        sum = sum.add(dataRow[i].multiply(m.getEntry(i, col)));
+                    }
+                    outDataRow[col] = sum;
+                }
+            }
+            return new BigMatrixImpl(outData, false);
+        }
+    }
+
+    /**
+     * Returns the result of postmultiplying this by <code>m</code>.
+     * @param m    matrix to postmultiply by
+     * @return     this*m
+     * @throws     IllegalArgumentException
+     *             if columnDimension(this) != rowDimension(m)
+     */
+    public BigMatrixImpl multiply(BigMatrixImpl m) throws IllegalArgumentException {
+
+        // safety check
+        MatrixUtils.checkMultiplicationCompatible(this, m);
+
+        final int nRows = this.getRowDimension();
+        final int nCols = m.getColumnDimension();
+        final int nSum = this.getColumnDimension();
+        final BigDecimal[][] outData = new BigDecimal[nRows][nCols];
+        for (int row = 0; row < nRows; row++) {
+            final BigDecimal[] dataRow    = data[row];
+            final BigDecimal[] outDataRow = outData[row];
+            for (int col = 0; col < nCols; col++) {
+                BigDecimal sum = ZERO;
+                for (int i = 0; i < nSum; i++) {
+                    sum = sum.add(dataRow[i].multiply(m.data[i][col]));
+                }
+                outDataRow[col] = sum;
+            }
+        }
+        return new BigMatrixImpl(outData, false);
+    }
+
+    /**
+     * Returns the result premultiplying this by <code>m</code>.
+     * @param m    matrix to premultiply by
+     * @return     m * this
+     * @throws     IllegalArgumentException
+     *             if rowDimension(this) != columnDimension(m)
+     */
+    public BigMatrix preMultiply(BigMatrix m) throws IllegalArgumentException {
+        return m.multiply(this);
+    }
+
+    /**
+     * Returns matrix entries as a two-dimensional array.
+     * <p>
+     * Makes a fresh copy of the underlying data.</p>
+     *
+     * @return    2-dimensional array of entries
+     */
+    public BigDecimal[][] getData() {
+        return copyOut();
+    }
+
+    /**
+     * Returns matrix entries as a two-dimensional array.
+     * <p>
+     * Makes a fresh copy of the underlying data converted to
+     * <code>double</code> values.</p>
+     *
+     * @return    2-dimensional array of entries
+     */
+    public double[][] getDataAsDoubleArray() {
+        final int nRows = getRowDimension();
+        final int nCols = getColumnDimension();
+        final double d[][] = new double[nRows][nCols];
+        for (int i = 0; i < nRows; i++) {
+            for (int j = 0; j < nCols; j++) {
+                d[i][j] = data[i][j].doubleValue();
+            }
+        }
+        return d;
+    }
+
+    /**
+     * Returns a reference to the underlying data array.
+     * <p>
+     * Does not make a fresh copy of the underlying data.</p>
+     *
+     * @return 2-dimensional array of entries
+     */
+    public BigDecimal[][] getDataRef() {
+        return data;
+    }
+
+    /***
+     * Gets the rounding mode for division operations
+     * The default is {@link java.math.BigDecimal#ROUND_HALF_UP}
+     * @see BigDecimal
+     * @return the rounding mode.
+     */
+    public int getRoundingMode() {
+        return roundingMode;
+    }
+
+    /***
+     * Sets the rounding mode for decimal divisions.
+     * @see BigDecimal
+     * @param roundingMode rounding mode for decimal divisions
+     */
+    public void setRoundingMode(int roundingMode) {
+        this.roundingMode = roundingMode;
+    }
+
+    /***
+     * Sets the scale for division operations.
+     * The default is 64
+     * @see BigDecimal
+     * @return the scale
+     */
+    public int getScale() {
+        return scale;
+    }
+
+    /***
+     * Sets the scale for division operations.
+     * @see BigDecimal
+     * @param scale scale for division operations
+     */
+    public void setScale(int scale) {
+        this.scale = scale;
+    }
+
+    /**
+     * Returns the <a href="http://mathworld.wolfram.com/MaximumAbsoluteRowSumNorm.html">
+     * maximum absolute row sum norm</a> of the matrix.
+     *
+     * @return norm
+     */
+    public BigDecimal getNorm() {
+        BigDecimal maxColSum = ZERO;
+        for (int col = 0; col < this.getColumnDimension(); col++) {
+            BigDecimal sum = ZERO;
+            for (int row = 0; row < this.getRowDimension(); row++) {
+                sum = sum.add(data[row][col].abs());
+            }
+            maxColSum = maxColSum.max(sum);
+        }
+        return maxColSum;
+    }
+
+    /**
+     * Gets a submatrix. 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
+     * @return The subMatrix containing the data of the
+     *         specified rows and columns
+     * @exception MatrixIndexException if row or column selections are not valid
+     */
+    public BigMatrix getSubMatrix(int startRow, int endRow,
+                                  int startColumn, int endColumn)
+        throws MatrixIndexException {
+
+        MatrixUtils.checkRowIndex(this, startRow);
+        MatrixUtils.checkRowIndex(this, endRow);
+        if (startRow > endRow) {
+            throw new MatrixIndexException(LocalizedFormats.INITIAL_ROW_AFTER_FINAL_ROW,
+                                           startRow, endRow);
+        }
+
+        MatrixUtils.checkColumnIndex(this, startColumn);
+        MatrixUtils.checkColumnIndex(this, endColumn);
+        if (startColumn > endColumn) {
+            throw new MatrixIndexException(LocalizedFormats.INITIAL_COLUMN_AFTER_FINAL_COLUMN,
+                                           startColumn, endColumn);
+        }
+
+        final BigDecimal[][] subMatrixData =
+            new BigDecimal[endRow - startRow + 1][endColumn - startColumn + 1];
+        for (int i = startRow; i <= endRow; i++) {
+            System.arraycopy(data[i], startColumn,
+                             subMatrixData[i - startRow], 0,
+                             endColumn - startColumn + 1);
+        }
+
+        return new BigMatrixImpl(subMatrixData, false);
+
+    }
+
+    /**
+     * Gets a submatrix. Rows and columns are indicated
+     * counting from 0 to n-1.
+     *
+     * @param selectedRows Array of row indices must be non-empty
+     * @param selectedColumns Array of column indices must be non-empty
+     * @return The subMatrix containing the data in the
+     *     specified rows and columns
+     * @exception MatrixIndexException  if supplied row or column index arrays
+     *     are not valid
+     */
+    public BigMatrix getSubMatrix(int[] selectedRows, int[] selectedColumns)
+        throws MatrixIndexException {
+
+        if (selectedRows.length * selectedColumns.length == 0) {
+            if (selectedRows.length == 0) {
+                throw new MatrixIndexException(LocalizedFormats.EMPTY_SELECTED_ROW_INDEX_ARRAY);
+            }
+            throw new MatrixIndexException(LocalizedFormats.EMPTY_SELECTED_COLUMN_INDEX_ARRAY);
+        }
+
+        final BigDecimal[][] subMatrixData =
+            new BigDecimal[selectedRows.length][selectedColumns.length];
+        try  {
+            for (int i = 0; i < selectedRows.length; i++) {
+                final BigDecimal[] subI = subMatrixData[i];
+                final BigDecimal[] dataSelectedI = data[selectedRows[i]];
+                for (int j = 0; j < selectedColumns.length; j++) {
+                    subI[j] = dataSelectedI[selectedColumns[j]];
+                }
+            }
+        } catch (ArrayIndexOutOfBoundsException e) {
+            // we redo the loop with checks enabled
+            // in order to generate an appropriate message
+            for (final int row : selectedRows) {
+                MatrixUtils.checkRowIndex(this, row);
+            }
+            for (final int column : selectedColumns) {
+                MatrixUtils.checkColumnIndex(this, column);
+            }
+        }
+        return new BigMatrixImpl(subMatrixData, false);
+    }
+
+    /**
+     * Replace the submatrix starting at <code>row, column</code> using data in
+     * the input <code>subMatrix</code> 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></p>
+     *
+     * @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 MatrixIndexException  if subMatrix does not fit into this
+     *    matrix from element in (row, column)
+     * @throws IllegalArgumentException if <code>subMatrix</code> is not rectangular
+     *  (not all rows have the same length) or empty
+     * @throws NullPointerException if <code>subMatrix</code> is null
+     * @since 1.1
+     */
+    public void setSubMatrix(BigDecimal[][] subMatrix, int row, int column)
+    throws MatrixIndexException {
+
+        final int nRows = subMatrix.length;
+        if (nRows == 0) {
+            throw MathRuntimeException.createIllegalArgumentException(LocalizedFormats.AT_LEAST_ONE_ROW);
+        }
+
+        final int nCols = subMatrix[0].length;
+        if (nCols == 0) {
+            throw MathRuntimeException.createIllegalArgumentException(LocalizedFormats.AT_LEAST_ONE_COLUMN);
+        }
+
+        for (int r = 1; r < nRows; r++) {
+            if (subMatrix[r].length != nCols) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                      LocalizedFormats.DIFFERENT_ROWS_LENGTHS,
+                      nCols, subMatrix[r].length);
+            }
+        }
+
+        if (data == null) {
+            if (row > 0) {
+                throw MathRuntimeException.createIllegalStateException(
+                        LocalizedFormats.FIRST_ROWS_NOT_INITIALIZED_YET,
+                        row);
+            }
+            if (column > 0) {
+                throw MathRuntimeException.createIllegalStateException(
+                        LocalizedFormats.FIRST_COLUMNS_NOT_INITIALIZED_YET,
+                        column);
+            }
+            data = new BigDecimal[nRows][nCols];
+            System.arraycopy(subMatrix, 0, data, 0, subMatrix.length);
+        } else {
+            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++) {
+            System.arraycopy(subMatrix[i], 0, data[row + i], column, nCols);
+        }
+
+        lu = null;
+
+    }
+
+    /**
+     * Returns the entries in row number <code>row</code>
+     * as a row matrix.  Row indices start at 0.
+     *
+     * @param row the row to be fetched
+     * @return row matrix
+     * @throws MatrixIndexException if the specified row index is invalid
+     */
+    public BigMatrix getRowMatrix(int row) throws MatrixIndexException {
+        MatrixUtils.checkRowIndex(this, row);
+        final int ncols = this.getColumnDimension();
+        final BigDecimal[][] out = new BigDecimal[1][ncols];
+        System.arraycopy(data[row], 0, out[0], 0, ncols);
+        return new BigMatrixImpl(out, false);
+    }
+
+    /**
+     * Returns the entries in column number <code>column</code>
+     * as a column matrix.  Column indices start at 0.
+     *
+     * @param column the column to be fetched
+     * @return column matrix
+     * @throws MatrixIndexException if the specified column index is invalid
+     */
+    public BigMatrix getColumnMatrix(int column) throws MatrixIndexException {
+        MatrixUtils.checkColumnIndex(this, column);
+        final int nRows = this.getRowDimension();
+        final BigDecimal[][] out = new BigDecimal[nRows][1];
+        for (int row = 0; row < nRows; row++) {
+            out[row][0] = data[row][column];
+        }
+        return new BigMatrixImpl(out, false);
+    }
+
+    /**
+     * Returns the entries in row number <code>row</code> as an array.
+     * <p>
+     * Row indices start at 0.  A <code>MatrixIndexException</code> is thrown
+     * unless <code>0 <= row < rowDimension.</code></p>
+     *
+     * @param row the row to be fetched
+     * @return array of entries in the row
+     * @throws MatrixIndexException if the specified row index is not valid
+     */
+    public BigDecimal[] getRow(int row) throws MatrixIndexException {
+        MatrixUtils.checkRowIndex(this, row);
+        final int ncols = this.getColumnDimension();
+        final BigDecimal[] out = new BigDecimal[ncols];
+        System.arraycopy(data[row], 0, out, 0, ncols);
+        return out;
+    }
+
+     /**
+     * Returns the entries in row number <code>row</code> as an array
+     * of double values.
+     * <p>
+     * Row indices start at 0.  A <code>MatrixIndexException</code> is thrown
+     * unless <code>0 <= row < rowDimension.</code></p>
+     *
+     * @param row the row to be fetched
+     * @return array of entries in the row
+     * @throws MatrixIndexException if the specified row index is not valid
+     */
+    public double[] getRowAsDoubleArray(int row) throws MatrixIndexException {
+        MatrixUtils.checkRowIndex(this, row);
+        final int ncols = this.getColumnDimension();
+        final double[] out = new double[ncols];
+        for (int i=0;i<ncols;i++) {
+            out[i] = data[row][i].doubleValue();
+        }
+        return out;
+    }
+
+     /**
+     * Returns the entries in column number <code>col</code> as an array.
+     * <p>
+     * Column indices start at 0.  A <code>MatrixIndexException</code> is thrown
+     * unless <code>0 <= column < columnDimension.</code></p>
+     *
+     * @param col the column to be fetched
+     * @return array of entries in the column
+     * @throws MatrixIndexException if the specified column index is not valid
+     */
+    public BigDecimal[] getColumn(int col) throws MatrixIndexException {
+        MatrixUtils.checkColumnIndex(this, col);
+        final int nRows = this.getRowDimension();
+        final BigDecimal[] out = new BigDecimal[nRows];
+        for (int i = 0; i < nRows; i++) {
+            out[i] = data[i][col];
+        }
+        return out;
+    }
+
+    /**
+     * Returns the entries in column number <code>col</code> as an array
+     * of double values.
+     * <p>
+     * Column indices start at 0.  A <code>MatrixIndexException</code> is thrown
+     * unless <code>0 <= column < columnDimension.</code></p>
+     *
+     * @param col the column to be fetched
+     * @return array of entries in the column
+     * @throws MatrixIndexException if the specified column index is not valid
+     */
+    public double[] getColumnAsDoubleArray(int col) throws MatrixIndexException {
+        MatrixUtils.checkColumnIndex(this, col);
+        final int nrows = this.getRowDimension();
+        final double[] out = new double[nrows];
+        for (int i=0;i<nrows;i++) {
+            out[i] = data[i][col].doubleValue();
+        }
+        return out;
+    }
+
+     /**
+     * Returns the entry in the specified row and column.
+     * <p>
+     * Row and column indices start at 0 and must satisfy
+     * <ul>
+     * <li><code>0 <= row < rowDimension</code></li>
+     * <li><code> 0 <= column < columnDimension</code></li>
+     * </ul>
+     * otherwise a <code>MatrixIndexException</code> is thrown.</p>
+     *
+     * @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 MatrixIndexException if the row or column index is not valid
+     */
+    public BigDecimal getEntry(int row, int column)
+    throws MatrixIndexException {
+        try {
+            return data[row][column];
+        } catch (ArrayIndexOutOfBoundsException e) {
+            throw new MatrixIndexException(
+                    LocalizedFormats.NO_SUCH_MATRIX_ENTRY,
+                    row, column, getRowDimension(), getColumnDimension());
+        }
+    }
+
+    /**
+     * Returns the entry in the specified row and column as a double.
+     * <p>
+     * Row and column indices start at 0 and must satisfy
+     * <ul>
+     * <li><code>0 <= row < rowDimension</code></li>
+     * <li><code> 0 <= column < columnDimension</code></li>
+     * </ul>
+     * otherwise a <code>MatrixIndexException</code> is thrown.</p>
+     *
+     * @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 MatrixIndexException if the row
+     * or column index is not valid
+     */
+    public double getEntryAsDouble(int row, int column) throws MatrixIndexException {
+        return getEntry(row,column).doubleValue();
+    }
+
+    /**
+     * Returns the transpose matrix.
+     *
+     * @return transpose matrix
+     */
+    public BigMatrix transpose() {
+        final int nRows = this.getRowDimension();
+        final int nCols = this.getColumnDimension();
+        final BigDecimal[][] outData = new BigDecimal[nCols][nRows];
+        for (int row = 0; row < nRows; row++) {
+            final BigDecimal[] dataRow = data[row];
+            for (int col = 0; col < nCols; col++) {
+                outData[col][row] = dataRow[col];
+            }
+        }
+        return new BigMatrixImpl(outData, false);
+    }
+
+    /**
+     * Returns the inverse matrix if this matrix is invertible.
+     *
+     * @return inverse matrix
+     * @throws InvalidMatrixException if this is not invertible
+     */
+    public BigMatrix inverse() throws InvalidMatrixException {
+        return solve(MatrixUtils.createBigIdentityMatrix(getRowDimension()));
+    }
+
+    /**
+     * Returns the determinant of this matrix.
+     *
+     * @return determinant
+     * @throws InvalidMatrixException if matrix is not square
+     */
+    public BigDecimal getDeterminant() throws InvalidMatrixException {
+        if (!isSquare()) {
+            throw new NonSquareMatrixException(getRowDimension(), getColumnDimension());
+        }
+        if (isSingular()) {   // note: this has side effect of attempting LU decomp if lu == null
+            return ZERO;
+        } else {
+            BigDecimal det = (parity == 1) ? ONE : ONE.negate();
+            for (int i = 0; i < getRowDimension(); i++) {
+                det = det.multiply(lu[i][i]);
+            }
+            return det;
+        }
+    }
+
+     /**
+     * Is this a square matrix?
+     * @return true if the matrix is square (rowDimension = columnDimension)
+     */
+    public boolean isSquare() {
+        return getColumnDimension() == getRowDimension();
+    }
+
+    /**
+     * Is this a singular matrix?
+     * @return true if the matrix is singular
+     */
+    public boolean isSingular() {
+        if (lu == null) {
+            try {
+                luDecompose();
+                return false;
+            } catch (InvalidMatrixException ex) {
+                return true;
+            }
+        } else { // LU decomp must have been successfully performed
+            return false; // so the matrix is not singular
+        }
+    }
+
+    /**
+     * Returns the number of rows in the matrix.
+     *
+     * @return rowDimension
+     */
+    public int getRowDimension() {
+        return data.length;
+    }
+
+    /**
+     * Returns the number of columns in the matrix.
+     *
+     * @return columnDimension
+     */
+    public int getColumnDimension() {
+        return data[0].length;
+    }
+
+     /**
+     * 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 IllegalArgumentException if this matrix is not square.
+     */
+    public BigDecimal getTrace() throws IllegalArgumentException {
+        if (!isSquare()) {
+            throw new NonSquareMatrixException(getRowDimension(), getColumnDimension());
+        }
+        BigDecimal trace = data[0][0];
+        for (int i = 1; i < this.getRowDimension(); i++) {
+            trace = trace.add(data[i][i]);
+        }
+        return trace;
+    }
+
+    /**
+     * Returns the result of multiplying this by the vector <code>v</code>.
+     *
+     * @param v the vector to operate on
+     * @return this*v
+     * @throws IllegalArgumentException if columnDimension != v.size()
+     */
+    public BigDecimal[] operate(BigDecimal[] v) throws IllegalArgumentException {
+        if (v.length != getColumnDimension()) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.VECTOR_LENGTH_MISMATCH,
+                    v.length, getColumnDimension() );
+        }
+        final int nRows = this.getRowDimension();
+        final int nCols = this.getColumnDimension();
+        final BigDecimal[] out = new BigDecimal[nRows];
+        for (int row = 0; row < nRows; row++) {
+            BigDecimal sum = ZERO;
+            for (int i = 0; i < nCols; i++) {
+                sum = sum.add(data[row][i].multiply(v[i]));
+            }
+            out[row] = sum;
+        }
+        return out;
+    }
+
+    /**
+     * Returns the result of multiplying this by the vector <code>v</code>.
+     *
+     * @param v the vector to operate on
+     * @return this*v
+     * @throws IllegalArgumentException if columnDimension != v.size()
+     */
+    public BigDecimal[] operate(double[] v) throws IllegalArgumentException {
+        final BigDecimal bd[] = new BigDecimal[v.length];
+        for (int i = 0; i < bd.length; i++) {
+            bd[i] = new BigDecimal(v[i]);
+        }
+        return operate(bd);
+    }
+
+    /**
+     * Returns the (row) vector result of premultiplying this by the vector <code>v</code>.
+     *
+     * @param v the row vector to premultiply by
+     * @return v*this
+     * @throws IllegalArgumentException if rowDimension != v.size()
+     */
+    public BigDecimal[] preMultiply(BigDecimal[] v) throws IllegalArgumentException {
+        final int nRows = this.getRowDimension();
+        if (v.length != nRows) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.VECTOR_LENGTH_MISMATCH,
+                    v.length, nRows );
+        }
+        final int nCols = this.getColumnDimension();
+        final BigDecimal[] out = new BigDecimal[nCols];
+        for (int col = 0; col < nCols; col++) {
+            BigDecimal sum = ZERO;
+            for (int i = 0; i < nRows; i++) {
+                sum = sum.add(data[i][col].multiply(v[i]));
+            }
+            out[col] = sum;
+        }
+        return out;
+    }
+
+    /**
+     * Returns a matrix of (column) solution vectors for linear systems with
+     * coefficient matrix = this and constant vectors = columns of
+     * <code>b</code>.
+     *
+     * @param b  array of constants forming RHS of linear systems to
+     * to solve
+     * @return solution array
+     * @throws IllegalArgumentException if this.rowDimension != row dimension
+     * @throws InvalidMatrixException if this matrix is not square or is singular
+     */
+    public BigDecimal[] solve(BigDecimal[] b) throws IllegalArgumentException, InvalidMatrixException {
+        final int nRows = this.getRowDimension();
+        if (b.length != nRows) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.VECTOR_LENGTH_MISMATCH,
+                    b.length, nRows);
+        }
+        final BigMatrix bMatrix = new BigMatrixImpl(b);
+        final BigDecimal[][] solution = ((BigMatrixImpl) (solve(bMatrix))).getDataRef();
+        final BigDecimal[] out = new BigDecimal[nRows];
+        for (int row = 0; row < nRows; row++) {
+            out[row] = solution[row][0];
+        }
+        return out;
+    }
+
+    /**
+     * Returns a matrix of (column) solution vectors for linear systems with
+     * coefficient matrix = this and constant vectors = columns of
+     * <code>b</code>.
+     *
+     * @param b  array of constants forming RHS of linear systems to
+     * to solve
+     * @return solution array
+     * @throws IllegalArgumentException if this.rowDimension != row dimension
+     * @throws InvalidMatrixException if this matrix is not square or is singular
+     */
+    public BigDecimal[] solve(double[] b) throws IllegalArgumentException, InvalidMatrixException {
+        final BigDecimal bd[] = new BigDecimal[b.length];
+        for (int i = 0; i < bd.length; i++) {
+            bd[i] = new BigDecimal(b[i]);
+        }
+        return solve(bd);
+    }
+
+    /**
+     * Returns a matrix of (column) solution vectors for linear systems with
+     * coefficient matrix = this and constant vectors = columns of
+     * <code>b</code>.
+     *
+     * @param b  matrix of constant vectors forming RHS of linear systems to
+     * to solve
+     * @return matrix of solution vectors
+     * @throws IllegalArgumentException if this.rowDimension != row dimension
+     * @throws InvalidMatrixException if this matrix is not square or is singular
+     */
+    public BigMatrix solve(BigMatrix b) throws IllegalArgumentException, InvalidMatrixException  {
+        if (b.getRowDimension() != getRowDimension()) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.DIMENSIONS_MISMATCH_2x2,
+                    b.getRowDimension(), b.getColumnDimension(), getRowDimension(), "n");
+        }
+        if (!isSquare()) {
+            throw new NonSquareMatrixException(getRowDimension(), getColumnDimension());
+        }
+        if (this.isSingular()) { // side effect: compute LU decomp
+            throw new SingularMatrixException();
+        }
+
+        final int nCol = this.getColumnDimension();
+        final int nColB = b.getColumnDimension();
+        final int nRowB = b.getRowDimension();
+
+        // Apply permutations to b
+        final BigDecimal[][] bp = new BigDecimal[nRowB][nColB];
+        for (int row = 0; row < nRowB; row++) {
+            final BigDecimal[] bpRow = bp[row];
+            for (int col = 0; col < nColB; col++) {
+                bpRow[col] = b.getEntry(permutation[row], col);
+            }
+        }
+
+        // Solve LY = b
+        for (int col = 0; col < nCol; col++) {
+            for (int i = col + 1; i < nCol; i++) {
+                final BigDecimal[] bpI = bp[i];
+                final BigDecimal[] luI = lu[i];
+                for (int j = 0; j < nColB; j++) {
+                    bpI[j] = bpI[j].subtract(bp[col][j].multiply(luI[col]));
+                }
+            }
+        }
+
+        // Solve UX = Y
+        for (int col = nCol - 1; col >= 0; col--) {
+            final BigDecimal[] bpCol = bp[col];
+            final BigDecimal luDiag = lu[col][col];
+            for (int j = 0; j < nColB; j++) {
+                bpCol[j] = bpCol[j].divide(luDiag, scale, roundingMode);
+            }
+            for (int i = 0; i < col; i++) {
+                final BigDecimal[] bpI = bp[i];
+                final BigDecimal[] luI = lu[i];
+                for (int j = 0; j < nColB; j++) {
+                    bpI[j] = bpI[j].subtract(bp[col][j].multiply(luI[col]));
+                }
+            }
+        }
+
+        return new BigMatrixImpl(bp, false);
+
+    }
+
+    /**
+     * Computes a new
+     * <a href="http://www.math.gatech.edu/~bourbaki/math2601/Web-notes/2num.pdf">
+     * LU decompostion</a> for this matrix, storing the result for use by other methods.
+     * <p>
+     * <strong>Implementation Note</strong>:<br>
+     * Uses <a href="http://www.damtp.cam.ac.uk/user/fdl/people/sd/lectures/nummeth98/linear.htm">
+     * Crout's algortithm</a>, with partial pivoting.</p>
+     * <p>
+     * <strong>Usage Note</strong>:<br>
+     * This method should rarely be invoked directly. Its only use is
+     * to force recomputation of the LU decomposition when changes have been
+     * made to the underlying data using direct array references. Changes
+     * made using setXxx methods will trigger recomputation when needed
+     * automatically.</p>
+     *
+     * @throws InvalidMatrixException if the matrix is non-square or singular.
+     */
+    public void luDecompose() throws InvalidMatrixException {
+
+        final int nRows = this.getRowDimension();
+        final int nCols = this.getColumnDimension();
+        if (nRows != nCols) {
+            throw new NonSquareMatrixException(getRowDimension(), getColumnDimension());
+        }
+        lu = this.getData();
+
+        // Initialize permutation array and parity
+        permutation = new int[nRows];
+        for (int row = 0; row < nRows; row++) {
+            permutation[row] = row;
+        }
+        parity = 1;
+
+        // Loop over columns
+        for (int col = 0; col < nCols; col++) {
+
+            BigDecimal sum = ZERO;
+
+            // upper
+            for (int row = 0; row < col; row++) {
+                final BigDecimal[] 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 max = col; // permutation row
+            BigDecimal largest = ZERO;
+            for (int row = col; row < nRows; row++) {
+                final BigDecimal[] 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;
+
+                // maintain best permutation choice
+                if (sum.abs().compareTo(largest) == 1) {
+                    largest = sum.abs();
+                    max = row;
+                }
+            }
+
+            // Singularity check
+            if (lu[max][col].abs().compareTo(TOO_SMALL) <= 0) {
+                lu = null;
+                throw new SingularMatrixException();
+            }
+
+            // Pivot if necessary
+            if (max != col) {
+                BigDecimal tmp = ZERO;
+                for (int i = 0; i < nCols; i++) {
+                    tmp = lu[max][i];
+                    lu[max][i] = lu[col][i];
+                    lu[col][i] = tmp;
+                }
+                int temp = permutation[max];
+                permutation[max] = permutation[col];
+                permutation[col] = temp;
+                parity = -parity;
+            }
+
+            // Divide the lower elements by the "winning" diagonal elt.
+            final BigDecimal luDiag = lu[col][col];
+            for (int row = col + 1; row < nRows; row++) {
+                final BigDecimal[] luRow = lu[row];
+                luRow[col] = luRow[col].divide(luDiag, scale, roundingMode);
+            }
+
+        }
+
+    }
+
+    /**
+     * Get a string representation for this matrix.
+     * @return a string representation for this matrix
+     */
+    @Override
+    public String toString() {
+        StringBuilder res = new StringBuilder();
+        res.append("BigMatrixImpl{");
+        if (data != null) {
+            for (int i = 0; i < data.length; i++) {
+                if (i > 0) {
+                    res.append(",");
+                }
+                res.append("{");
+                for (int j = 0; j < data[0].length; j++) {
+                    if (j > 0) {
+                        res.append(",");
+                    }
+                    res.append(data[i][j]);
+                }
+                res.append("}");
+            }
+        }
+        res.append("}");
+        return res.toString();
+    }
+
+    /**
+     * Returns true iff <code>object</code> is a
+     * <code>BigMatrixImpl</code> instance with the same dimensions as this
+     * and all corresponding matrix entries are equal.  BigDecimal.equals
+     * is used to compare corresponding entries.
+     *
+     * @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 BigMatrixImpl == false) {
+            return false;
+        }
+        final BigMatrix m = (BigMatrix) 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++) {
+            final BigDecimal[] dataRow = data[row];
+            for (int col = 0; col < nCols; col++) {
+                if (!dataRow[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 = 7;
+        final int nRows = getRowDimension();
+        final int nCols = getColumnDimension();
+        ret = ret * 31 + nRows;
+        ret = ret * 31 + nCols;
+        for (int row = 0; row < nRows; row++) {
+            final BigDecimal[] dataRow = data[row];
+            for (int col = 0; col < nCols; col++) {
+                ret = ret * 31 + (11 * (row+1) + 17 * (col+1)) *
+                dataRow[col].hashCode();
+            }
+        }
+        return ret;
+    }
+
+    //------------------------ Protected methods
+
+    /**
+     *  Returns the LU decomposition as a BigMatrix.
+     *  Returns a fresh copy of the cached LU matrix if this has been computed;
+     *  otherwise the composition is computed and cached for use by other methods.
+     *  Since a copy is returned in either case, changes to the returned matrix do not
+     *  affect the LU decomposition property.
+     * <p>
+     * The matrix returned is a compact representation of the LU decomposition.
+     * Elements below the main diagonal correspond to entries of the "L" matrix;
+     * elements on and above the main diagonal correspond to entries of the "U"
+     * matrix.</p>
+     * <p>
+     * Example: <pre>
+     *
+     *     Returned matrix                L                  U
+     *         2  3  1                   1  0  0            2  3  1
+     *         5  4  6                   5  1  0            0  4  6
+     *         1  7  8                   1  7  1            0  0  8
+     * </pre>
+     *
+     * The L and U matrices satisfy the matrix equation LU = permuteRows(this), <br>
+     *  where permuteRows reorders the rows of the matrix to follow the order determined
+     *  by the <a href=#getPermutation()>permutation</a> property.</p>
+     *
+     * @return LU decomposition matrix
+     * @throws InvalidMatrixException if the matrix is non-square or singular.
+     */
+    protected BigMatrix getLUMatrix() throws InvalidMatrixException {
+        if (lu == null) {
+            luDecompose();
+        }
+        return new BigMatrixImpl(lu);
+    }
+
+    /**
+     * Returns the permutation associated with the lu decomposition.
+     * The entries of the array represent a permutation of the numbers 0, ... , nRows - 1.
+     * <p>
+     * Example:
+     * permutation = [1, 2, 0] means current 2nd row is first, current third row is second
+     * and current first row is last.</p>
+     * <p>
+     * Returns a fresh copy of the array.</p>
+     *
+     * @return the permutation
+     */
+    protected int[] getPermutation() {
+        final int[] out = new int[permutation.length];
+        System.arraycopy(permutation, 0, out, 0, permutation.length);
+        return out;
+    }
+
+    //------------------------ Private methods
+
+    /**
+     * Returns a fresh copy of the underlying data array.
+     *
+     * @return a copy of the underlying data array.
+     */
+    private BigDecimal[][] copyOut() {
+        final int nRows = this.getRowDimension();
+        final BigDecimal[][] out = new BigDecimal[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;
+    }
+
+    /**
+     * Replaces data with a fresh copy of the input array.
+     * <p>
+     * Verifies that the input array is rectangular and non-empty.</p>
+     *
+     * @param in data to copy in
+     * @throws IllegalArgumentException if input array is emtpy or not
+     *    rectangular
+     * @throws NullPointerException if input array is null
+     */
+    private void copyIn(BigDecimal[][] in) {
+        setSubMatrix(in,0,0);
+    }
+
+    /**
+     * Replaces data with a fresh copy of the input array.
+     *
+     * @param in data to copy in
+     */
+    private void copyIn(double[][] in) {
+        final int nRows = in.length;
+        final int nCols = in[0].length;
+        data = new BigDecimal[nRows][nCols];
+        for (int i = 0; i < nRows; i++) {
+            final BigDecimal[] dataI = data[i];
+            final double[] inI = in[i];
+            for (int j = 0; j < nCols; j++) {
+                dataI[j] = new BigDecimal(inI[j]);
+            }
+        }
+        lu = null;
+    }
+
+    /**
+     * Replaces data with BigDecimals represented by the strings in the input
+     * array.
+     *
+     * @param in data to copy in
+     */
+    private void copyIn(String[][] in) {
+        final int nRows = in.length;
+        final int nCols = in[0].length;
+        data = new BigDecimal[nRows][nCols];
+        for (int i = 0; i < nRows; i++) {
+            final BigDecimal[] dataI = data[i];
+            final String[] inI = in[i];
+            for (int j = 0; j < nCols; j++) {
+                dataI[j] = new BigDecimal(inI[j]);
+            }
+        }
+        lu = null;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/BlockFieldMatrix.java b/src/main/java/org/apache/commons/math/linear/BlockFieldMatrix.java
new file mode 100644
index 0000000..35ae1c2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/BlockFieldMatrix.java
@@ -0,0 +1,1670 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.linear;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.Field;
+import org.apache.commons.math.FieldElement;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.linear.MatrixVisitorException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * 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>
+ * <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>
+ * <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>
+ * <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>
+ * <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.
+ * </p>
+ * @param <T> the type of the field elements
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @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  the number of rows in the new matrix
+     * @param columns  the number of columns in the new matrix
+     * @throws IllegalArgumentException if row or column dimension is not
+     *  positive
+     */
+    public BlockFieldMatrix(final Field<T> field, final int rows, final int columns)
+        throws IllegalArgumentException {
+
+        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>
+     * <p>Calling this constructor is equivalent to call:
+     * <pre>matrix = new BlockFieldMatrix<T>(getField(), rawData.length, rawData[0].length,
+     *                                   toBlocksLayout(rawData), false);</pre>
+     * </p>
+     * @param rawData data for new matrix, in raw layout
+     *
+     * @exception IllegalArgumentException if <code>blockData</code> shape is
+     * inconsistent with block layout
+     * @see #BlockFieldMatrix(int, int, FieldElement[][], boolean)
+     */
+    public BlockFieldMatrix(final T[][] rawData)
+        throws IllegalArgumentException {
+        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.</p>
+     * @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
+     *
+     * @exception IllegalArgumentException if <code>blockData</code> shape is
+     * inconsistent with block layout
+     * @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 IllegalArgumentException {
+
+        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 = 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 MathRuntimeException.createIllegalArgumentException(
+                            LocalizedFormats.WRONG_BLOCK_LENGTH,
+                            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>
+     * <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.
+     * </p>
+     * @param <T> the 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
+     * @exception IllegalArgumentException if <code>rawData</code> 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 IllegalArgumentException {
+
+        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 MathRuntimeException.createIllegalArgumentException(
+                        LocalizedFormats.DIFFERENT_ROWS_LENGTHS,
+                        columns, length);
+            }
+        }
+
+        // convert array
+        final Field<T> field = extractField(rawData);
+        final T[][] blocks = 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 = 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.
+     * </p>
+     * @param <T> the type of the field elements
+     * @param field field to which the elements belong
+     * @param rows  the number of rows in the new matrix
+     * @param columns  the 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 = 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] = buildArray(field, iHeight * jWidth);
+                ++blockIndex;
+            }
+        }
+
+        return blocks;
+
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public FieldMatrix<T> createMatrix(final int rowDimension, final int columnDimension)
+        throws IllegalArgumentException {
+        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 IllegalArgumentException {
+        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 this and <code>m</code>.
+     *
+     * @param m    matrix to be added
+     * @return     this + m
+     * @throws  IllegalArgumentException if m is not the same size as this
+     */
+    public BlockFieldMatrix<T> add(final BlockFieldMatrix<T> m)
+        throws IllegalArgumentException {
+
+        // 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 IllegalArgumentException {
+        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 this minus <code>m</code>.
+     *
+     * @param m    matrix to be subtracted
+     * @return     this - m
+     * @throws  IllegalArgumentException if m is not the same size as this
+     */
+    public BlockFieldMatrix<T> subtract(final BlockFieldMatrix<T> m)
+        throws IllegalArgumentException {
+
+        // 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)
+        throws IllegalArgumentException {
+
+        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)
+        throws IllegalArgumentException {
+
+        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 IllegalArgumentException {
+        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 this by m.
+     *
+     * @param m    matrix to postmultiply by
+     * @return     this * m
+     * @throws     IllegalArgumentException
+     *             if columnDimension(this) != rowDimension(m)
+     */
+    public BlockFieldMatrix<T> multiply(BlockFieldMatrix<T> m) throws IllegalArgumentException {
+
+        // 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 = 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 MatrixIndexException {
+
+        // 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.</p>
+     * @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 MatrixIndexException {
+
+        // safety checks
+        final int refLength = subMatrix[0].length;
+        if (refLength < 1) {
+            throw MathRuntimeException.createIllegalArgumentException(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 MathRuntimeException.createIllegalArgumentException(
+                        LocalizedFormats.DIFFERENT_ROWS_LENGTHS,
+                        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 MatrixIndexException {
+
+        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 MatrixIndexException, InvalidMatrixException {
+        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 MatrixIndexException if the specified row index is invalid
+     * @throws InvalidMatrixException if the matrix dimensions do not match one
+     * instance row
+     */
+    public void setRowMatrix(final int row, final BlockFieldMatrix<T> matrix)
+        throws MatrixIndexException, InvalidMatrixException {
+
+        checkRowIndex(row);
+        final int nCols = getColumnDimension();
+        if ((matrix.getRowDimension() != 1) ||
+            (matrix.getColumnDimension() != nCols)) {
+            throw new InvalidMatrixException(
+                    LocalizedFormats.DIMENSIONS_MISMATCH_2x2,
+                    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 MatrixIndexException {
+
+        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 MatrixIndexException, InvalidMatrixException {
+        try {
+            setColumnMatrix(column, (BlockFieldMatrix<T>) 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 MatrixIndexException if the specified column index is invalid
+     * @throws InvalidMatrixException if the matrix dimensions do not match one
+     * instance column
+     */
+    void setColumnMatrix(final int column, final BlockFieldMatrix<T> matrix)
+        throws MatrixIndexException, InvalidMatrixException {
+
+        checkColumnIndex(column);
+        final int nRows = getRowDimension();
+        if ((matrix.getRowDimension() != nRows) ||
+            (matrix.getColumnDimension() != 1)) {
+            throw new InvalidMatrixException(
+                    LocalizedFormats.DIMENSIONS_MISMATCH_2x2,
+                    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 MatrixIndexException {
+
+        checkRowIndex(row);
+        final T[] outData = 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>(outData, false);
+
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setRowVector(final int row, final FieldVector<T> vector)
+        throws MatrixIndexException, InvalidMatrixException {
+        try {
+            setRow(row, ((ArrayFieldVector<T>) vector).getDataRef());
+        } catch (ClassCastException cce) {
+            super.setRowVector(row, vector);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public FieldVector<T> getColumnVector(final int column)
+        throws MatrixIndexException {
+
+        checkColumnIndex(column);
+        final T[] outData = 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>(outData, false);
+
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setColumnVector(final int column, final FieldVector<T> vector)
+        throws MatrixIndexException, InvalidMatrixException {
+        try {
+            setColumn(column, ((ArrayFieldVector<T>) vector).getDataRef());
+        } catch (ClassCastException cce) {
+            super.setColumnVector(column, vector);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public T[] getRow(final int row)
+        throws MatrixIndexException {
+
+        checkRowIndex(row);
+        final T[] out = 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 MatrixIndexException, InvalidMatrixException {
+
+        checkRowIndex(row);
+        final int nCols = getColumnDimension();
+        if (array.length != nCols) {
+            throw new InvalidMatrixException(
+                    LocalizedFormats.DIMENSIONS_MISMATCH_2x2,
+                    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 MatrixIndexException {
+
+        checkColumnIndex(column);
+        final T[] out = 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 MatrixIndexException, InvalidMatrixException {
+
+        checkColumnIndex(column);
+        final int nRows = getRowDimension();
+        if (array.length != nRows) {
+            throw new InvalidMatrixException(
+                    LocalizedFormats.DIMENSIONS_MISMATCH_2x2,
+                    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 MatrixIndexException {
+        try {
+            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];
+        } catch (ArrayIndexOutOfBoundsException e) {
+            throw new MatrixIndexException(
+                    LocalizedFormats.NO_SUCH_MATRIX_ENTRY,
+                    row, column, getRowDimension(), getColumnDimension());
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setEntry(final int row, final int column, final T value)
+        throws MatrixIndexException {
+        try {
+            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;
+        } catch (ArrayIndexOutOfBoundsException e) {
+            throw new MatrixIndexException(
+                    LocalizedFormats.NO_SUCH_MATRIX_ENTRY,
+                    row, column, getRowDimension(), getColumnDimension());
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addToEntry(final int row, final int column, final T increment)
+        throws MatrixIndexException {
+        try {
+            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);
+        } catch (ArrayIndexOutOfBoundsException e) {
+            throw new MatrixIndexException(
+                    LocalizedFormats.NO_SUCH_MATRIX_ENTRY,
+                    row, column, getRowDimension(), getColumnDimension());
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void multiplyEntry(final int row, final int column, final T factor)
+        throws MatrixIndexException {
+        try {
+            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);
+        } catch (ArrayIndexOutOfBoundsException e) {
+            throw new MatrixIndexException(
+                    LocalizedFormats.NO_SUCH_MATRIX_ENTRY,
+                    row, column, getRowDimension(), getColumnDimension());
+        }
+    }
+
+    /** {@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 IllegalArgumentException {
+
+        if (v.length != columns) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.VECTOR_LENGTH_MISMATCH,
+                    v.length, columns);
+        }
+        final T[] out = 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 IllegalArgumentException {
+
+        if (v.length != rows) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.VECTOR_LENGTH_MISMATCH,
+                    v.length, rows);
+        }
+        final T[] out = 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)
+        throws MatrixVisitorException {
+        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)
+        throws MatrixVisitorException {
+        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 MatrixIndexException, MatrixVisitorException {
+        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 MatrixIndexException, MatrixVisitorException {
+        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)
+        throws MatrixVisitorException {
+        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)
+        throws MatrixVisitorException {
+        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 MatrixIndexException, MatrixVisitorException {
+        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 MatrixIndexException, MatrixVisitorException {
+        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/math/linear/BlockRealMatrix.java b/src/main/java/org/apache/commons/math/linear/BlockRealMatrix.java
new file mode 100644
index 0000000..5e7060e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/BlockRealMatrix.java
@@ -0,0 +1,1690 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.linear;
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.linear.MatrixVisitorException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * 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>
+ * <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>
+ * <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>
+ * <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>
+ * <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.
+ * </p>
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @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 IllegalArgumentException if row or column dimension is not
+     *  positive
+     */
+    public BlockRealMatrix(final int rows, final int columns)
+        throws IllegalArgumentException {
+
+        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>
+     * <p>Calling this constructor is equivalent to call:
+     * <pre>matrix = new BlockRealMatrix(rawData.length, rawData[0].length,
+     *                                   toBlocksLayout(rawData), false);</pre>
+     * </p>
+     * @param rawData data for new matrix, in raw layout
+     *
+     * @exception IllegalArgumentException if <code>blockData</code> shape is
+     * inconsistent with block layout
+     * @see #BlockRealMatrix(int, int, double[][], boolean)
+     */
+    public BlockRealMatrix(final double[][] rawData)
+        throws IllegalArgumentException {
+        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.</p>
+     * @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
+     *
+     * @exception IllegalArgumentException if <code>blockData</code> shape is
+     * inconsistent with block layout
+     * @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 IllegalArgumentException {
+
+        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 MathRuntimeException.createIllegalArgumentException(
+                            LocalizedFormats.WRONG_BLOCK_LENGTH,
+                            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>
+     * <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.
+     * </p>
+     * @param rawData data array in raw layout
+     * @return a new data array containing the same entries but in blocks layout
+     * @exception IllegalArgumentException if <code>rawData</code> is not rectangular
+     *  (not all rows have the same length)
+     * @see #createBlocksLayout(int, int)
+     * @see #BlockRealMatrix(int, int, double[][], boolean)
+     */
+    public static double[][] toBlocksLayout(final double[][] rawData)
+        throws IllegalArgumentException {
+
+        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 MathRuntimeException.createIllegalArgumentException(
+                        LocalizedFormats.DIFFERENT_ROWS_LENGTHS,
+                        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.
+     * </p>
+     * @param rows  the number of rows in the new matrix
+     * @param columns  the 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 IllegalArgumentException {
+        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 IllegalArgumentException {
+        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 and <code>m</code>.
+     *
+     * @param m    matrix to be added
+     * @return     this + m
+     * @throws  IllegalArgumentException if m is not the same size as this
+     */
+    public BlockRealMatrix add(final BlockRealMatrix m)
+        throws IllegalArgumentException {
+
+        // 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 IllegalArgumentException {
+        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;
+
+        }
+    }
+
+    /**
+     * Compute this minus <code>m</code>.
+     *
+     * @param m    matrix to be subtracted
+     * @return     this - m
+     * @throws  IllegalArgumentException if m is not the same size as this
+     */
+    public BlockRealMatrix subtract(final BlockRealMatrix m)
+        throws IllegalArgumentException {
+
+        // 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)
+        throws IllegalArgumentException {
+
+        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)
+        throws IllegalArgumentException {
+
+        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 IllegalArgumentException {
+        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 m.
+     *
+     * @param m    matrix to postmultiply by
+     * @return     this * m
+     * @throws     IllegalArgumentException
+     *             if columnDimension(this) != rowDimension(m)
+     */
+    public BlockRealMatrix multiply(BlockRealMatrix m) throws IllegalArgumentException {
+
+        // 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 MatrixIndexException {
+
+        // 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.</p>
+     * @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 MatrixIndexException {
+
+        // safety checks
+        final int refLength = subMatrix[0].length;
+        if (refLength < 1) {
+            throw MathRuntimeException.createIllegalArgumentException(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 MathRuntimeException.createIllegalArgumentException(
+                        LocalizedFormats.DIFFERENT_ROWS_LENGTHS,
+                        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 MatrixIndexException {
+
+        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 MatrixIndexException, InvalidMatrixException {
+        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 MatrixIndexException if the specified row index is invalid
+     * @throws InvalidMatrixException if the matrix dimensions do not match one
+     * instance row
+     */
+    public void setRowMatrix(final int row, final BlockRealMatrix matrix)
+        throws MatrixIndexException, InvalidMatrixException {
+
+        MatrixUtils.checkRowIndex(this, row);
+        final int nCols = getColumnDimension();
+        if ((matrix.getRowDimension() != 1) ||
+            (matrix.getColumnDimension() != nCols)) {
+            throw new InvalidMatrixException(
+                    LocalizedFormats.DIMENSIONS_MISMATCH_2x2,
+                    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 MatrixIndexException {
+
+        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 MatrixIndexException, InvalidMatrixException {
+        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 MatrixIndexException if the specified column index is invalid
+     * @throws InvalidMatrixException if the matrix dimensions do not match one
+     * instance column
+     */
+    void setColumnMatrix(final int column, final BlockRealMatrix matrix)
+        throws MatrixIndexException, InvalidMatrixException {
+
+        MatrixUtils.checkColumnIndex(this, column);
+        final int nRows = getRowDimension();
+        if ((matrix.getRowDimension() != nRows) ||
+            (matrix.getColumnDimension() != 1)) {
+            throw new InvalidMatrixException(
+                    LocalizedFormats.DIMENSIONS_MISMATCH_2x2,
+                    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 MatrixIndexException {
+
+        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 MatrixIndexException, InvalidMatrixException {
+        try {
+            setRow(row, ((ArrayRealVector) vector).getDataRef());
+        } catch (ClassCastException cce) {
+            super.setRowVector(row, vector);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector getColumnVector(final int column)
+        throws MatrixIndexException {
+
+        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 MatrixIndexException, InvalidMatrixException {
+        try {
+            setColumn(column, ((ArrayRealVector) vector).getDataRef());
+        } catch (ClassCastException cce) {
+            super.setColumnVector(column, vector);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double[] getRow(final int row)
+        throws MatrixIndexException {
+
+        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 MatrixIndexException, InvalidMatrixException {
+
+        MatrixUtils.checkRowIndex(this, row);
+        final int nCols = getColumnDimension();
+        if (array.length != nCols) {
+            throw new InvalidMatrixException(
+                    LocalizedFormats.DIMENSIONS_MISMATCH_2x2,
+                    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 MatrixIndexException {
+
+        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 MatrixIndexException, InvalidMatrixException {
+
+        MatrixUtils.checkColumnIndex(this, column);
+        final int nRows = getRowDimension();
+        if (array.length != nRows) {
+            throw new InvalidMatrixException(
+                    LocalizedFormats.DIMENSIONS_MISMATCH_2x2,
+                    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 MatrixIndexException {
+        try {
+            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];
+        } catch (ArrayIndexOutOfBoundsException e) {
+            throw new MatrixIndexException(
+                    LocalizedFormats.NO_SUCH_MATRIX_ENTRY,
+                    row, column, getRowDimension(), getColumnDimension());
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setEntry(final int row, final int column, final double value)
+        throws MatrixIndexException {
+        try {
+            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;
+        } catch (ArrayIndexOutOfBoundsException e) {
+            throw new MatrixIndexException(
+                    LocalizedFormats.NO_SUCH_MATRIX_ENTRY,
+                    row, column, getRowDimension(), getColumnDimension());
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addToEntry(final int row, final int column, final double increment)
+        throws MatrixIndexException {
+        try {
+            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;
+        } catch (ArrayIndexOutOfBoundsException e) {
+            throw new MatrixIndexException(
+                    LocalizedFormats.NO_SUCH_MATRIX_ENTRY,
+                    row, column, getRowDimension(), getColumnDimension());
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void multiplyEntry(final int row, final int column, final double factor)
+        throws MatrixIndexException {
+        try {
+            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;
+        } catch (ArrayIndexOutOfBoundsException e) {
+            throw new MatrixIndexException(
+                    LocalizedFormats.NO_SUCH_MATRIX_ENTRY,
+                    row, column, getRowDimension(), getColumnDimension());
+        }
+    }
+
+    /** {@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 IllegalArgumentException {
+
+        if (v.length != columns) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.VECTOR_LENGTH_MISMATCH,
+                    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 IllegalArgumentException {
+
+        if (v.length != rows) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.VECTOR_LENGTH_MISMATCH,
+                    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)
+        throws MatrixVisitorException {
+        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)
+        throws MatrixVisitorException {
+        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 MatrixIndexException, MatrixVisitorException {
+        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 MatrixIndexException, MatrixVisitorException {
+        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)
+        throws MatrixVisitorException {
+        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)
+        throws MatrixVisitorException {
+        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 MatrixIndexException, MatrixVisitorException {
+        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 MatrixIndexException, MatrixVisitorException {
+        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/math/linear/CholeskyDecomposition.java b/src/main/java/org/apache/commons/math/linear/CholeskyDecomposition.java
new file mode 100644
index 0000000..d28523e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/CholeskyDecomposition.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.math.linear;
+
+
+/**
+ * An interface to classes that implement an algorithm to calculate the
+ * Cholesky decomposition of a real symmetric positive-definite matrix.
+ * <p>This interface 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:</p>
+ * <ul>
+ *   <li>a {@link #getLT() getLT} method has been added,</li>
+ *   <li>the <code>isspd</code> method has been removed, the constructors of
+ *   implementation classes being expected to throw {@link
+ *   NotPositiveDefiniteMatrixException} when a matrix cannot be decomposed,</li>
+ *   <li>a {@link #getDeterminant() getDeterminant} method has been added,</li>
+ *   <li>the <code>solve</code> method has been replaced by a {@link
+ *   #getSolver() getSolver} method and the equivalent method provided by
+ *   the returned {@link DecompositionSolver}.</li>
+ * </ul>
+ *
+ * @see <a href="http://mathworld.wolfram.com/CholeskyDecomposition.html">MathWorld</a>
+ * @see <a href="http://en.wikipedia.org/wiki/Cholesky_decomposition">Wikipedia</a>
+ * @version $Revision: 826627 $ $Date: 2009-10-19 12:27:47 +0200 (lun. 19 oct. 2009) $
+ * @since 2.0
+ */
+public interface CholeskyDecomposition {
+
+    /**
+     * Returns the matrix L of the decomposition.
+     * <p>L is an lower-triangular matrix</p>
+     * @return the L matrix
+     */
+    RealMatrix getL();
+
+    /**
+     * Returns the transpose of the matrix L of the decomposition.
+     * <p>L<sup>T</sup> is an upper-triangular matrix</p>
+     * @return the transpose of the matrix L of the decomposition
+     */
+    RealMatrix getLT();
+
+    /**
+     * Return the determinant of the matrix
+     * @return determinant of the matrix
+     */
+    double getDeterminant();
+
+    /**
+     * Get a solver for finding the A &times; X = B solution in least square sense.
+     * @return a solver
+     */
+    DecompositionSolver getSolver();
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/CholeskyDecompositionImpl.java b/src/main/java/org/apache/commons/math/linear/CholeskyDecompositionImpl.java
new file mode 100644
index 0000000..bde380e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/CholeskyDecompositionImpl.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.math.linear;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.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 that
+ * satisfy: A = LL<sup>T</sup>Q = I). In a sense, this is the square root of A.</p>
+ *
+ * @see <a href="http://mathworld.wolfram.com/CholeskyDecomposition.html">MathWorld</a>
+ * @see <a href="http://en.wikipedia.org/wiki/Cholesky_decomposition">Wikipedia</a>
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ * @since 2.0
+ */
+public class CholeskyDecompositionImpl implements 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
+     * #CholeskyDecompositionImpl(RealMatrix, double, double)} with the
+     * thresholds set to the default values {@link
+     * #DEFAULT_RELATIVE_SYMMETRY_THRESHOLD} and {@link
+     * #DEFAULT_ABSOLUTE_POSITIVITY_THRESHOLD}
+     * </p>
+     * @param matrix the matrix to decompose
+     * @exception NonSquareMatrixException if matrix is not square
+     * @exception NotSymmetricMatrixException if matrix is not symmetric
+     * @exception NotPositiveDefiniteMatrixException if the matrix is not
+     * strictly positive definite
+     * @see #CholeskyDecompositionImpl(RealMatrix, double, double)
+     * @see #DEFAULT_RELATIVE_SYMMETRY_THRESHOLD
+     * @see #DEFAULT_ABSOLUTE_POSITIVITY_THRESHOLD
+     */
+    public CholeskyDecompositionImpl(final RealMatrix matrix)
+        throws NonSquareMatrixException,
+               NotSymmetricMatrixException, NotPositiveDefiniteMatrixException {
+        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
+     * @exception NonSquareMatrixException if matrix is not square
+     * @exception NotSymmetricMatrixException if matrix is not symmetric
+     * @exception NotPositiveDefiniteMatrixException if the matrix is not
+     * strictly positive definite
+     * @see #CholeskyDecompositionImpl(RealMatrix)
+     * @see #DEFAULT_RELATIVE_SYMMETRY_THRESHOLD
+     * @see #DEFAULT_ABSOLUTE_POSITIVITY_THRESHOLD
+     */
+    public CholeskyDecompositionImpl(final RealMatrix matrix,
+                                     final double relativeSymmetryThreshold,
+                                     final double absolutePositivityThreshold)
+        throws NonSquareMatrixException,
+               NotSymmetricMatrixException, NotPositiveDefiniteMatrixException {
+
+        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 NotSymmetricMatrixException();
+                }
+                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 NotPositiveDefiniteMatrixException();
+            }
+
+            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];
+                }
+            }
+
+        }
+
+    }
+
+    /** {@inheritDoc} */
+    public RealMatrix getL() {
+        if (cachedL == null) {
+            cachedL = getLT().transpose();
+        }
+        return cachedL;
+    }
+
+    /** {@inheritDoc} */
+    public RealMatrix getLT() {
+
+        if (cachedLT == null) {
+            cachedLT = MatrixUtils.createRealMatrix(lTData);
+        }
+
+        // return the cached matrix
+        return cachedLT;
+
+    }
+
+    /** {@inheritDoc} */
+    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;
+    }
+
+    /** {@inheritDoc} */
+    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 double[] solve(double[] b)
+            throws IllegalArgumentException, InvalidMatrixException {
+
+            final int m = lTData.length;
+            if (b.length != m) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                        LocalizedFormats.VECTOR_LENGTH_MISMATCH,
+                        b.length, m);
+            }
+
+            final double[] x = b.clone();
+
+            // 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 x;
+
+        }
+
+        /** {@inheritDoc} */
+        public RealVector solve(RealVector b)
+            throws IllegalArgumentException, InvalidMatrixException {
+            try {
+                return solve((ArrayRealVector) b);
+            } catch (ClassCastException cce) {
+
+                final int m = lTData.length;
+                if (b.getDimension() != m) {
+                    throw MathRuntimeException.createIllegalArgumentException(
+                            LocalizedFormats.VECTOR_LENGTH_MISMATCH,
+                            b.getDimension(), m);
+                }
+
+                final double[] x = b.getData();
+
+                // 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);
+
+            }
+        }
+
+        /** Solve the linear equation A &times; X = B.
+         * <p>The A matrix is implicit here. It is </p>
+         * @param b right-hand side of the equation A &times; X = B
+         * @return a vector X such that A &times; X = B
+         * @exception IllegalArgumentException if matrices dimensions don't match
+         * @exception InvalidMatrixException if decomposed matrix is singular
+         */
+        public ArrayRealVector solve(ArrayRealVector b)
+            throws IllegalArgumentException, InvalidMatrixException {
+            return new ArrayRealVector(solve(b.getDataRef()), false);
+        }
+
+        /** {@inheritDoc} */
+        public RealMatrix solve(RealMatrix b)
+            throws IllegalArgumentException, InvalidMatrixException {
+
+            final int m = lTData.length;
+            if (b.getRowDimension() != m) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                        LocalizedFormats.DIMENSIONS_MISMATCH_2x2,
+                        b.getRowDimension(), b.getColumnDimension(), m, "n");
+            }
+
+            final int nColB = b.getColumnDimension();
+            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, false);
+
+        }
+
+        /** {@inheritDoc} */
+        public RealMatrix getInverse() throws InvalidMatrixException {
+            return solve(MatrixUtils.createRealIdentityMatrix(lTData.length));
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/DecompositionSolver.java b/src/main/java/org/apache/commons/math/linear/DecompositionSolver.java
new file mode 100644
index 0000000..4f7cb53
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/DecompositionSolver.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.math.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>
+ * <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.</p>
+ *
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ * @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.</p>
+     * @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
+     * @exception IllegalArgumentException if matrices dimensions don't match
+     * @exception InvalidMatrixException if decomposed matrix is singular
+     */
+    double[] solve(final double[] b)
+        throws IllegalArgumentException, InvalidMatrixException;
+
+    /** 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.</p>
+     * @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
+     * @exception IllegalArgumentException if matrices dimensions don't match
+     * @exception InvalidMatrixException if decomposed matrix is singular
+     */
+    RealVector solve(final RealVector b)
+        throws IllegalArgumentException, InvalidMatrixException;
+
+    /** 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.</p>
+     * @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
+     * @exception IllegalArgumentException if matrices dimensions don't match
+     * @exception InvalidMatrixException if decomposed matrix is singular
+     */
+    RealMatrix solve(final RealMatrix b)
+        throws IllegalArgumentException, InvalidMatrixException;
+
+    /**
+     * 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 InvalidMatrixException if decomposed matrix is singular
+     */
+    RealMatrix getInverse()
+        throws InvalidMatrixException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/DefaultFieldMatrixChangingVisitor.java b/src/main/java/org/apache/commons/math/linear/DefaultFieldMatrixChangingVisitor.java
new file mode 100644
index 0000000..808b00d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/DefaultFieldMatrixChangingVisitor.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.math.linear;
+
+import org.apache.commons.math.FieldElement;
+import org.apache.commons.math.linear.MatrixVisitorException;
+
+/**
+ * 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.
+ * </p>
+ *
+ * @param <T> the type of the field elements
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @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) throws MatrixVisitorException {
+        return value;
+    }
+
+    /** {@inheritDoc} */
+    public T end() {
+        return zero;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/DefaultFieldMatrixPreservingVisitor.java b/src/main/java/org/apache/commons/math/linear/DefaultFieldMatrixPreservingVisitor.java
new file mode 100644
index 0000000..e1fd606
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/DefaultFieldMatrixPreservingVisitor.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.math.linear;
+
+import org.apache.commons.math.FieldElement;
+import org.apache.commons.math.linear.MatrixVisitorException;
+
+/**
+ * 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.
+ * </p>
+ *
+ * @param <T> the type of the field elements
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @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)
+        throws MatrixVisitorException {
+    }
+
+    /** {@inheritDoc} */
+    public T end() {
+        return zero;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/DefaultRealMatrixChangingVisitor.java b/src/main/java/org/apache/commons/math/linear/DefaultRealMatrixChangingVisitor.java
new file mode 100644
index 0000000..c609d81
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/DefaultRealMatrixChangingVisitor.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.math.linear;
+
+import org.apache.commons.math.linear.MatrixVisitorException;
+
+/**
+ * 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.
+ * </p>
+ *
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @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) throws MatrixVisitorException {
+        return value;
+    }
+
+    /** {@inheritDoc} */
+    public double end() {
+        return 0;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/DefaultRealMatrixPreservingVisitor.java b/src/main/java/org/apache/commons/math/linear/DefaultRealMatrixPreservingVisitor.java
new file mode 100644
index 0000000..1ab5b14
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/DefaultRealMatrixPreservingVisitor.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.math.linear;
+
+import org.apache.commons.math.linear.MatrixVisitorException;
+
+/**
+ * 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.
+ * </p>
+ *
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @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)
+        throws MatrixVisitorException {
+    }
+
+    /** {@inheritDoc} */
+    public double end() {
+        return 0;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/EigenDecomposition.java b/src/main/java/org/apache/commons/math/linear/EigenDecomposition.java
new file mode 100644
index 0000000..f991e8b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/EigenDecomposition.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.math.linear;
+
+
+/**
+ * An interface to classes that implement an algorithm to calculate 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>
+ * <p>This interface 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:</p>
+ * <ul>
+ *   <li>a {@link #getVT() getVt} method has been added,</li>
+ *   <li>two {@link #getRealEigenvalue(int) getRealEigenvalue} and {@link #getImagEigenvalue(int)
+ *   getImagEigenvalue} methods to pick up a single eigenvalue have been added,</li>
+ *   <li>a {@link #getEigenvector(int) getEigenvector} method to pick up a single
+ *   eigenvector has been added,</li>
+ *   <li>a {@link #getDeterminant() getDeterminant} method has been added.</li>
+ *   <li>a {@link #getSolver() getSolver} method has been added.</li>
+ * </ul>
+ * @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>
+ * @version $Revision: 997726 $ $Date: 2010-09-16 14:39:51 +0200 (jeu. 16 sept. 2010) $
+ * @since 2.0
+ */
+public interface EigenDecomposition {
+
+    /**
+     * Returns the matrix V of the decomposition.
+     * <p>V is an orthogonal matrix, i.e. its transpose is also its inverse.</p>
+     * <p>The columns of V are the eigenvectors of the original matrix.</p>
+     * <p>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).</p>
+     * @return the V matrix
+     */
+    RealMatrix getV();
+
+    /**
+     * Returns the block diagonal matrix D of the decomposition.
+     * <p>D is a block diagonal matrix.</p>
+     * <p>Real eigenvalues are on the diagonal while complex values are on
+     * 2x2 blocks { {real +imaginary}, {-imaginary, real} }.</p>
+     * @return the D matrix
+     * @see #getRealEigenvalues()
+     * @see #getImagEigenvalues()
+     */
+    RealMatrix getD();
+
+    /**
+     * Returns the transpose of the matrix V of the decomposition.
+     * <p>V is an orthogonal matrix, i.e. its transpose is also its inverse.</p>
+     * <p>The columns of V are the eigenvectors of the original matrix.</p>
+     * <p>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).</p>
+     * @return the transpose of the V matrix
+     */
+    RealMatrix getVT();
+
+    /**
+     * Returns 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()
+     */
+    double[] getRealEigenvalues();
+
+    /**
+     * 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)
+     */
+    double getRealEigenvalue(int i);
+
+    /**
+     * Returns 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()
+     */
+    double[] getImagEigenvalues();
+
+    /**
+     * Returns the imaginary part of the i<sup>th</sup> eigenvalue of the original matrix.
+     * @param i index of the eigenvalue (counting from 0)
+     * @return imaginary part of the i<sup>th</sup> eigenvalue of the original matrix
+     * @see #getD()
+     * @see #getImagEigenvalues()
+     * @see #getRealEigenvalue(int)
+     */
+    double getImagEigenvalue(int i);
+
+    /**
+     * Returns a copy of the i<sup>th</sup> eigenvector of the original matrix.
+     * @param i index of the eigenvector (counting from 0)
+     * @return copy of the i<sup>th</sup> eigenvector of the original matrix
+     * @see #getD()
+     */
+    RealVector getEigenvector(int i);
+
+    /**
+     * Return the determinant of the matrix
+     * @return determinant of the matrix
+     */
+    double getDeterminant();
+
+    /**
+     * Get a solver for finding the A &times; X = B solution in exact linear sense.
+     * @return a solver
+     */
+    DecompositionSolver getSolver();
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/EigenDecompositionImpl.java b/src/main/java/org/apache/commons/math/linear/EigenDecompositionImpl.java
new file mode 100644
index 0000000..1b3085c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/EigenDecompositionImpl.java
@@ -0,0 +1,619 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.linear;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.MaxIterationsExceededException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.util.MathUtils;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Calculates the eigen decomposition of a real <strong>symmetric</strong>
+ * matrix.
+ * <p>
+ * The eigen decomposition of matrix A is a set of two matrices: V and D such
+ * that A = V D V<sup>T</sup>. A, V and D are all m &times; m matrices.
+ * </p>
+ * <p>
+ * As of 2.0, this class supports only <strong>symmetric</strong> matrices, and
+ * hence computes only real realEigenvalues. This implies the D matrix returned
+ * by {@link #getD()} is always diagonal and the imaginary values returned
+ * {@link #getImagEigenvalue(int)} and {@link #getImagEigenvalues()} are always
+ * null.
+ * </p>
+ * <p>
+ * When called with a {@link RealMatrix} argument, this implementation only uses
+ * the upper part of the matrix, the part below the diagonal is not accessed at
+ * all.
+ * </p>
+ * <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
+ * </p>
+ * @version $Revision: 1002040 $ $Date: 2010-09-28 09:18:31 +0200 (mar. 28 sept. 2010) $
+ * @since 2.0
+ */
+public class EigenDecompositionImpl implements EigenDecomposition {
+
+    /** 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;
+
+    /**
+     * Calculates the eigen decomposition of the given symmetric matrix.
+     * @param matrix The <strong>symmetric</strong> matrix to decompose.
+     * @param splitTolerance dummy parameter, present for backward compatibility only.
+     * @exception InvalidMatrixException (wrapping a
+     * {@link org.apache.commons.math.ConvergenceException} if algorithm
+     * fails to converge
+     */
+    public EigenDecompositionImpl(final RealMatrix matrix,final double splitTolerance)
+            throws InvalidMatrixException {
+        if (isSymmetric(matrix)) {
+            transformToTridiagonal(matrix);
+            findEigenVectors(transformer.getQ().getData());
+        } else {
+            // as of 2.0, non-symmetric matrices (i.e. complex eigenvalues) are
+            // NOT supported
+            // see issue https://issues.apache.org/jira/browse/MATH-235
+            throw new InvalidMatrixException(
+                    LocalizedFormats.ASSYMETRIC_EIGEN_NOT_SUPPORTED);
+        }
+    }
+
+    /**
+     * 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 triadiagonal form
+     * @param secondary Secondary of the tridiagonal form
+     * @param splitTolerance dummy parameter, present for backward compatibility only.
+     * @exception InvalidMatrixException (wrapping a
+     * {@link org.apache.commons.math.ConvergenceException} if algorithm
+     * fails to converge
+     */
+    public EigenDecompositionImpl(final double[] main,final double[] secondary,
+            final double splitTolerance)
+            throws InvalidMatrixException {
+        this.main      = main.clone();
+        this.secondary = secondary.clone();
+        transformer    = null;
+        final int size=main.length;
+        double[][] z = new double[size][size];
+        for (int i=0;i<size;i++) {
+            z[i][i]=1.0;
+        }
+        findEigenVectors(z);
+    }
+
+    /**
+     * Check if a matrix is symmetric.
+     * @param matrix
+     *            matrix to check
+     * @return true if matrix is symmetric
+     */
+    private boolean isSymmetric(final RealMatrix matrix) {
+        final int rows = matrix.getRowDimension();
+        final int columns = matrix.getColumnDimension();
+        final double eps = 10 * rows * columns * MathUtils.EPSILON;
+        for (int i = 0; i < rows; ++i) {
+            for (int j = i + 1; j < columns; ++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)) * eps)) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    public RealMatrix getV() throws InvalidMatrixException {
+
+        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;
+
+    }
+
+    /** {@inheritDoc} */
+    public RealMatrix getD() throws InvalidMatrixException {
+        if (cachedD == null) {
+            // cache the matrix for subsequent calls
+            cachedD = MatrixUtils.createRealDiagonalMatrix(realEigenvalues);
+        }
+        return cachedD;
+    }
+
+    /** {@inheritDoc} */
+    public RealMatrix getVT() throws InvalidMatrixException {
+
+        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;
+    }
+
+    /** {@inheritDoc} */
+    public double[] getRealEigenvalues() throws InvalidMatrixException {
+        return realEigenvalues.clone();
+    }
+
+    /** {@inheritDoc} */
+    public double getRealEigenvalue(final int i) throws InvalidMatrixException,
+            ArrayIndexOutOfBoundsException {
+        return realEigenvalues[i];
+    }
+
+    /** {@inheritDoc} */
+    public double[] getImagEigenvalues() throws InvalidMatrixException {
+        return imagEigenvalues.clone();
+    }
+
+    /** {@inheritDoc} */
+    public double getImagEigenvalue(final int i) throws InvalidMatrixException,
+            ArrayIndexOutOfBoundsException {
+        return imagEigenvalues[i];
+    }
+
+    /** {@inheritDoc} */
+    public RealVector getEigenvector(final int i)
+            throws InvalidMatrixException, ArrayIndexOutOfBoundsException {
+        return eigenvectors[i].copy();
+    }
+
+    /**
+     * Return the determinant of the matrix
+     * @return determinant of the matrix
+     */
+    public double getDeterminant() {
+        double determinant = 1;
+        for (double lambda : realEigenvalues) {
+            determinant *= lambda;
+        }
+        return determinant;
+    }
+
+    /** {@inheritDoc} */
+    public DecompositionSolver getSolver() {
+        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;
+
+        /**
+         * Build 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;
+        }
+
+        /**
+         * Solve the linear equation A &times; X = B for symmetric matrices A.
+         * <p>
+         * This method only find exact linear solutions, i.e. solutions for
+         * which ||A &times; X - B|| is exactly 0.
+         * </p>
+         * @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
+         * @exception IllegalArgumentException
+         *                if matrices dimensions don't match
+         * @exception InvalidMatrixException
+         *                if decomposed matrix is singular
+         */
+        public double[] solve(final double[] b)
+                throws IllegalArgumentException, InvalidMatrixException {
+
+            if (!isNonSingular()) {
+                throw new SingularMatrixException();
+            }
+
+            final int m = realEigenvalues.length;
+            if (b.length != m) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                        LocalizedFormats.VECTOR_LENGTH_MISMATCH,
+                        b.length, 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 bp;
+
+        }
+
+        /**
+         * Solve the linear equation A &times; X = B for symmetric matrices A.
+         * <p>
+         * This method only find exact linear solutions, i.e. solutions for
+         * which ||A &times; X - B|| is exactly 0.
+         * </p>
+         * @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
+         * @exception IllegalArgumentException
+         *                if matrices dimensions don't match
+         * @exception InvalidMatrixException
+         *                if decomposed matrix is singular
+         */
+        public RealVector solve(final RealVector b)
+                throws IllegalArgumentException, InvalidMatrixException {
+
+            if (!isNonSingular()) {
+                throw new SingularMatrixException();
+            }
+
+            final int m = realEigenvalues.length;
+            if (b.getDimension() != m) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                        LocalizedFormats.VECTOR_LENGTH_MISMATCH, 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);
+
+        }
+
+        /**
+         * Solve the linear equation A &times; X = B for symmetric matrices A.
+         * <p>
+         * This method only find exact linear solutions, i.e. solutions for
+         * which ||A &times; X - B|| is exactly 0.
+         * </p>
+         * @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
+         * @exception IllegalArgumentException
+         *                if matrices dimensions don't match
+         * @exception InvalidMatrixException
+         *                if decomposed matrix is singular
+         */
+        public RealMatrix solve(final RealMatrix b)
+                throws IllegalArgumentException, InvalidMatrixException {
+
+            if (!isNonSingular()) {
+                throw new SingularMatrixException();
+            }
+
+            final int m = realEigenvalues.length;
+            if (b.getRowDimension() != m) {
+                throw MathRuntimeException
+                        .createIllegalArgumentException(
+                                LocalizedFormats.DIMENSIONS_MISMATCH_2x2,
+                                b.getRowDimension(), b.getColumnDimension(), m,
+                                "n");
+            }
+
+            final int nColB = b.getColumnDimension();
+            final double[][] bp = new double[m][nColB];
+            for (int k = 0; k < nColB; ++k) {
+                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) * b.getEntry(j, k);
+                    }
+                    s /= realEigenvalues[i];
+                    for (int j = 0; j < m; ++j) {
+                        bp[j][k] += s * vData[j];
+                    }
+                }
+            }
+
+            return MatrixUtils.createRealMatrix(bp);
+
+        }
+
+        /**
+         * Check if the decomposed matrix is non-singular.
+         * @return true if the decomposed matrix is non-singular
+         */
+        public boolean isNonSingular() {
+            for (int i = 0; i < realEigenvalues.length; ++i) {
+                if ((realEigenvalues[i] == 0) && (imagEigenvalues[i] == 0)) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        /**
+         * Get the inverse of the decomposed matrix.
+         * @return inverse matrix
+         * @throws InvalidMatrixException
+         *             if decomposed matrix is singular
+         */
+        public RealMatrix getInverse() throws InvalidMatrixException {
+
+            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);
+
+        }
+
+    }
+
+    /**
+     * Transform matrix to tridiagonal.
+     * @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 tri-diagonal form.
+     */
+    private void findEigenVectors(double[][] householderMatrix) {
+
+        double[][]z = householderMatrix.clone();
+        final int n = main.length;
+        realEigenvalues = new double[n];
+        imagEigenvalues = new double[n];
+        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.0;
+
+        // Determine the largest main and secondary value in absolute term.
+        double maxAbsoluteValue=0.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.0) {
+            for (int i=0; i < n; i++) {
+                if (FastMath.abs(realEigenvalues[i])<=MathUtils.EPSILON*maxAbsoluteValue) {
+                    realEigenvalues[i]=0.0;
+                }
+                if (FastMath.abs(e[i])<=MathUtils.EPSILON*maxAbsoluteValue) {
+                    e[i]=0.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 InvalidMatrixException(
+                                new MaxIterationsExceededException(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 = c * s;
+                        } else {
+                            s = p / q;
+                            t = FastMath.sqrt(s * s + 1.0);
+                            e[i + 1] = q * t;
+                            c = 1.0 / t;
+                            s = 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.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])<MathUtils.EPSILON*maxAbsoluteValue) {
+                    realEigenvalues[i]=0.0;
+                }
+            }
+        }
+        eigenvectors = new ArrayRealVector[n];
+        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);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/linear/FieldDecompositionSolver.java b/src/main/java/org/apache/commons/math/linear/FieldDecompositionSolver.java
new file mode 100644
index 0000000..8238cc3
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/FieldDecompositionSolver.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.math.linear;
+
+import org.apache.commons.math.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>
+ * <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.</p>
+ *
+ * @param <T> the type of the field elements
+ * @version $Revision: 781122 $ $Date: 2009-06-02 20:53:23 +0200 (mar. 02 juin 2009) $
+ * @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.</p>
+     * @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
+     * @exception IllegalArgumentException if matrices dimensions don't match
+     * @exception InvalidMatrixException if decomposed matrix is singular
+     */
+    T[] solve(final T[] b)
+        throws IllegalArgumentException, InvalidMatrixException;
+
+    /** 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.</p>
+     * @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
+     * @exception IllegalArgumentException if matrices dimensions don't match
+     * @exception InvalidMatrixException if decomposed matrix is singular
+     */
+    FieldVector<T> solve(final FieldVector<T> b)
+        throws IllegalArgumentException, InvalidMatrixException;
+
+    /** 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.</p>
+     * @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
+     * @exception IllegalArgumentException if matrices dimensions don't match
+     * @exception InvalidMatrixException if decomposed matrix is singular
+     */
+    FieldMatrix<T> solve(final FieldMatrix<T> b)
+        throws IllegalArgumentException, InvalidMatrixException;
+
+    /**
+     * 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 InvalidMatrixException if decomposed matrix is singular
+     */
+    FieldMatrix<T> getInverse()
+        throws InvalidMatrixException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/FieldLUDecomposition.java b/src/main/java/org/apache/commons/math/linear/FieldLUDecomposition.java
new file mode 100644
index 0000000..cbdbadd
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/FieldLUDecomposition.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.math.linear;
+
+import org.apache.commons.math.FieldElement;
+
+/**
+ * An interface to classes that implement an algorithm to calculate the
+ * LU-decomposition of a real matrix.
+ * <p>The LU-decomposition of matrix A is a set of three matrices: P, L and U
+ * such that P&times;A = L&times;U. P is a rows permutation matrix that is used
+ * to rearrange the rows of A before so that it can be decomposed. L is a lower
+ * triangular matrix with unit diagonal terms and U is an upper triangular matrix.</p>
+ * <p>This interface is based on the class with similar name from the
+ * <a href="http://math.nist.gov/javanumerics/jama/">JAMA</a> library.</p>
+ * <ul>
+ *   <li>a {@link #getP() getP} method has been added,</li>
+ *   <li>the <code>det</code> method has been renamed as {@link #getDeterminant()
+ *   getDeterminant},</li>
+ *   <li>the <code>getDoublePivot</code> method has been removed (but the int based
+ *   {@link #getPivot() getPivot} method has been kept),</li>
+ *   <li>the <code>solve</code> and <code>isNonSingular</code> methods have been replaced
+ *   by a {@link #getSolver() getSolver} method and the equivalent methods provided by
+ *   the returned {@link DecompositionSolver}.</li>
+ * </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>
+ * @version $Revision: 826627 $ $Date: 2009-10-19 12:27:47 +0200 (lun. 19 oct. 2009) $
+ * @since 2.0
+ */
+public interface FieldLUDecomposition<T extends FieldElement<T>> {
+
+    /**
+     * Returns the matrix L of the decomposition.
+     * <p>L is an lower-triangular matrix</p>
+     * @return the L matrix (or null if decomposed matrix is singular)
+     */
+    FieldMatrix<T> getL();
+
+    /**
+     * Returns the matrix U of the decomposition.
+     * <p>U is an upper-triangular matrix</p>
+     * @return the U matrix (or null if decomposed matrix is singular)
+     */
+    FieldMatrix<T> getU();
+
+    /**
+     * 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>
+     * <p>The positions of the 1 elements are given by the {@link #getPivot()
+     * pivot permutation vector}.</p>
+     * @return the P rows permutation matrix (or null if decomposed matrix is singular)
+     * @see #getPivot()
+     */
+    FieldMatrix<T> getP();
+
+    /**
+     * Returns the pivot permutation vector.
+     * @return the pivot permutation vector
+     * @see #getP()
+     */
+    int[] getPivot();
+
+    /**
+     * Return the determinant of the matrix
+     * @return determinant of the matrix
+     */
+    T getDeterminant();
+
+    /**
+     * Get a solver for finding the A &times; X = B solution in exact linear sense.
+     * @return a solver
+     */
+    FieldDecompositionSolver<T> getSolver();
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/FieldLUDecompositionImpl.java b/src/main/java/org/apache/commons/math/linear/FieldLUDecompositionImpl.java
new file mode 100644
index 0000000..44fba31
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/FieldLUDecompositionImpl.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.math.linear;
+
+import java.lang.reflect.Array;
+
+import org.apache.commons.math.Field;
+import org.apache.commons.math.FieldElement;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * 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>
+ * <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>
+ *
+ * @param <T> the type of the field elements
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ * @since 2.0
+ */
+public class FieldLUDecompositionImpl<T extends FieldElement<T>> implements FieldLUDecomposition<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.
+     * @exception NonSquareMatrixException if matrix is not square
+     */
+    public FieldLUDecompositionImpl(FieldMatrix<T> matrix)
+        throws NonSquareMatrixException {
+
+        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);
+            }
+        }
+
+    }
+
+    /** {@inheritDoc} */
+    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;
+    }
+
+    /** {@inheritDoc} */
+    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;
+    }
+
+    /** {@inheritDoc} */
+    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;
+    }
+
+    /** {@inheritDoc} */
+    public int[] getPivot() {
+        return pivot.clone();
+    }
+
+    /** {@inheritDoc} */
+    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;
+        }
+    }
+
+    /** {@inheritDoc} */
+    public FieldDecompositionSolver<T> getSolver() {
+        return new Solver<T>(field, lu, pivot, singular);
+    }
+
+    /** Specialized solver. */
+    private static class Solver<T extends FieldElement<T>> implements FieldDecompositionSolver<T> {
+
+        /** Serializable version identifier. */
+        private static final long serialVersionUID = -6353105415121373022L;
+
+        /** 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 T[] solve(T[] b)
+            throws IllegalArgumentException, InvalidMatrixException {
+
+            final int m = pivot.length;
+            if (b.length != m) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                        LocalizedFormats.VECTOR_LENGTH_MISMATCH,
+                        b.length, m);
+            }
+            if (singular) {
+                throw new SingularMatrixException();
+            }
+
+            @SuppressWarnings("unchecked") // field is of type T
+            final T[] bp = (T[]) Array.newInstance(field.getZero().getClass(), m);
+
+            // Apply permutations to b
+            for (int row = 0; row < m; row++) {
+                bp[row] = b[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 bp;
+
+        }
+
+        /** {@inheritDoc} */
+        public FieldVector<T> solve(FieldVector<T> b)
+            throws IllegalArgumentException, InvalidMatrixException {
+            try {
+                return solve((ArrayFieldVector<T>) b);
+            } catch (ClassCastException cce) {
+
+                final int m = pivot.length;
+                if (b.getDimension() != m) {
+                    throw MathRuntimeException.createIllegalArgumentException(
+                            LocalizedFormats.VECTOR_LENGTH_MISMATCH,
+                            b.getDimension(), m);
+                }
+                if (singular) {
+                    throw new SingularMatrixException();
+                }
+
+                @SuppressWarnings("unchecked") // field is of type T
+                final T[] bp = (T[]) Array.newInstance(field.getZero().getClass(), 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 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);
+
+            }
+        }
+
+        /** Solve the linear equation A &times; X = B.
+         * <p>The A matrix is implicit here. It is </p>
+         * @param b right-hand side of the equation A &times; X = B
+         * @return a vector X such that A &times; X = B
+         * @exception IllegalArgumentException if matrices dimensions don't match
+         * @exception InvalidMatrixException if decomposed matrix is singular
+         */
+        public ArrayFieldVector<T> solve(ArrayFieldVector<T> b)
+            throws IllegalArgumentException, InvalidMatrixException {
+            return new ArrayFieldVector<T>(solve(b.getDataRef()), false);
+        }
+
+        /** {@inheritDoc} */
+        public FieldMatrix<T> solve(FieldMatrix<T> b)
+            throws IllegalArgumentException, InvalidMatrixException {
+
+            final int m = pivot.length;
+            if (b.getRowDimension() != m) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                        LocalizedFormats.DIMENSIONS_MISMATCH_2x2,
+                        b.getRowDimension(), b.getColumnDimension(), m, "n");
+            }
+            if (singular) {
+                throw new SingularMatrixException();
+            }
+
+            final int nColB = b.getColumnDimension();
+
+            // Apply permutations to b
+            @SuppressWarnings("unchecked") // field is of type T
+            final T[][] bp = (T[][]) Array.newInstance(field.getZero().getClass(), new int[] { 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>(bp, false);
+
+        }
+
+        /** {@inheritDoc} */
+        public FieldMatrix<T> getInverse() throws InvalidMatrixException {
+            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/math/linear/FieldMatrix.java b/src/main/java/org/apache/commons/math/linear/FieldMatrix.java
new file mode 100644
index 0000000..98d82ae
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/FieldMatrix.java
@@ -0,0 +1,798 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.linear;
+
+
+import org.apache.commons.math.Field;
+import org.apache.commons.math.FieldElement;
+import org.apache.commons.math.linear.MatrixVisitorException;
+
+/**
+ * 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.</p>
+ *
+ * @param <T> the type of the field elements
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ */
+public interface FieldMatrix<T extends FieldElement<T>> extends AnyMatrix {
+
+    /**
+     * Get the type of field elements of the matrix.
+     * @return 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 IllegalArgumentException if row or column dimension is not positive
+     * @since 2.0
+     */
+    FieldMatrix<T> createMatrix(final int rowDimension, final int columnDimension);
+
+    /**
+     * Returns a (deep) copy of this.
+     *
+     * @return matrix copy
+     */
+    FieldMatrix<T> copy();
+
+    /**
+     * Compute the sum of this and m.
+     *
+     * @param m    matrix to be added
+     * @return     this + m
+     * @throws  IllegalArgumentException if m is not the same size as this
+     */
+    FieldMatrix<T> add(FieldMatrix<T> m) throws IllegalArgumentException;
+
+    /**
+     * Compute this minus m.
+     *
+     * @param m    matrix to be subtracted
+     * @return     this + m
+     * @throws  IllegalArgumentException if m is not the same size as this
+     */
+    FieldMatrix<T> subtract(FieldMatrix<T> m) throws IllegalArgumentException;
+
+     /**
+     * Returns the result of adding d to each entry of this.
+     *
+     * @param d    value to be added to each entry
+     * @return     d + this
+     */
+    FieldMatrix<T> scalarAdd(T d);
+
+    /**
+     * Returns the result multiplying each entry of this by d.
+     *
+     * @param d    value to multiply all entries by
+     * @return     d * this
+     */
+    FieldMatrix<T> scalarMultiply(T d);
+
+    /**
+     * Returns the result of postmultiplying this by m.
+     *
+     * @param m    matrix to postmultiply by
+     * @return     this * m
+     * @throws     IllegalArgumentException
+     *             if columnDimension(this) != rowDimension(m)
+     */
+    FieldMatrix<T> multiply(FieldMatrix<T> m) throws IllegalArgumentException;
+
+    /**
+     * Returns the result premultiplying this by <code>m</code>.
+     * @param m    matrix to premultiply by
+     * @return     m * this
+     * @throws     IllegalArgumentException
+     *             if rowDimension(this) != columnDimension(m)
+     */
+    FieldMatrix<T> preMultiply(FieldMatrix<T> m) throws IllegalArgumentException;
+
+    /**
+     * Returns matrix entries as a two-dimensional array.
+     *
+     * @return    2-dimensional array of entries
+     */
+    T[][] getData();
+
+    /**
+     * 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
+     * @exception MatrixIndexException  if the indices are not valid
+     */
+   FieldMatrix<T> getSubMatrix(int startRow, int endRow, int startColumn, int endColumn)
+       throws MatrixIndexException;
+
+   /**
+    * 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
+    * @exception MatrixIndexException if row or column selections are not valid
+    */
+   FieldMatrix<T> getSubMatrix(int[] selectedRows, int[] selectedColumns)
+       throws MatrixIndexException;
+
+   /**
+    * 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)
+    * @exception MatrixIndexException if the indices are not valid
+    * @exception IllegalArgumentException if the destination array is too small
+    */
+  void copySubMatrix(int startRow, int endRow, int startColumn, int endColumn,
+                     T[][] destination)
+      throws MatrixIndexException, IllegalArgumentException;
+
+  /**
+   * 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)
+   * @exception MatrixIndexException if the indices are not valid
+   * @exception IllegalArgumentException if the destination array is too small
+   */
+  void copySubMatrix(int[] selectedRows, int[] selectedColumns, T[][] destination)
+      throws MatrixIndexException, IllegalArgumentException;
+
+   /**
+    * Replace the submatrix starting at <code>row, column</code> using data in
+    * the input <code>subMatrix</code> 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></p>
+    *
+    * @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 MatrixIndexException  if subMatrix does not fit into this
+    *    matrix from element in (row, column)
+    * @throws IllegalArgumentException if <code>subMatrix</code> is not rectangular
+    *  (not all rows have the same length) or empty
+    * @throws NullPointerException if <code>subMatrix</code> is null
+    * @since 2.0
+    */
+   void setSubMatrix(T[][] subMatrix, int row, int column)
+       throws MatrixIndexException;
+
+   /**
+    * Returns the entries in row number <code>row</code>
+    * as a row matrix.  Row indices start at 0.
+    *
+    * @param row the row to be fetched
+    * @return row matrix
+    * @throws MatrixIndexException if the specified row index is invalid
+    */
+   FieldMatrix<T> getRowMatrix(int row) throws MatrixIndexException;
+
+   /**
+    * 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 MatrixIndexException if the specified row index is invalid
+    * @throws InvalidMatrixException if the matrix dimensions do not match one
+    * instance row
+    */
+   void setRowMatrix(int row, FieldMatrix<T> matrix)
+       throws MatrixIndexException, InvalidMatrixException;
+
+   /**
+    * Returns the entries in column number <code>column</code>
+    * as a column matrix.  Column indices start at 0.
+    *
+    * @param column the column to be fetched
+    * @return column matrix
+    * @throws MatrixIndexException if the specified column index is invalid
+    */
+   FieldMatrix<T> getColumnMatrix(int column) throws MatrixIndexException;
+
+   /**
+    * 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 MatrixIndexException if the specified column index is invalid
+    * @throws InvalidMatrixException if the matrix dimensions do not match one
+    * instance column
+    */
+   void setColumnMatrix(int column, FieldMatrix<T> matrix)
+       throws MatrixIndexException, InvalidMatrixException;
+
+   /**
+    * Returns the entries in row number <code>row</code>
+    * as a vector.  Row indices start at 0.
+    *
+    * @param row the row to be fetched
+    * @return row vector
+    * @throws MatrixIndexException if the specified row index is invalid
+    */
+   FieldVector<T> getRowVector(int row) throws MatrixIndexException;
+
+   /**
+    * Sets the entries in row number <code>row</code>
+    * as a vector.  Row indices start at 0.
+    *
+    * @param row the row to be set
+    * @param vector row vector (must have the same number of columns
+    * as the instance)
+    * @throws MatrixIndexException if the specified row index is invalid
+    * @throws InvalidMatrixException if the vector dimension does not match one
+    * instance row
+    */
+   void setRowVector(int row, FieldVector<T> vector)
+       throws MatrixIndexException, InvalidMatrixException;
+
+   /**
+    * Returns the entries in column number <code>column</code>
+    * as a vector.  Column indices start at 0.
+    *
+    * @param column the column to be fetched
+    * @return column vector
+    * @throws MatrixIndexException if the specified column index is invalid
+    */
+   FieldVector<T> getColumnVector(int column) throws MatrixIndexException;
+
+   /**
+    * Sets the entries in column number <code>column</code>
+    * as a vector.  Column indices start at 0.
+    *
+    * @param column the column to be set
+    * @param vector column vector (must have the same number of rows as the instance)
+    * @throws MatrixIndexException if the specified column index is invalid
+    * @throws InvalidMatrixException if the vector dimension does not match one
+    * instance column
+    */
+   void setColumnVector(int column, FieldVector<T> vector)
+       throws MatrixIndexException, InvalidMatrixException;
+
+    /**
+     * Returns the entries in row number <code>row</code> as an array.
+     * <p>
+     * Row indices start at 0.  A <code>MatrixIndexException</code> is thrown
+     * unless <code>0 <= row < rowDimension.</code></p>
+     *
+     * @param row the row to be fetched
+     * @return array of entries in the row
+     * @throws MatrixIndexException if the specified row index is not valid
+     */
+    T[] getRow(int row) throws MatrixIndexException;
+
+    /**
+     * 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 array row matrix (must have the same number of columns as the instance)
+     * @throws MatrixIndexException if the specified row index is invalid
+     * @throws InvalidMatrixException if the array size does not match one
+     * instance row
+     */
+    void setRow(int row, T[] array)
+        throws MatrixIndexException, InvalidMatrixException;
+
+    /**
+     * Returns the entries in column number <code>col</code> as an array.
+     * <p>
+     * Column indices start at 0.  A <code>MatrixIndexException</code> is thrown
+     * unless <code>0 <= column < columnDimension.</code></p>
+     *
+     * @param column the column to be fetched
+     * @return array of entries in the column
+     * @throws MatrixIndexException if the specified column index is not valid
+     */
+    T[] getColumn(int column) throws MatrixIndexException;
+
+    /**
+     * 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 array column array (must have the same number of rows as the instance)
+     * @throws MatrixIndexException if the specified column index is invalid
+     * @throws InvalidMatrixException if the array size does not match one
+     * instance column
+     */
+    void setColumn(int column, T[] array)
+        throws MatrixIndexException, InvalidMatrixException;
+
+    /**
+     * Returns the entry in the specified row and column.
+     * <p>
+     * Row and column indices start at 0 and must satisfy
+     * <ul>
+     * <li><code>0 <= row < rowDimension</code></li>
+     * <li><code> 0 <= column < columnDimension</code></li>
+     * </ul>
+     * otherwise a <code>MatrixIndexException</code> is thrown.</p>
+     *
+     * @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 MatrixIndexException if the row or column index is not valid
+     */
+    T getEntry(int row, int column) throws MatrixIndexException;
+
+    /**
+     * Set the entry in the specified row and column.
+     * <p>
+     * Row and column indices start at 0 and must satisfy
+     * <ul>
+     * <li><code>0 <= row < rowDimension</code></li>
+     * <li><code> 0 <= column < columnDimension</code></li>
+     * </ul>
+     * otherwise a <code>MatrixIndexException</code> is thrown.</p>
+     *
+     * @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 MatrixIndexException if the row or column index is not valid
+     * @since 2.0
+     */
+    void setEntry(int row, int column, T value) throws MatrixIndexException;
+
+    /**
+     * Change an entry in the specified row and column.
+     * <p>
+     * Row and column indices start at 0 and must satisfy
+     * <ul>
+     * <li><code>0 <= row < rowDimension</code></li>
+     * <li><code> 0 <= column < columnDimension</code></li>
+     * </ul>
+     * otherwise a <code>MatrixIndexException</code> is thrown.</p>
+     *
+     * @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 row,column
+     * @throws MatrixIndexException if the row or column index is not valid
+     * @since 2.0
+     */
+    void addToEntry(int row, int column, T increment) throws MatrixIndexException;
+
+    /**
+     * Change an entry in the specified row and column.
+     * <p>
+     * Row and column indices start at 0 and must satisfy
+     * <ul>
+     * <li><code>0 <= row < rowDimension</code></li>
+     * <li><code> 0 <= column < columnDimension</code></li>
+     * </ul>
+     * otherwise a <code>MatrixIndexException</code> is thrown.</p>
+     *
+     * @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 row,column
+     * @throws MatrixIndexException if the row or column index is not valid
+     * @since 2.0
+     */
+    void multiplyEntry(int row, int column, T factor) throws MatrixIndexException;
+
+    /**
+     * 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</code>.
+     *
+     * @param v the vector to operate on
+     * @return this*v
+     * @throws IllegalArgumentException if columnDimension != v.size()
+     */
+    T[] operate(T[] v) throws IllegalArgumentException;
+
+    /**
+     * Returns the result of multiplying this by the vector <code>v</code>.
+     *
+     * @param v the vector to operate on
+     * @return this*v
+     * @throws IllegalArgumentException if columnDimension != v.size()
+     */
+    FieldVector<T> operate(FieldVector<T> v) throws IllegalArgumentException;
+
+    /**
+     * Returns the (row) vector result of premultiplying this by the vector <code>v</code>.
+     *
+     * @param v the row vector to premultiply by
+     * @return v*this
+     * @throws IllegalArgumentException if rowDimension != v.size()
+     */
+    T[] preMultiply(T[] v) throws IllegalArgumentException;
+
+    /**
+     * Returns the (row) vector result of premultiplying this by the vector <code>v</code>.
+     *
+     * @param v the row vector to premultiply by
+     * @return v*this
+     * @throws IllegalArgumentException if rowDimension != v.size()
+     */
+    FieldVector<T> preMultiply(FieldVector<T> v) throws IllegalArgumentException;
+
+    /**
+     * 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.</p>
+     * @param visitor visitor used to process all matrix entries
+     * @exception  MatrixVisitorException if the visitor cannot process an entry
+     * @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)
+        throws MatrixVisitorException;
+
+    /**
+     * 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.</p>
+     * @param visitor visitor used to process all matrix entries
+     * @exception  MatrixVisitorException if the visitor cannot process an entry
+     * @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)
+        throws MatrixVisitorException;
+
+    /**
+     * 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.</p>
+     * @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
+     * @exception  MatrixVisitorException if the visitor cannot process an entry
+     * @exception MatrixIndexException  if the indices are not valid
+     * @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 MatrixIndexException, MatrixVisitorException;
+
+    /**
+     * 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.</p>
+     * @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
+     * @exception  MatrixVisitorException if the visitor cannot process an entry
+     * @exception MatrixIndexException  if the indices are not valid
+     * @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 MatrixIndexException, MatrixVisitorException;
+
+    /**
+     * 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.</p>
+     * @param visitor visitor used to process all matrix entries
+     * @exception  MatrixVisitorException if the visitor cannot process an entry
+     * @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)
+        throws MatrixVisitorException;
+
+    /**
+     * 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.</p>
+     * @param visitor visitor used to process all matrix entries
+     * @exception  MatrixVisitorException if the visitor cannot process an entry
+     * @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)
+        throws MatrixVisitorException;
+
+    /**
+     * 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.</p>
+     * @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
+     * @exception  MatrixVisitorException if the visitor cannot process an entry
+     * @exception MatrixIndexException  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 MatrixIndexException, MatrixVisitorException;
+
+    /**
+     * 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.</p>
+     * @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
+     * @exception  MatrixVisitorException if the visitor cannot process an entry
+     * @exception MatrixIndexException  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 MatrixIndexException, MatrixVisitorException;
+
+    /**
+     * 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.</p>
+     * @param visitor visitor used to process all matrix entries
+     * @exception  MatrixVisitorException if the visitor cannot process an entry
+     * @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)
+        throws MatrixVisitorException;
+
+    /**
+     * 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.</p>
+     * @param visitor visitor used to process all matrix entries
+     * @exception  MatrixVisitorException if the visitor cannot process an entry
+     * @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)
+        throws MatrixVisitorException;
+
+    /**
+     * 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.</p>
+     * @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)
+     * @exception  MatrixVisitorException if the visitor cannot process an entry
+     * @exception MatrixIndexException  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 MatrixIndexException, MatrixVisitorException;
+
+    /**
+     * 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.</p>
+     * @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)
+     * @exception  MatrixVisitorException if the visitor cannot process an entry
+     * @exception MatrixIndexException  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 MatrixIndexException, MatrixVisitorException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/FieldMatrixChangingVisitor.java b/src/main/java/org/apache/commons/math/linear/FieldMatrixChangingVisitor.java
new file mode 100644
index 0000000..c621975
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/FieldMatrixChangingVisitor.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.math.linear;
+
+import org.apache.commons.math.FieldElement;
+import org.apache.commons.math.linear.MatrixVisitorException;
+
+/**
+ * Interface defining a visitor for matrix entries.
+ *
+ * @param <T> the type of the field elements
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @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.</p>
+     * @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
+     * @throws MatrixVisitorException if something wrong occurs
+     */
+    T visit(int row, int column, T value)
+        throws MatrixVisitorException;
+
+    /**
+     * End visiting a matrix.
+     * <p>This method is called once after all entries of the matrix have been visited.</p>
+     * @return the value that the <code>walkInXxxOrder</code> must return
+     */
+    T end();
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/FieldMatrixPreservingVisitor.java b/src/main/java/org/apache/commons/math/linear/FieldMatrixPreservingVisitor.java
new file mode 100644
index 0000000..76e062e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/FieldMatrixPreservingVisitor.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.math.linear;
+
+import org.apache.commons.math.FieldElement;
+import org.apache.commons.math.linear.MatrixVisitorException;
+
+/**
+ * Interface defining a visitor for matrix entries.
+ *
+ * @param <T> the type of the field elements
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @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.</p>
+     * @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
+     * @throws MatrixVisitorException if something wrong occurs
+     */
+    void visit(int row, int column, T value)
+        throws MatrixVisitorException;
+
+    /**
+     * End visiting a matrix.
+     * <p>This method is called once after all entries of the matrix have been visited.</p>
+     * @return the value that the <code>walkInXxxOrder</code> must return
+     */
+    T end();
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/FieldVector.java b/src/main/java/org/apache/commons/math/linear/FieldVector.java
new file mode 100644
index 0000000..c68281b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/FieldVector.java
@@ -0,0 +1,358 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.linear;
+
+import org.apache.commons.math.Field;
+import org.apache.commons.math.FieldElement;
+
+/**
+ * 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>
+ * <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:
+ * </p>
+ * <pre>
+ *   RealVector result = v.mapAddToSelf(3.0).mapTanToSelf().mapSquareToSelf();
+ * </pre>
+ *
+ * @param <T> the type of the field elements
+ * @version $Revision: 811786 $ $Date: 2009-09-06 11:36:08 +0200 (dim. 06 sept. 2009) $
+ * @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 this and v.
+     * @param v vector to be added
+     * @return this + v
+     * @throws IllegalArgumentException if v is not the same size as this
+     */
+    FieldVector<T> add(FieldVector<T> v)
+        throws IllegalArgumentException;
+
+    /**
+     * Compute the sum of this and v.
+     * @param v vector to be added
+     * @return this + v
+     * @throws IllegalArgumentException if v is not the same size as this
+     */
+    FieldVector<T> add(T[] v)
+        throws IllegalArgumentException;
+
+    /**
+     * Compute this minus v.
+     * @param v vector to be subtracted
+     * @return this + v
+     * @throws IllegalArgumentException if v is not the same size as this
+     */
+    FieldVector<T> subtract(FieldVector<T> v)
+        throws IllegalArgumentException;
+
+    /**
+     * Compute this minus v.
+     * @param v vector to be subtracted
+     * @return this + v
+     * @throws IllegalArgumentException if v is not the same size as this
+     */
+    FieldVector<T> subtract(T[] v)
+        throws IllegalArgumentException;
+
+    /**
+     * Map an addition operation to each entry.
+     * @param d value to be added to each entry
+     * @return this + d
+     */
+    FieldVector<T> mapAdd(T d);
+
+    /**
+     * Map an addition operation to each entry.
+     * <p>The instance <strong>is</strong> changed by this method.</p>
+     * @param d value to be added to each entry
+     * @return for convenience, return this
+     */
+    FieldVector<T> mapAddToSelf(T d);
+
+    /**
+     * Map a subtraction operation to each entry.
+     * @param d value to be subtracted to each entry
+     * @return this - d
+     */
+    FieldVector<T> mapSubtract(T d);
+
+    /**
+     * Map a subtraction operation to each entry.
+     * <p>The instance <strong>is</strong> changed by this method.</p>
+     * @param d value to be subtracted to each entry
+     * @return for convenience, return this
+     */
+    FieldVector<T> mapSubtractToSelf(T d);
+
+    /**
+     * Map a multiplication operation to each entry.
+     * @param d value to multiply all entries by
+     * @return this * d
+     */
+    FieldVector<T> mapMultiply(T d);
+
+    /**
+     * Map a multiplication operation to each entry.
+     * <p>The instance <strong>is</strong> changed by this method.</p>
+     * @param d value to multiply all entries by
+     * @return for convenience, return this
+     */
+    FieldVector<T> mapMultiplyToSelf(T d);
+
+    /**
+     * Map a division operation to each entry.
+     * @param d value to divide all entries by
+     * @return this / d
+     */
+    FieldVector<T> mapDivide(T d);
+
+    /**
+     * Map a division operation to each entry.
+     * <p>The instance <strong>is</strong> changed by this method.</p>
+     * @param d value to divide all entries by
+     * @return for convenience, return this
+     */
+    FieldVector<T> mapDivideToSelf(T d);
+
+    /**
+     * Map the 1/x function to each entry.
+     * @return a vector containing the result of applying the function to each entry
+     */
+    FieldVector<T> mapInv();
+
+    /**
+     * Map the 1/x function to each entry.
+     * <p>The instance <strong>is</strong> changed by this method.</p>
+     * @return for convenience, return this
+     */
+    FieldVector<T> mapInvToSelf();
+
+    /**
+     * 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 IllegalArgumentException if v is not the same size as this
+     */
+    FieldVector<T> ebeMultiply(FieldVector<T> v) throws IllegalArgumentException;
+
+    /**
+     * 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 IllegalArgumentException if v is not the same size as this
+     */
+    FieldVector<T> ebeMultiply(T[] v) throws IllegalArgumentException;
+
+    /**
+     * 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 IllegalArgumentException if v is not the same size as this
+     */
+    FieldVector<T> ebeDivide(FieldVector<T> v) throws IllegalArgumentException;
+
+    /**
+     * 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 IllegalArgumentException if v is not the same size as this
+     */
+    FieldVector<T> ebeDivide(T[] v) throws IllegalArgumentException;
+
+    /**
+     * Returns vector entries as a T array.
+     * @return T array of entries
+     */
+     T[] getData();
+
+    /**
+     * Compute the dot product.
+     * @param v vector with which dot product should be computed
+     * @return the scalar dot product between instance and v
+     * @exception IllegalArgumentException if v is not the same size as this
+     */
+    T dotProduct(FieldVector<T> v)
+        throws IllegalArgumentException;
+
+    /**
+     * Compute the dot product.
+     * @param v vector with which dot product should be computed
+     * @return the scalar dot product between instance and v
+     * @exception IllegalArgumentException if v is not the same size as this
+     */
+    T dotProduct(T[] v)
+        throws IllegalArgumentException;
+
+    /** 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 v
+     * @throws IllegalArgumentException if v is not the same size as this
+     */
+    FieldVector<T> projection(FieldVector<T> v)
+        throws IllegalArgumentException;
+
+    /** 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 v
+     * @throws IllegalArgumentException if v is not the same size as this
+     */
+    FieldVector<T> projection(T[] v)
+        throws IllegalArgumentException;
+
+    /**
+     * Compute the outer product.
+     * @param v vector with which outer product should be computed
+     * @return the square matrix outer product between instance and v
+     * @exception IllegalArgumentException if v is not the same size as this
+     */
+    FieldMatrix<T> outerProduct(FieldVector<T> v)
+        throws IllegalArgumentException;
+
+    /**
+     * Compute the outer product.
+     * @param v vector with which outer product should be computed
+     * @return the square matrix outer product between instance and v
+     * @exception IllegalArgumentException if v is not the same size as this
+     */
+    FieldMatrix<T> outerProduct(T[] v)
+        throws IllegalArgumentException;
+
+    /**
+     * Returns the entry in the specified index.
+     * <p>
+     * The index start at 0 and must be lesser than the size,
+     * otherwise a {@link MatrixIndexException} is thrown.
+     * </p>
+     * @param index  index location of entry to be fetched
+     * @return vector entry at index
+     * @throws MatrixIndexException if the index is not valid
+     * @see #setEntry(int, FieldElement)
+     */
+    T getEntry(int index)
+        throws MatrixIndexException;
+
+    /**
+     * Set a single element.
+     * @param index element index.
+     * @param value new value for the element.
+     * @exception MatrixIndexException if the index is
+     * inconsistent with vector size
+     * @see #getEntry(int)
+     */
+    void setEntry(int index, T value)
+        throws MatrixIndexException;
+
+    /**
+     * 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);
+
+    /**
+     * Construct a vector by appending a T array to this vector.
+     * @param a T array to append.
+     * @return a new vector
+     */
+    FieldVector<T> append(T[] a);
+
+    /**
+     * 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.
+     * @exception MatrixIndexException if the index is
+     * inconsistent with vector size
+     */
+    FieldVector<T> getSubVector(int index, int n)
+        throws MatrixIndexException;
+
+    /**
+     * Set a set of consecutive elements.
+     * @param index index of first element to be set.
+     * @param v vector containing the values to set.
+     * @exception MatrixIndexException if the index is
+     * inconsistent with vector size
+     * @see #setSubVector(int, FieldElement[])
+     */
+    void setSubVector(int index, FieldVector<T> v)
+        throws MatrixIndexException;
+
+    /**
+     * Set a set of consecutive elements.
+     * @param index index of first element to be set.
+     * @param v vector containing the values to set.
+     * @exception MatrixIndexException if the index is
+     * inconsistent with vector size
+     * @see #setSubVector(int, FieldVector)
+     */
+    void setSubVector(int index, T[] v)
+        throws MatrixIndexException;
+
+    /**
+     * 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.</p>
+     * @return array containing a copy of vector elements
+     */
+    T[] toArray();
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/InvalidMatrixException.java b/src/main/java/org/apache/commons/math/linear/InvalidMatrixException.java
new file mode 100644
index 0000000..403188d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/InvalidMatrixException.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.math.linear;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.DummyLocalizable;
+import org.apache.commons.math.exception.util.Localizable;
+
+/**
+ * Thrown when a system attempts an operation on a matrix, and
+ * that matrix does not satisfy the preconditions for the
+ * aforementioned operation.
+ * @version $Revision: 1073253 $ $Date: 2011-02-22 09:40:05 +0100 (mar. 22 févr. 2011) $
+ */
+public class InvalidMatrixException extends MathRuntimeException {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -2068020346562029801L;
+
+    /**
+     * Construct an exception with the given message.
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @since 2.0
+     * @deprecated since 2.2 replaced by {@link #InvalidMatrixException(Localizable, Object...)}
+     */
+    @Deprecated
+    public InvalidMatrixException(final String pattern, final Object ... arguments) {
+        this(new DummyLocalizable(pattern), arguments);
+    }
+
+    /**
+     * Construct an exception with the given message.
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @since 2.2
+     */
+    public InvalidMatrixException(final Localizable pattern, final Object ... arguments) {
+        super(pattern, arguments);
+    }
+
+    /**
+     * Construct an exception with the given message.
+     * @param cause the exception or error that caused this exception
+     * to be thrown.
+     * @since 2.0
+     */
+    public InvalidMatrixException(final Throwable cause) {
+        super(cause);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/LUDecomposition.java b/src/main/java/org/apache/commons/math/linear/LUDecomposition.java
new file mode 100644
index 0000000..41a61aa
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/LUDecomposition.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.math.linear;
+
+
+/**
+ * An interface to classes that implement an algorithm to calculate the
+ * LU-decomposition of a real matrix.
+ * <p>The LU-decomposition of matrix A is a set of three matrices: P, L and U
+ * such that P&times;A = L&times;U. P is a rows permutation matrix that is used
+ * to rearrange the rows of A before so that it can be decomposed. L is a lower
+ * triangular matrix with unit diagonal terms and U is an upper triangular matrix.</p>
+ * <p>This interface is based on the class with similar name from the
+ * <a href="http://math.nist.gov/javanumerics/jama/">JAMA</a> library.</p>
+ * <ul>
+ *   <li>a {@link #getP() getP} method has been added,</li>
+ *   <li>the <code>det</code> method has been renamed as {@link #getDeterminant()
+ *   getDeterminant},</li>
+ *   <li>the <code>getDoublePivot</code> method has been removed (but the int based
+ *   {@link #getPivot() getPivot} method has been kept),</li>
+ *   <li>the <code>solve</code> and <code>isNonSingular</code> methods have been replaced
+ *   by a {@link #getSolver() getSolver} method and the equivalent methods provided by
+ *   the returned {@link DecompositionSolver}.</li>
+ * </ul>
+ *
+ * @see <a href="http://mathworld.wolfram.com/LUDecomposition.html">MathWorld</a>
+ * @see <a href="http://en.wikipedia.org/wiki/LU_decomposition">Wikipedia</a>
+ * @version $Revision: 826627 $ $Date: 2009-10-19 12:27:47 +0200 (lun. 19 oct. 2009) $
+ * @since 2.0
+ */
+public interface LUDecomposition {
+
+    /**
+     * Returns the matrix L of the decomposition.
+     * <p>L is an lower-triangular matrix</p>
+     * @return the L matrix (or null if decomposed matrix is singular)
+     */
+    RealMatrix getL();
+
+    /**
+     * Returns the matrix U of the decomposition.
+     * <p>U is an upper-triangular matrix</p>
+     * @return the U matrix (or null if decomposed matrix is singular)
+     */
+    RealMatrix getU();
+
+    /**
+     * 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>
+     * <p>The positions of the 1 elements are given by the {@link #getPivot()
+     * pivot permutation vector}.</p>
+     * @return the P rows permutation matrix (or null if decomposed matrix is singular)
+     * @see #getPivot()
+     */
+    RealMatrix getP();
+
+    /**
+     * Returns the pivot permutation vector.
+     * @return the pivot permutation vector
+     * @see #getP()
+     */
+    int[] getPivot();
+
+    /**
+     * Return the determinant of the matrix
+     * @return determinant of the matrix
+     */
+    double getDeterminant();
+
+    /**
+     * Get a solver for finding the A &times; X = B solution in exact linear sense.
+     * @return a solver
+     */
+    DecompositionSolver getSolver();
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/LUDecompositionImpl.java b/src/main/java/org/apache/commons/math/linear/LUDecompositionImpl.java
new file mode 100644
index 0000000..e5c9bbf
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/LUDecompositionImpl.java
@@ -0,0 +1,423 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.linear;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.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: PA = LU, L is lower triangular, and U is
+ * upper triangular and P is a permutation matrix. All matrices are
+ * m&times;m.</p>
+ * <p>As shown by the presence of the P matrix, this decomposition is
+ * implemented using partial pivoting.</p>
+ *
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ * @since 2.0
+ */
+public class LUDecompositionImpl implements LUDecomposition {
+
+    /** Default bound to determine effective singularity in LU decomposition */
+    private static final double DEFAULT_TOO_SMALL = 10E-12;
+
+    /** Entries of LU decomposition. */
+    private double 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 RealMatrix cachedL;
+
+    /** Cached value of U. */
+    private RealMatrix cachedU;
+
+    /** Cached value of P. */
+    private RealMatrix cachedP;
+
+    /**
+     * Calculates the LU-decomposition of the given matrix.
+     * @param matrix The matrix to decompose.
+     * @exception InvalidMatrixException if matrix is not square
+     */
+    public LUDecompositionImpl(RealMatrix matrix)
+        throws InvalidMatrixException {
+        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
+     * @exception NonSquareMatrixException if matrix is not square
+     */
+    public LUDecompositionImpl(RealMatrix matrix, double singularityThreshold)
+        throws NonSquareMatrixException {
+
+        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++) {
+
+            double sum = 0;
+
+            // upper
+            for (int row = 0; row < col; row++) {
+                final double[] luRow = lu[row];
+                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];
+                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;
+            }
+        }
+
+    }
+
+    /** {@inheritDoc} */
+    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;
+    }
+
+    /** {@inheritDoc} */
+    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;
+    }
+
+    /** {@inheritDoc} */
+    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;
+    }
+
+    /** {@inheritDoc} */
+    public int[] getPivot() {
+        return pivot.clone();
+    }
+
+    /** {@inheritDoc} */
+    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;
+        }
+    }
+
+    /** {@inheritDoc} */
+    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 double[] solve(double[] b)
+            throws IllegalArgumentException, InvalidMatrixException {
+
+            final int m = pivot.length;
+            if (b.length != m) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                        LocalizedFormats.VECTOR_LENGTH_MISMATCH, b.length, 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[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 bp;
+
+        }
+
+        /** {@inheritDoc} */
+        public RealVector solve(RealVector b)
+            throws IllegalArgumentException, InvalidMatrixException {
+            try {
+                return solve((ArrayRealVector) b);
+            } catch (ClassCastException cce) {
+
+                final int m = pivot.length;
+                if (b.getDimension() != m) {
+                    throw MathRuntimeException.createIllegalArgumentException(
+                            LocalizedFormats.VECTOR_LENGTH_MISMATCH, 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);
+
+            }
+        }
+
+        /** Solve the linear equation A &times; X = B.
+         * <p>The A matrix is implicit here. It is </p>
+         * @param b right-hand side of the equation A &times; X = B
+         * @return a vector X such that A &times; X = B
+         * @exception IllegalArgumentException if matrices dimensions don't match
+         * @exception InvalidMatrixException if decomposed matrix is singular
+         */
+        public ArrayRealVector solve(ArrayRealVector b)
+            throws IllegalArgumentException, InvalidMatrixException {
+            return new ArrayRealVector(solve(b.getDataRef()), false);
+        }
+
+        /** {@inheritDoc} */
+        public RealMatrix solve(RealMatrix b)
+            throws IllegalArgumentException, InvalidMatrixException {
+
+            final int m = pivot.length;
+            if (b.getRowDimension() != m) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                        LocalizedFormats.DIMENSIONS_MISMATCH_2x2,
+                        b.getRowDimension(), b.getColumnDimension(), m, "n");
+            }
+            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);
+
+        }
+
+        /** {@inheritDoc} */
+        public RealMatrix getInverse() throws InvalidMatrixException {
+            return solve(MatrixUtils.createRealIdentityMatrix(pivot.length));
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/MatrixIndexException.java b/src/main/java/org/apache/commons/math/linear/MatrixIndexException.java
new file mode 100644
index 0000000..6d1e0a3
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/MatrixIndexException.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.math.linear;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.DummyLocalizable;
+import org.apache.commons.math.exception.util.Localizable;
+
+/**
+ * Thrown when an operation addresses a matrix coordinate (row, col)
+ * which is outside of the dimensions of a matrix.
+ * @version $Revision: 1073255 $ $Date: 2011-02-22 09:42:06 +0100 (mar. 22 févr. 2011) $
+ */
+public class MatrixIndexException extends MathRuntimeException {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 8120540015829487660L;
+
+    /**
+     * Constructs a new instance with specified formatted detail message.
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @deprecated as of 2.2 replaced by {@link #MatrixIndexException(Localizable, Object...)}
+     */
+    @Deprecated
+    public MatrixIndexException(final String pattern, final Object ... arguments) {
+      this(new DummyLocalizable(pattern), arguments);
+    }
+
+    /**
+     * Constructs a new instance with specified formatted detail message.
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @since 2.2
+     */
+    public MatrixIndexException(final Localizable pattern, final Object ... arguments) {
+      super(pattern, arguments);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/MatrixUtils.java b/src/main/java/org/apache/commons/math/linear/MatrixUtils.java
new file mode 100644
index 0000000..4917b89
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/MatrixUtils.java
@@ -0,0 +1,957 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.linear;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.lang.reflect.Array;
+import java.math.BigDecimal;
+import java.util.Arrays;
+
+import org.apache.commons.math.Field;
+import org.apache.commons.math.FieldElement;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.fraction.BigFraction;
+import org.apache.commons.math.fraction.Fraction;
+
+/**
+ * A collection of static methods that operate on or return matrices.
+ *
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ */
+public class MatrixUtils {
+
+    /**
+     * 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>
+     * <p>The matrix elements are all set to 0.0.</p>
+     * @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>
+     * <p>The matrix elements are all set to field.getZero().</p>
+     * @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>
+     * <p>The input array is copied, not referenced.</p>
+     *
+     * @param data input array
+     * @return  RealMatrix containing the values of the array
+     * @throws IllegalArgumentException if <code>data</code> is not rectangular
+     *  (not all rows have the same length) or empty
+     * @throws NullPointerException if either <code>data</code> or
+     * <code>data[0]</code> is null
+     * @see #createRealMatrix(int, int)
+     */
+    public static RealMatrix createRealMatrix(double[][] data) {
+        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>
+     * <p>The input array is copied, not referenced.</p>
+     * @param <T> the type of the field elements
+     * @param data input array
+     * @return  RealMatrix containing the values of the array
+     * @throws IllegalArgumentException if <code>data</code> is not rectangular
+     *  (not all rows have the same length) or empty
+     * @throws NullPointerException if either <code>data</code> or
+     * <code>data[0]</code> is null
+     * @see #createFieldMatrix(Field, int, int)
+     * @since 2.0
+     */
+    public static <T extends FieldElement<T>> FieldMatrix<T> createFieldMatrix(T[][] data) {
+        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();
+        @SuppressWarnings("unchecked") // zero is type T
+        final T[][] d = (T[][]) Array.newInstance(zero.getClass(), new int[] { 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>(d, false);
+    }
+
+    /**
+     * 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
+     * @deprecated since 2.0, replaced by {@link #createFieldIdentityMatrix(Field, int)}
+     */
+    @Deprecated
+    public static BigMatrix createBigIdentityMatrix(int dimension) {
+        final BigDecimal[][] d = new BigDecimal[dimension][dimension];
+        for (int row = 0; row < dimension; row++) {
+            final BigDecimal[] dRow = d[row];
+            Arrays.fill(dRow, BigMatrixImpl.ZERO);
+            dRow[row] = BigMatrixImpl.ONE;
+        }
+        return new BigMatrixImpl(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;
+    }
+
+    /**
+     * Returns a {@link BigMatrix} whose entries are the the values in the
+     * the input array.  The input array is copied, not referenced.
+     *
+     * @param data input array
+     * @return  RealMatrix containing the values of the array
+     * @throws IllegalArgumentException if <code>data</code> is not rectangular
+     *  (not all rows have the same length) or empty
+     * @throws NullPointerException if data is null
+     * @deprecated since 2.0 replaced by {@link #createFieldMatrix(FieldElement[][])}
+     */
+    @Deprecated
+    public static BigMatrix createBigMatrix(double[][] data) {
+        return new BigMatrixImpl(data);
+    }
+
+    /**
+     * Returns a {@link BigMatrix} whose entries are the the values in the
+     * the input array.  The input array is copied, not referenced.
+     *
+     * @param data input array
+     * @return  RealMatrix containing the values of the array
+     * @throws IllegalArgumentException if <code>data</code> is not rectangular
+     *  (not all rows have the same length) or empty
+     * @throws NullPointerException if data is null
+     * @deprecated since 2.0 replaced by {@link #createFieldMatrix(FieldElement[][])}
+     */
+    @Deprecated
+    public static BigMatrix createBigMatrix(BigDecimal[][] data) {
+        return new BigMatrixImpl(data);
+    }
+
+    /**
+     * Returns a {@link BigMatrix} whose entries are the the values in the
+     * the input array.
+     * <p>If an array is built specially in order to be embedded in a
+     * BigMatrix and not used directly, the <code>copyArray</code> may be
+     * set to <code>false</code. This will prevent the copying and improve
+     * performance as no new array will be built and no data will be copied.</p>
+     * @param data data for new matrix
+     * @param copyArray if true, the input array will be copied, otherwise
+     * it will be referenced
+     * @return  BigMatrix containing the values of the array
+     * @throws IllegalArgumentException if <code>data</code> is not rectangular
+     *  (not all rows have the same length) or empty
+     * @throws NullPointerException if <code>data</code> is null
+     * @see #createRealMatrix(double[][])
+     * @deprecated since 2.0 replaced by {@link #createFieldMatrix(FieldElement[][])}
+     */
+    @Deprecated
+    public static BigMatrix createBigMatrix(BigDecimal[][] data, boolean copyArray) {
+        return new BigMatrixImpl(data, copyArray);
+    }
+
+    /**
+     * Returns a {@link BigMatrix} whose entries are the the values in the
+     * the input array.  The input array is copied, not referenced.
+     *
+     * @param data input array
+     * @return  RealMatrix containing the values of the array
+     * @throws IllegalArgumentException if <code>data</code> is not rectangular
+     *  (not all rows have the same length) or empty
+     * @throws NullPointerException if data is null
+     * @deprecated since 2.0 replaced by {@link #createFieldMatrix(FieldElement[][])}
+     */
+    @Deprecated
+    public static BigMatrix createBigMatrix(String[][] data) {
+        return new BigMatrixImpl(data);
+    }
+
+    /**
+     * Creates a {@link RealVector} using the data from the input array.
+     *
+     * @param data the input data
+     * @return a data.length RealVector
+     * @throws IllegalArgumentException if <code>data</code> is empty
+     * @throws NullPointerException if <code>data</code>is null
+     */
+    public static RealVector createRealVector(double[] data) {
+        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 IllegalArgumentException if <code>data</code> is empty
+     * @throws NullPointerException if <code>data</code>is null
+     */
+    public static <T extends FieldElement<T>> FieldVector<T> createFieldVector(final T[] data) {
+        return new ArrayFieldVector<T>(data, true);
+    }
+
+    /**
+     * Creates 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 IllegalArgumentException if <code>rowData</code> is empty
+     * @throws NullPointerException if <code>rowData</code>is null
+     */
+    public static RealMatrix createRowRealMatrix(double[] rowData) {
+        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;
+    }
+
+    /**
+     * Creates 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 IllegalArgumentException if <code>rowData</code> is empty
+     * @throws NullPointerException if <code>rowData</code>is null
+     */
+    public static <T extends FieldElement<T>> FieldMatrix<T>
+        createRowFieldMatrix(final T[] rowData) {
+        final int nCols = rowData.length;
+        if (nCols == 0) {
+            throw MathRuntimeException.createIllegalArgumentException(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 row {@link BigMatrix} using the data from the input
+     * array.
+     *
+     * @param rowData the input row data
+     * @return a 1 x rowData.length BigMatrix
+     * @throws IllegalArgumentException if <code>rowData</code> is empty
+     * @throws NullPointerException if <code>rowData</code>is null
+     * @deprecated since 2.0 replaced by {@link #createRowFieldMatrix(FieldElement[])}
+     */
+    @Deprecated
+    public static BigMatrix createRowBigMatrix(double[] rowData) {
+        final int nCols = rowData.length;
+        final BigDecimal[][] data = new BigDecimal[1][nCols];
+        for (int i = 0; i < nCols; ++i) {
+            data[0][i] = new BigDecimal(rowData[i]);
+        }
+        return new BigMatrixImpl(data, false);
+    }
+
+    /**
+     * Creates a row {@link BigMatrix} using the data from the input
+     * array.
+     *
+     * @param rowData the input row data
+     * @return a 1 x rowData.length BigMatrix
+     * @throws IllegalArgumentException if <code>rowData</code> is empty
+     * @throws NullPointerException if <code>rowData</code>is null
+     * @deprecated since 2.0 replaced by {@link #createRowFieldMatrix(FieldElement[])}
+     */
+    @Deprecated
+    public static BigMatrix createRowBigMatrix(BigDecimal[] rowData) {
+        final int nCols = rowData.length;
+        final BigDecimal[][] data = new BigDecimal[1][nCols];
+        System.arraycopy(rowData, 0, data[0], 0, nCols);
+        return new BigMatrixImpl(data, false);
+    }
+
+    /**
+     * Creates a row {@link BigMatrix} using the data from the input
+     * array.
+     *
+     * @param rowData the input row data
+     * @return a 1 x rowData.length BigMatrix
+     * @throws IllegalArgumentException if <code>rowData</code> is empty
+     * @throws NullPointerException if <code>rowData</code>is null
+     * @deprecated since 2.0 replaced by {@link #createRowFieldMatrix(FieldElement[])}
+     */
+    @Deprecated
+    public static BigMatrix createRowBigMatrix(String[] rowData) {
+        final int nCols = rowData.length;
+        final BigDecimal[][] data = new BigDecimal[1][nCols];
+        for (int i = 0; i < nCols; ++i) {
+            data[0][i] = new BigDecimal(rowData[i]);
+        }
+        return new BigMatrixImpl(data, false);
+    }
+
+    /**
+     * 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 IllegalArgumentException if <code>columnData</code> is empty
+     * @throws NullPointerException if <code>columnData</code>is null
+     */
+    public static RealMatrix createColumnRealMatrix(double[] columnData) {
+        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 IllegalArgumentException if <code>columnData</code> is empty
+     * @throws NullPointerException if <code>columnData</code>is null
+     */
+    public static <T extends FieldElement<T>> FieldMatrix<T>
+        createColumnFieldMatrix(final T[] columnData) {
+        final int nRows = columnData.length;
+        if (nRows == 0) {
+            throw MathRuntimeException.createIllegalArgumentException(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;
+    }
+
+    /**
+     * Creates a column {@link BigMatrix} using the data from the input
+     * array.
+     *
+     * @param columnData  the input column data
+     * @return a columnData x 1 BigMatrix
+     * @throws IllegalArgumentException if <code>columnData</code> is empty
+     * @throws NullPointerException if <code>columnData</code>is null
+     * @deprecated since 2.0 replaced by {@link #createColumnFieldMatrix(FieldElement[])}
+     */
+    @Deprecated
+    public static BigMatrix createColumnBigMatrix(double[] columnData) {
+        final int nRows = columnData.length;
+        final BigDecimal[][] data = new BigDecimal[nRows][1];
+        for (int row = 0; row < nRows; row++) {
+            data[row][0] = new BigDecimal(columnData[row]);
+        }
+        return new BigMatrixImpl(data, false);
+    }
+
+    /**
+     * Creates a column {@link BigMatrix} using the data from the input
+     * array.
+     *
+     * @param columnData  the input column data
+     * @return a columnData x 1 BigMatrix
+     * @throws IllegalArgumentException if <code>columnData</code> is empty
+     * @throws NullPointerException if <code>columnData</code>is null
+     * @deprecated since 2.0 replaced by {@link #createColumnFieldMatrix(FieldElement[])}
+     */
+    @Deprecated
+    public static BigMatrix createColumnBigMatrix(BigDecimal[] columnData) {
+        final int nRows = columnData.length;
+        final BigDecimal[][] data = new BigDecimal[nRows][1];
+        for (int row = 0; row < nRows; row++) {
+            data[row][0] = columnData[row];
+        }
+        return new BigMatrixImpl(data, false);
+    }
+
+    /**
+     * Creates a column {@link BigMatrix} using the data from the input
+     * array.
+     *
+     * @param columnData  the input column data
+     * @return a columnData x 1 BigMatrix
+     * @throws IllegalArgumentException if <code>columnData</code> is empty
+     * @throws NullPointerException if <code>columnData</code>is null
+     * @deprecated since 2.0 replaced by {@link #createColumnFieldMatrix(FieldElement[])}
+     */
+    @Deprecated
+    public static BigMatrix createColumnBigMatrix(String[] columnData) {
+        int nRows = columnData.length;
+        final BigDecimal[][] data = new BigDecimal[nRows][1];
+        for (int row = 0; row < nRows; row++) {
+            data[row][0] = new BigDecimal(columnData[row]);
+        }
+        return new BigMatrixImpl(data, false);
+    }
+
+    /**
+     * Check if a row index is valid.
+     * @param m matrix containing the submatrix
+     * @param row row index to check
+     * @exception MatrixIndexException if index is not valid
+     */
+    public static void checkRowIndex(final AnyMatrix m, final int row) {
+        if (row < 0 || row >= m.getRowDimension()) {
+            throw new MatrixIndexException(LocalizedFormats.ROW_INDEX_OUT_OF_RANGE,
+                                           row, 0, m.getRowDimension() - 1);
+        }
+    }
+
+    /**
+     * Check if a column index is valid.
+     * @param m matrix containing the submatrix
+     * @param column column index to check
+     * @exception MatrixIndexException if index is not valid
+     */
+    public static void checkColumnIndex(final AnyMatrix m, final int column)
+        throws MatrixIndexException {
+        if (column < 0 || column >= m.getColumnDimension()) {
+            throw new MatrixIndexException(LocalizedFormats.COLUMN_INDEX_OUT_OF_RANGE,
+                                           column, 0, m.getColumnDimension() - 1);
+        }
+    }
+
+    /**
+     * Check if submatrix ranges indices are valid.
+     * Rows and columns are indicated counting from 0 to n-1.
+     *
+     * @param m matrix containing the submatrix
+     * @param startRow Initial row index
+     * @param endRow Final row index
+     * @param startColumn Initial column index
+     * @param endColumn Final column index
+     * @exception MatrixIndexException  if the indices are not valid
+     */
+    public static void checkSubMatrixIndex(final AnyMatrix m,
+                                           final int startRow, final int endRow,
+                                           final int startColumn, final int endColumn) {
+        checkRowIndex(m, startRow);
+        checkRowIndex(m, endRow);
+        if (startRow > endRow) {
+            throw new MatrixIndexException(LocalizedFormats.INITIAL_ROW_AFTER_FINAL_ROW,
+                                           startRow, endRow);
+        }
+
+        checkColumnIndex(m, startColumn);
+        checkColumnIndex(m, endColumn);
+        if (startColumn > endColumn) {
+            throw new MatrixIndexException(LocalizedFormats.INITIAL_COLUMN_AFTER_FINAL_COLUMN,
+                                           startColumn, endColumn);
+        }
+
+
+    }
+
+    /**
+     * Check if submatrix ranges indices are valid.
+     * Rows and columns are indicated counting from 0 to n-1.
+     *
+     * @param m matrix containing the submatrix
+     * @param selectedRows Array of row indices.
+     * @param selectedColumns Array of column indices.
+     * @exception MatrixIndexException if row or column selections are not valid
+     */
+    public static void checkSubMatrixIndex(final AnyMatrix m,
+                                           final int[] selectedRows, final int[] selectedColumns)
+        throws MatrixIndexException {
+        if (selectedRows.length * selectedColumns.length == 0) {
+            if (selectedRows.length == 0) {
+                throw new MatrixIndexException(LocalizedFormats.EMPTY_SELECTED_ROW_INDEX_ARRAY);
+            }
+            throw new MatrixIndexException(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
+     * @exception IllegalArgumentException if matrices are not addition compatible
+     */
+    public static void checkAdditionCompatible(final AnyMatrix left, final AnyMatrix right)
+        throws IllegalArgumentException {
+        if ((left.getRowDimension()    != right.getRowDimension()) ||
+            (left.getColumnDimension() != right.getColumnDimension())) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.NOT_ADDITION_COMPATIBLE_MATRICES,
+                    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
+     * @exception IllegalArgumentException if matrices are not subtraction compatible
+     */
+    public static void checkSubtractionCompatible(final AnyMatrix left, final AnyMatrix right)
+        throws IllegalArgumentException {
+        if ((left.getRowDimension()    != right.getRowDimension()) ||
+            (left.getColumnDimension() != right.getColumnDimension())) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.NOT_SUBTRACTION_COMPATIBLE_MATRICES,
+                    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
+     * @exception IllegalArgumentException if matrices are not multiplication compatible
+     */
+    public static void checkMultiplicationCompatible(final AnyMatrix left, final AnyMatrix right)
+        throws IllegalArgumentException {
+        if (left.getColumnDimension() != right.getRowDimension()) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.NOT_MULTIPLICATION_COMPATIBLE_MATRICES,
+                    left.getRowDimension(), left.getColumnDimension(),
+                    right.getRowDimension(), right.getColumnDimension());
+        }
+    }
+
+    /**
+     * Convert a {@link FieldMatrix}/{@link Fraction} matrix to a {@link RealMatrix}.
+     * @param m matrix to convert
+     * @return 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. */
+        public 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 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 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. */
+        public 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 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>
+     * <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>
+     * </p>
+     *
+     * @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.
+     * </p>
+     * @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>
+     * <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>
+     * </p>
+     *
+     * @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.
+     * </p>
+     * @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;
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/MatrixVisitorException.java b/src/main/java/org/apache/commons/math/linear/MatrixVisitorException.java
new file mode 100644
index 0000000..de25f16
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/MatrixVisitorException.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.math.linear;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.DummyLocalizable;
+import org.apache.commons.math.exception.util.Localizable;
+
+/**
+ * Thrown when a visitor encounters an error while processing a matrix entry.
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ */
+public class MatrixVisitorException extends MathRuntimeException {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 3814333035048617048L;
+
+    /**
+     * Constructs a new instance with specified formatted detail message.
+     * @param pattern format specifier
+     * @param arguments format arguments
+     */
+    public MatrixVisitorException(final String pattern, final Object[] arguments) {
+      super(new DummyLocalizable(pattern), arguments);
+    }
+
+    /**
+     * Constructs a new instance with specified formatted detail message.
+     * @param pattern format specifier
+     * @param arguments format arguments
+     * @since 2.2
+     */
+    public MatrixVisitorException(final Localizable pattern, final Object[] arguments) {
+      super(pattern, arguments);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/NonSquareMatrixException.java b/src/main/java/org/apache/commons/math/linear/NonSquareMatrixException.java
new file mode 100644
index 0000000..291c0e5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/NonSquareMatrixException.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.math.linear;
+
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+
+/**
+ * Thrown when an operation defined only for square matrices is applied to non-square ones.
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ * @since 2.0
+ */
+public class NonSquareMatrixException extends InvalidMatrixException {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 8996207526636673730L;
+
+    /**
+     * Construct an exception with the given message.
+     * @param rows number of rows of the faulty matrix
+     * @param columns number of columns of the faulty matrix
+     */
+    public NonSquareMatrixException(final int rows, final int columns) {
+        super(LocalizedFormats.NON_SQUARE_MATRIX, rows, columns);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/NotPositiveDefiniteMatrixException.java b/src/main/java/org/apache/commons/math/linear/NotPositiveDefiniteMatrixException.java
new file mode 100644
index 0000000..f7ede55
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/NotPositiveDefiniteMatrixException.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.math.linear;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * This class represents exceptions thrown when a matrix expected to
+ * be positive definite is not.
+ *
+ * @since 1.2
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ */
+
+public class NotPositiveDefiniteMatrixException extends MathException {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 4122929125438624648L;
+
+    /** Simple constructor.
+     * build an exception with a default message.
+     */
+    public NotPositiveDefiniteMatrixException() {
+        super(LocalizedFormats.NOT_POSITIVE_DEFINITE_MATRIX);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/NotSymmetricMatrixException.java b/src/main/java/org/apache/commons/math/linear/NotSymmetricMatrixException.java
new file mode 100644
index 0000000..e422079
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/NotSymmetricMatrixException.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.math.linear;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * This class represents exceptions thrown when a matrix expected to
+ * be symmetric is not
+ *
+ * @since 2.0
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ */
+
+public class NotSymmetricMatrixException extends MathException {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -7012803946709786097L;
+
+    /** Simple constructor.
+     * build an exception with a default message.
+     */
+    public NotSymmetricMatrixException() {
+        super(LocalizedFormats.NOT_SYMMETRIC_MATRIX);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/OpenMapRealMatrix.java b/src/main/java/org/apache/commons/math/linear/OpenMapRealMatrix.java
new file mode 100644
index 0000000..e651320
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/OpenMapRealMatrix.java
@@ -0,0 +1,292 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.linear;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.util.OpenIntToDoubleHashMap;
+
+/**
+ * Sparse matrix implementation based on an open addressed map.
+ *
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ * @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
+     */
+    public OpenMapRealMatrix(int rowDimension, int columnDimension) {
+        super(rowDimension, columnDimension);
+        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} */
+    @Override
+    public OpenMapRealMatrix createMatrix(int rowDimension, int columnDimension)
+            throws IllegalArgumentException {
+        return new OpenMapRealMatrix(rowDimension, columnDimension);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getColumnDimension() {
+        return columns;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public OpenMapRealMatrix add(final RealMatrix m)
+        throws IllegalArgumentException {
+        try {
+            return add((OpenMapRealMatrix) m);
+        } catch (ClassCastException cce) {
+            return (OpenMapRealMatrix) super.add(m);
+        }
+    }
+
+    /**
+     * Compute the sum of this and <code>m</code>.
+     *
+     * @param m    matrix to be added
+     * @return     this + m
+     * @throws  IllegalArgumentException if m is not the same size as this
+     */
+    public OpenMapRealMatrix add(OpenMapRealMatrix m) throws IllegalArgumentException {
+
+        // safety check
+        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 IllegalArgumentException {
+        try {
+            return subtract((OpenMapRealMatrix) m);
+        } catch (ClassCastException cce) {
+            return (OpenMapRealMatrix) super.subtract(m);
+        }
+    }
+
+    /**
+     * Compute this minus <code>m</code>.
+     *
+     * @param m    matrix to be subtracted
+     * @return     this - m
+     * @throws  IllegalArgumentException if m is not the same size as this
+     */
+    public OpenMapRealMatrix subtract(OpenMapRealMatrix m) throws IllegalArgumentException {
+
+        // safety check
+        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 RealMatrix multiply(final RealMatrix m)
+        throws IllegalArgumentException {
+        try {
+            return multiply((OpenMapRealMatrix) m);
+        } catch (ClassCastException cce) {
+
+            // safety check
+            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;
+
+        }
+    }
+
+    /**
+     * Returns the result of postmultiplying this by m.
+     *
+     * @param m    matrix to postmultiply by
+     * @return     this * m
+     * @throws     IllegalArgumentException
+     *             if columnDimension(this) != rowDimension(m)
+     */
+    public OpenMapRealMatrix multiply(OpenMapRealMatrix m) throws IllegalArgumentException {
+
+        // 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 MatrixIndexException {
+        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 MatrixIndexException {
+        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 MatrixIndexException {
+        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 MatrixIndexException {
+        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/math/linear/OpenMapRealVector.java b/src/main/java/org/apache/commons/math/linear/OpenMapRealVector.java
new file mode 100644
index 0000000..b1d1912
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/OpenMapRealVector.java
@@ -0,0 +1,915 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.linear;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.util.OpenIntToDoubleHashMap;
+import org.apache.commons.math.util.OpenIntToDoubleHashMap.Iterator;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * This class implements the {@link RealVector} interface with a {@link OpenIntToDoubleHashMap} backing store.
+ * @version $Revision: 1073262 $ $Date: 2011-02-22 10:02:25 +0100 (mar. 22 févr. 2011) $
+ * @since 2.0
+*/
+public class OpenMapRealVector extends AbstractRealVector implements SparseRealVector, 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.
+     * <p>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</code> method ({@link #append(double)}, {@link
+     * #append(double[])}, {@link #append(RealVector)}) to gather data
+     * into this vector.</p>
+     */
+    public OpenMapRealVector() {
+        this(0, DEFAULT_ZERO_TOLERANCE);
+    }
+
+    /**
+     * Construct a (dimension)-length vector of zeros.
+     * @param dimension size of the vector
+     */
+    public OpenMapRealVector(int dimension) {
+        this(dimension, DEFAULT_ZERO_TOLERANCE);
+    }
+
+    /**
+     * Construct a (dimension)-length vector of zeros, specifying zero tolerance.
+     * @param dimension Size of the vector
+     * @param epsilon The tolerance for having 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 The original vector
+     * @param resize The amount to resize it
+     */
+    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 The 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 The size of the vector
+     * @param expectedSize The expected number of non-zero entries
+     * @param epsilon The tolerance for having a value considered zero
+     */
+    public OpenMapRealVector(int dimension, int expectedSize, double epsilon) {
+        virtualSize = dimension;
+        entries = new OpenIntToDoubleHashMap(expectedSize, 0.0);
+        this.epsilon = epsilon;
+    }
+
+    /**
+     * Create from a double 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 a double array, specifying zero tolerance.
+     * Only non-zero entries will be stored
+     * @param values The set of values to create from
+     * @param epsilon The tolerance for having a value 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 a Double 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 a Double array.
+     * Only non-zero entries will be stored
+     * @param values The set of values to create from
+     * @param epsilon The tolerance for having a value 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 The instance to copy from
+     */
+    public OpenMapRealVector(OpenMapRealVector v) {
+        virtualSize = v.getDimension();
+        entries = new OpenIntToDoubleHashMap(v.getEntries());
+        epsilon = v.epsilon;
+    }
+
+    /**
+     * Generic copy constructor.
+     * @param v The 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 entries of this instance
+     */
+    private OpenIntToDoubleHashMap getEntries() {
+        return entries;
+    }
+
+    /**
+     * Determine if this value is within epsilon of zero.
+     * @param value The value to test
+     * @return <code>true</code> if this value is within epsilon to zero, <code>false</code> otherwise
+     * @since 2.1
+     */
+    protected boolean isDefaultValue(double value) {
+        return FastMath.abs(value) < epsilon;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealVector add(RealVector v) throws IllegalArgumentException {
+        checkVectorDimensions(v.getDimension());
+        if (v instanceof OpenMapRealVector) {
+            return add((OpenMapRealVector) v);
+        } else {
+            return super.add(v);
+        }
+    }
+
+    /**
+     * Optimized method to add two OpenMapRealVectors.  Copies the larger vector, iterates over the smaller.
+     * @param v Vector to add with
+     * @return The sum of <code>this</code> with <code>v</code>
+     * @throws IllegalArgumentException If the dimensions don't match
+     */
+    public OpenMapRealVector add(OpenMapRealVector v) throws IllegalArgumentException{
+        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</code> 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} */
+    public OpenMapRealVector append(RealVector v) {
+        if (v instanceof OpenMapRealVector) {
+            return append((OpenMapRealVector) v);
+        }
+        return append(v.getData());
+    }
+
+    /** {@inheritDoc} */
+    public OpenMapRealVector append(double d) {
+        OpenMapRealVector res = new OpenMapRealVector(this, 1);
+        res.setEntry(virtualSize, d);
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    public OpenMapRealVector append(double[] a) {
+        OpenMapRealVector res = new OpenMapRealVector(this, a.length);
+        for (int i = 0; i < a.length; i++) {
+            res.setEntry(i + virtualSize, a[i]);
+        }
+        return res;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @since 2.1
+     */
+    @Override
+    public OpenMapRealVector copy() {
+        return new OpenMapRealVector(this);
+    }
+
+    /**
+     * Optimized method to compute the dot product with an OpenMapRealVector.
+     * Iterates over the smaller of the two.
+     * @param v The vector to compute the dot product with
+     * @return The dot product of <code>this</code> and <code>v</code>
+     * @throws IllegalArgumentException If the dimensions don't match
+     */
+    public double dotProduct(OpenMapRealVector v) throws IllegalArgumentException {
+        checkVectorDimensions(v.getDimension());
+        boolean thisIsSmaller  = entries.size() < v.entries.size();
+        Iterator iter = thisIsSmaller  ? entries.iterator() : v.entries.iterator();
+        OpenIntToDoubleHashMap larger = thisIsSmaller  ? v.entries : entries;
+        double d = 0;
+        while(iter.hasNext()) {
+            iter.advance();
+            d += iter.value() * larger.get(iter.key());
+        }
+        return d;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double dotProduct(RealVector v) throws IllegalArgumentException {
+        if(v instanceof OpenMapRealVector) {
+            return dotProduct((OpenMapRealVector)v);
+        } else {
+            return super.dotProduct(v);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public OpenMapRealVector ebeDivide(RealVector v) throws IllegalArgumentException {
+        checkVectorDimensions(v.getDimension());
+        OpenMapRealVector res = new OpenMapRealVector(this);
+        Iterator iter = res.entries.iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            res.setEntry(iter.key(), iter.value() / v.getEntry(iter.key()));
+        }
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public OpenMapRealVector ebeDivide(double[] v) throws IllegalArgumentException {
+        checkVectorDimensions(v.length);
+        OpenMapRealVector res = new OpenMapRealVector(this);
+        Iterator iter = res.entries.iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            res.setEntry(iter.key(), iter.value() / v[iter.key()]);
+        }
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    public OpenMapRealVector ebeMultiply(RealVector v) throws IllegalArgumentException {
+        checkVectorDimensions(v.getDimension());
+        OpenMapRealVector res = new OpenMapRealVector(this);
+        Iterator iter = res.entries.iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            res.setEntry(iter.key(), iter.value() * v.getEntry(iter.key()));
+        }
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public OpenMapRealVector ebeMultiply(double[] v) throws IllegalArgumentException {
+        checkVectorDimensions(v.length);
+        OpenMapRealVector res = new OpenMapRealVector(this);
+        Iterator iter = res.entries.iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            res.setEntry(iter.key(), iter.value() * v[iter.key()]);
+        }
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    public OpenMapRealVector getSubVector(int index, int n) throws MatrixIndexException {
+        checkIndex(index);
+        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 double[] getData() {
+        double[] res = new double[virtualSize];
+        Iterator iter = entries.iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            res[iter.key()] = iter.value();
+        }
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    public int getDimension() {
+        return virtualSize;
+    }
+
+    /**
+     * Optimized method to compute distance.
+     * @param v The vector to compute distance to
+     * @return The distance from <code>this</code> and <code>v</code>
+     * @throws IllegalArgumentException If the dimensions don't match
+     */
+    public double getDistance(OpenMapRealVector v) throws IllegalArgumentException {
+        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 IllegalArgumentException {
+        checkVectorDimensions(v.getDimension());
+        if (v instanceof OpenMapRealVector) {
+            return getDistance((OpenMapRealVector) v);
+        }
+        return getDistance(v.getData());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getDistance(double[] v) throws IllegalArgumentException {
+        checkVectorDimensions(v.length);
+        double res = 0;
+        for (int i = 0; i < v.length; i++) {
+            double delta = entries.get(i) - v[i];
+            res += delta * delta;
+        }
+        return FastMath.sqrt(res);
+    }
+
+    /** {@inheritDoc} */
+    public double getEntry(int index) throws MatrixIndexException {
+        checkIndex(index);
+        return entries.get(index);
+    }
+
+    /**
+     * 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
+     * elements differences.</p>
+     * @param v vector to which distance is requested
+     * @return distance between two vectors.
+     */
+    public double getL1Distance(OpenMapRealVector v) {
+        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 IllegalArgumentException {
+        checkVectorDimensions(v.getDimension());
+        if (v instanceof OpenMapRealVector) {
+            return getL1Distance((OpenMapRealVector) v);
+        }
+        return getL1Distance(v.getData());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getL1Distance(double[] v) throws IllegalArgumentException {
+        checkVectorDimensions(v.length);
+        double max = 0;
+        for (int i = 0; i < v.length; i++) {
+            double delta = FastMath.abs(getEntry(i) - v[i]);
+            max += delta;
+        }
+        return max;
+    }
+
+    /**
+     * Optimized method to compute LInfDistance.
+     * @param v The vector to compute from
+     * @return the LInfDistance
+     */
+    private double getLInfDistance(OpenMapRealVector v) {
+        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)) {
+                if (iter.value() > max) {
+                    max = iter.value();
+                }
+            }
+        }
+        return max;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getLInfDistance(RealVector v) throws IllegalArgumentException {
+        checkVectorDimensions(v.getDimension());
+        if (v instanceof OpenMapRealVector) {
+            return getLInfDistance((OpenMapRealVector) v);
+        }
+        return getLInfDistance(v.getData());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getLInfDistance(double[] v) throws IllegalArgumentException {
+        checkVectorDimensions(v.length);
+        double max = 0;
+        for (int i = 0; i < v.length; i++) {
+            double delta = FastMath.abs(getEntry(i) - v[i]);
+            if (delta > max) {
+                max = delta;
+            }
+        }
+        return max;
+    }
+
+    /** {@inheritDoc} */
+    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} */
+    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 RealMatrix outerProduct(double[] v) throws IllegalArgumentException {
+        checkVectorDimensions(v.length);
+        RealMatrix res = new OpenMapRealMatrix(virtualSize, virtualSize);
+        Iterator iter = entries.iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            int row = iter.key();
+            double value = iter.value();
+            for (int col = 0; col < virtualSize; col++) {
+                res.setEntry(row, col, value * v[col]);
+            }
+        }
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    public RealVector projection(RealVector v) throws IllegalArgumentException {
+        checkVectorDimensions(v.getDimension());
+        return v.mapMultiply(dotProduct(v) / v.dotProduct(v));
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public OpenMapRealVector projection(double[] v) throws IllegalArgumentException {
+        checkVectorDimensions(v.length);
+        return (OpenMapRealVector) projection(new OpenMapRealVector(v));
+    }
+
+    /** {@inheritDoc} */
+    public void setEntry(int index, double value) throws MatrixIndexException {
+        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 MatrixIndexException {
+        checkIndex(index);
+        checkIndex(index + v.getDimension() - 1);
+        setSubVector(index, v.getData());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setSubVector(int index, double[] v) throws MatrixIndexException {
+        checkIndex(index);
+        checkIndex(index + v.length - 1);
+        for (int i = 0; i < v.length; i++) {
+            setEntry(i + index, v[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 The vector to subtract from <code>this</code>
+     * @return The difference of <code>this</code> and <code>v</code>
+     * @throws IllegalArgumentException If the dimensions don't match
+     */
+    public OpenMapRealVector subtract(OpenMapRealVector v) throws IllegalArgumentException{
+        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 OpenMapRealVector subtract(RealVector v) throws IllegalArgumentException {
+        checkVectorDimensions(v.getDimension());
+        if (v instanceof OpenMapRealVector) {
+            return subtract((OpenMapRealVector) v);
+        }
+        return subtract(v.getData());
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public OpenMapRealVector subtract(double[] v) throws IllegalArgumentException {
+        checkVectorDimensions(v.length);
+        OpenMapRealVector res = new OpenMapRealVector(this);
+        for (int i = 0; i < v.length; i++) {
+            if (entries.containsKey(i)) {
+                res.setEntry(i, entries.get(i) - v[i]);
+            } else {
+                res.setEntry(i, -v[i]);
+            }
+        }
+        return res;
+    }
+
+
+    /** {@inheritDoc} */
+    @Override
+    public OpenMapRealVector unitVector() {
+        OpenMapRealVector res = copy();
+        res.unitize();
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void unitize() {
+        double norm = getNorm();
+        if (isDefaultValue(norm)) {
+            throw  MathRuntimeException.createArithmeticException(LocalizedFormats.CANNOT_NORMALIZE_A_ZERO_NORM_VECTOR);
+        }
+        Iterator iter = entries.iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            entries.put(iter.key(), iter.value() / norm);
+        }
+
+    }
+
+
+    /** {@inheritDoc} */
+    @Override
+    public double[] toArray() {
+        return getData();
+    }
+
+    /** {@inheritDoc}
+     * <p> 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()}.</p>
+     */
+    @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;
+    }
+
+    /**
+     * <p> 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}.</p>
+     * {@inheritDoc}
+     */
+    @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.
+     * @deprecated as of 2.2 replaced by the correctly spelled {@link #getSparsity()}
+     */
+    @Deprecated
+    public double getSparcity() {
+        return getSparsity();
+    }
+
+    /**
+    *
+    * @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</code> optimized for OpenMap.
+     * <p>This implementation does not allow arbitrary calls to <code>setIndex</code>
+     * since the order that 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.
+     *  <p>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/math/linear/QRDecomposition.java b/src/main/java/org/apache/commons/math/linear/QRDecomposition.java
new file mode 100644
index 0000000..2f58e05
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/QRDecomposition.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.math.linear;
+
+
+/**
+ * An interface to classes that implement an algorithm to calculate the
+ * QR-decomposition of a real matrix.
+ * <p>This interface 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:</p>
+ * <ul>
+ *   <li>a {@link #getQT() getQT} method has been added,</li>
+ *   <li>the <code>solve</code> and <code>isFullRank</code> methods have been replaced
+ *   by a {@link #getSolver() getSolver} method and the equivalent methods provided by
+ *   the returned {@link DecompositionSolver}.</li>
+ * </ul>
+ *
+ * @see <a href="http://mathworld.wolfram.com/QRDecomposition.html">MathWorld</a>
+ * @see <a href="http://en.wikipedia.org/wiki/QR_decomposition">Wikipedia</a>
+ * @version $Revision: 826627 $ $Date: 2009-10-19 12:27:47 +0200 (lun. 19 oct. 2009) $
+ * @since 1.2
+ */
+public interface QRDecomposition {
+
+    /**
+     * Returns the matrix R of the decomposition.
+     * <p>R is an upper-triangular matrix</p>
+     * @return the R matrix
+     */
+    RealMatrix getR();
+
+    /**
+     * Returns the matrix Q of the decomposition.
+     * <p>Q is an orthogonal matrix</p>
+     * @return the Q matrix
+     */
+    RealMatrix getQ();
+
+    /**
+     * Returns the transpose of the matrix Q of the decomposition.
+     * <p>Q is an orthogonal matrix</p>
+     * @return the Q matrix
+     */
+    RealMatrix getQT();
+
+    /**
+     * 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.</p>
+     * @return a matrix containing the Householder reflector vectors
+     */
+    RealMatrix getH();
+
+    /**
+     * Get a solver for finding the A &times; X = B solution in least square sense.
+     * @return a solver
+     */
+    DecompositionSolver getSolver();
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/QRDecompositionImpl.java b/src/main/java/org/apache/commons/math/linear/QRDecompositionImpl.java
new file mode 100644
index 0000000..7356a8a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/QRDecompositionImpl.java
@@ -0,0 +1,453 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.linear;
+
+import java.util.Arrays;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.util.FastMath;
+
+
+/**
+ * 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>
+ * <p>This class compute the decomposition using Householder reflectors.</p>
+ * <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>
+ *
+ * @see <a href="http://mathworld.wolfram.com/QRDecomposition.html">MathWorld</a>
+ * @see <a href="http://en.wikipedia.org/wiki/QR_decomposition">Wikipedia</a>
+ *
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ * @since 1.2
+ */
+public class QRDecompositionImpl implements 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.</p>
+     */
+    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;
+
+    /**
+     * Calculates the QR-decomposition of the given matrix.
+     * @param matrix The matrix to decompose.
+     */
+    public QRDecompositionImpl(RealMatrix matrix) {
+
+        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;
+
+        /*
+         * The QR decomposition of a matrix A is calculated using Householder
+         * reflectors by repeating the following operations to each minor
+         * A(minor,minor) of A:
+         */
+        for (int minor = 0; minor < FastMath.min(m, n); minor++) {
+
+            final double[] qrtMinor = qrt[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 < m; 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 < n; col++) {
+                    final double[] qrtCol = qrt[col];
+                    double alpha = 0;
+                    for (int row = minor; row < m; row++) {
+                        alpha -= qrtCol[row] * qrtMinor[row];
+                    }
+                    alpha /= a * qrtMinor[minor];
+
+                    // Subtract the column vector alpha*v from x.
+                    for (int row = minor; row < m; row++) {
+                        qrtCol[row] -= alpha * qrtMinor[row];
+                    }
+                }
+            }
+        }
+    }
+
+    /** {@inheritDoc} */
+    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;
+            cachedR = MatrixUtils.createRealMatrix(m, n);
+
+            // copy the diagonal from rDiag and the upper triangle of qr
+            for (int row = FastMath.min(m, n) - 1; row >= 0; row--) {
+                cachedR.setEntry(row, row, rDiag[row]);
+                for (int col = row + 1; col < n; col++) {
+                    cachedR.setEntry(row, col, qrt[col][row]);
+                }
+            }
+
+        }
+
+        // return the cached matrix
+        return cachedR;
+
+    }
+
+    /** {@inheritDoc} */
+    public RealMatrix getQ() {
+        if (cachedQ == null) {
+            cachedQ = getQT().transpose();
+        }
+        return cachedQ;
+    }
+
+    /** {@inheritDoc} */
+    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;
+            cachedQT = MatrixUtils.createRealMatrix(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--) {
+                cachedQT.setEntry(minor, minor, 1.0);
+            }
+
+            for (int minor = FastMath.min(m, n)-1; minor >= 0; minor--){
+                final double[] qrtMinor = qrt[minor];
+                cachedQT.setEntry(minor, minor, 1.0);
+                if (qrtMinor[minor] != 0.0) {
+                    for (int col = minor; col < m; col++) {
+                        double alpha = 0;
+                        for (int row = minor; row < m; row++) {
+                            alpha -= cachedQT.getEntry(col, row) * qrtMinor[row];
+                        }
+                        alpha /= rDiag[minor] * qrtMinor[minor];
+
+                        for (int row = minor; row < m; row++) {
+                            cachedQT.addToEntry(col, row, -alpha * qrtMinor[row]);
+                        }
+                    }
+                }
+            }
+
+        }
+
+        // return the cached matrix
+        return cachedQT;
+
+    }
+
+    /** {@inheritDoc} */
+    public RealMatrix getH() {
+
+        if (cachedH == null) {
+
+            final int n = qrt.length;
+            final int m = qrt[0].length;
+            cachedH = MatrixUtils.createRealMatrix(m, n);
+            for (int i = 0; i < m; ++i) {
+                for (int j = 0; j < FastMath.min(i + 1, n); ++j) {
+                    cachedH.setEntry(i, j, qrt[j][i] / -rDiag[j]);
+                }
+            }
+
+        }
+
+        // return the cached matrix
+        return cachedH;
+
+    }
+
+    /** {@inheritDoc} */
+    public DecompositionSolver getSolver() {
+        return new Solver(qrt, rDiag);
+    }
+
+    /** 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.</p>
+         */
+        private final double[][] qrt;
+
+        /** The diagonal elements of R. */
+        private final double[] rDiag;
+
+        /**
+         * Build a solver from decomposed matrix.
+         * @param qrt packed TRANSPOSED representation of the QR decomposition
+         * @param rDiag diagonal elements of R
+         */
+        private Solver(final double[][] qrt, final double[] rDiag) {
+            this.qrt   = qrt;
+            this.rDiag = rDiag;
+        }
+
+        /** {@inheritDoc} */
+        public boolean isNonSingular() {
+
+            for (double diag : rDiag) {
+                if (diag == 0) {
+                    return false;
+                }
+            }
+            return true;
+
+        }
+
+        /** {@inheritDoc} */
+        public double[] solve(double[] b)
+        throws IllegalArgumentException, InvalidMatrixException {
+
+            final int n = qrt.length;
+            final int m = qrt[0].length;
+            if (b.length != m) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                        LocalizedFormats.VECTOR_LENGTH_MISMATCH,
+                        b.length, m);
+            }
+            if (!isNonSingular()) {
+                throw new SingularMatrixException();
+            }
+
+            final double[] x = new double[n];
+            final double[] y = b.clone();
+
+            // 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 x;
+
+        }
+
+        /** {@inheritDoc} */
+        public RealVector solve(RealVector b)
+        throws IllegalArgumentException, InvalidMatrixException {
+            try {
+                return solve((ArrayRealVector) b);
+            } catch (ClassCastException cce) {
+                return new ArrayRealVector(solve(b.getData()), false);
+            }
+        }
+
+        /** Solve the linear equation A &times; X = B.
+         * <p>The A matrix is implicit here. It is </p>
+         * @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 IllegalArgumentException if matrices dimensions don't match
+         * @throws InvalidMatrixException if decomposed matrix is singular
+         */
+        public ArrayRealVector solve(ArrayRealVector b)
+        throws IllegalArgumentException, InvalidMatrixException {
+            return new ArrayRealVector(solve(b.getDataRef()), false);
+        }
+
+        /** {@inheritDoc} */
+        public RealMatrix solve(RealMatrix b)
+        throws IllegalArgumentException, InvalidMatrixException {
+
+            final int n = qrt.length;
+            final int m = qrt[0].length;
+            if (b.getRowDimension() != m) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                        LocalizedFormats.DIMENSIONS_MISMATCH_2x2,
+                        b.getRowDimension(), b.getColumnDimension(), m, "n");
+            }
+            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} */
+        public RealMatrix getInverse()
+        throws InvalidMatrixException {
+            return solve(MatrixUtils.createRealIdentityMatrix(rDiag.length));
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/RealMatrix.java b/src/main/java/org/apache/commons/math/linear/RealMatrix.java
new file mode 100644
index 0000000..e025fd4
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/RealMatrix.java
@@ -0,0 +1,871 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.linear;
+
+import org.apache.commons.math.linear.MatrixVisitorException;
+
+
+/**
+ * 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.</p>
+ *
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ */
+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 IllegalArgumentException if row or column dimension is not positive
+     * @since 2.0
+     */
+    RealMatrix createMatrix(final int rowDimension, final int columnDimension);
+
+    /**
+     * Returns a (deep) copy of this.
+     *
+     * @return matrix copy
+     */
+    RealMatrix copy();
+
+    /**
+     * Compute the sum of this and m.
+     *
+     * @param m    matrix to be added
+     * @return     this + m
+     * @throws  IllegalArgumentException if m is not the same size as this
+     */
+    RealMatrix add(RealMatrix m) throws IllegalArgumentException;
+
+    /**
+     * Compute this minus m.
+     *
+     * @param m    matrix to be subtracted
+     * @return     this - m
+     * @throws  IllegalArgumentException if m is not the same size as this
+     */
+    RealMatrix subtract(RealMatrix m) throws IllegalArgumentException;
+
+     /**
+     * Returns the result of adding d to each entry of this.
+     *
+     * @param d    value to be added to each entry
+     * @return     d + this
+     */
+    RealMatrix scalarAdd(double d);
+
+    /**
+     * Returns the result multiplying each entry of this by d.
+     *
+     * @param d    value to multiply all entries by
+     * @return     d * this
+     */
+    RealMatrix scalarMultiply(double d);
+
+    /**
+     * Returns the result of postmultiplying this by m.
+     *
+     * @param m    matrix to postmultiply by
+     * @return     this * m
+     * @throws     IllegalArgumentException
+     *             if columnDimension(this) != rowDimension(m)
+     */
+    RealMatrix multiply(RealMatrix m) throws IllegalArgumentException;
+
+    /**
+     * Returns the result premultiplying this by <code>m</code>.
+     * @param m    matrix to premultiply by
+     * @return     m * this
+     * @throws     IllegalArgumentException
+     *             if rowDimension(this) != columnDimension(m)
+     */
+    RealMatrix preMultiply(RealMatrix m) throws IllegalArgumentException;
+
+    /**
+     * 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
+     * @exception MatrixIndexException  if the indices are not valid
+     */
+   RealMatrix getSubMatrix(int startRow, int endRow, int startColumn, int endColumn)
+       throws MatrixIndexException;
+
+   /**
+    * 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
+    * @exception MatrixIndexException if row or column selections are not valid
+    */
+   RealMatrix getSubMatrix(int[] selectedRows, int[] selectedColumns)
+       throws MatrixIndexException;
+
+   /**
+    * 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)
+    * @exception MatrixIndexException if the indices are not valid
+    * @exception IllegalArgumentException if the destination array is too small
+    */
+  void copySubMatrix(int startRow, int endRow, int startColumn, int endColumn,
+                     double[][] destination)
+      throws MatrixIndexException, IllegalArgumentException;
+
+  /**
+   * 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)
+   * @exception MatrixIndexException if the indices are not valid
+   * @exception IllegalArgumentException if the destination array is too small
+   */
+  void copySubMatrix(int[] selectedRows, int[] selectedColumns, double[][] destination)
+      throws MatrixIndexException, IllegalArgumentException;
+
+   /**
+    * Replace the submatrix starting at <code>row, column</code> using data in
+    * the input <code>subMatrix</code> 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></p>
+    *
+    * @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 MatrixIndexException  if subMatrix does not fit into this
+    *    matrix from element in (row, column)
+    * @throws IllegalArgumentException if <code>subMatrix</code> is not rectangular
+    *  (not all rows have the same length) or empty
+    * @throws NullPointerException if <code>subMatrix</code> is null
+    * @since 2.0
+    */
+   void setSubMatrix(double[][] subMatrix, int row, int column)
+       throws MatrixIndexException;
+
+   /**
+    * Returns the entries in row number <code>row</code>
+    * as a row matrix.  Row indices start at 0.
+    *
+    * @param row the row to be fetched
+    * @return row matrix
+    * @throws MatrixIndexException if the specified row index is invalid
+    */
+   RealMatrix getRowMatrix(int row) throws MatrixIndexException;
+
+   /**
+    * 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 MatrixIndexException if the specified row index is invalid
+    * @throws InvalidMatrixException if the matrix dimensions do not match one
+    * instance row
+    */
+   void setRowMatrix(int row, RealMatrix matrix)
+       throws MatrixIndexException, InvalidMatrixException;
+
+   /**
+    * Returns the entries in column number <code>column</code>
+    * as a column matrix.  Column indices start at 0.
+    *
+    * @param column the column to be fetched
+    * @return column matrix
+    * @throws MatrixIndexException if the specified column index is invalid
+    */
+   RealMatrix getColumnMatrix(int column) throws MatrixIndexException;
+
+   /**
+    * 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 MatrixIndexException if the specified column index is invalid
+    * @throws InvalidMatrixException if the matrix dimensions do not match one
+    * instance column
+    */
+   void setColumnMatrix(int column, RealMatrix matrix)
+       throws MatrixIndexException, InvalidMatrixException;
+
+   /**
+    * Returns the entries in row number <code>row</code>
+    * as a vector.  Row indices start at 0.
+    *
+    * @param row the row to be fetched
+    * @return row vector
+    * @throws MatrixIndexException if the specified row index is invalid
+    */
+   RealVector getRowVector(int row) throws MatrixIndexException;
+
+   /**
+    * Sets the entries in row number <code>row</code>
+    * as a vector.  Row indices start at 0.
+    *
+    * @param row the row to be set
+    * @param vector row vector (must have the same number of columns
+    * as the instance)
+    * @throws MatrixIndexException if the specified row index is invalid
+    * @throws InvalidMatrixException if the vector dimension does not match one
+    * instance row
+    */
+   void setRowVector(int row, RealVector vector)
+       throws MatrixIndexException, InvalidMatrixException;
+
+   /**
+    * Returns the entries in column number <code>column</code>
+    * as a vector.  Column indices start at 0.
+    *
+    * @param column the column to be fetched
+    * @return column vector
+    * @throws MatrixIndexException if the specified column index is invalid
+    */
+   RealVector getColumnVector(int column) throws MatrixIndexException;
+
+   /**
+    * Sets the entries in column number <code>column</code>
+    * as a vector.  Column indices start at 0.
+    *
+    * @param column the column to be set
+    * @param vector column vector (must have the same number of rows as the instance)
+    * @throws MatrixIndexException if the specified column index is invalid
+    * @throws InvalidMatrixException if the vector dimension does not match one
+    * instance column
+    */
+   void setColumnVector(int column, RealVector vector)
+       throws MatrixIndexException, InvalidMatrixException;
+
+    /**
+     * Returns the entries in row number <code>row</code> as an array.
+     * <p>
+     * Row indices start at 0.  A <code>MatrixIndexException</code> is thrown
+     * unless <code>0 <= row < rowDimension.</code></p>
+     *
+     * @param row the row to be fetched
+     * @return array of entries in the row
+     * @throws MatrixIndexException if the specified row index is not valid
+     */
+    double[] getRow(int row) throws MatrixIndexException;
+
+    /**
+     * 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 array row matrix (must have the same number of columns as the instance)
+     * @throws MatrixIndexException if the specified row index is invalid
+     * @throws InvalidMatrixException if the array size does not match one
+     * instance row
+     */
+    void setRow(int row, double[] array)
+        throws MatrixIndexException, InvalidMatrixException;
+
+    /**
+     * Returns the entries in column number <code>col</code> as an array.
+     * <p>
+     * Column indices start at 0.  A <code>MatrixIndexException</code> is thrown
+     * unless <code>0 <= column < columnDimension.</code></p>
+     *
+     * @param column the column to be fetched
+     * @return array of entries in the column
+     * @throws MatrixIndexException if the specified column index is not valid
+     */
+    double[] getColumn(int column) throws MatrixIndexException;
+
+    /**
+     * 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 array column array (must have the same number of rows as the instance)
+     * @throws MatrixIndexException if the specified column index is invalid
+     * @throws InvalidMatrixException if the array size does not match one
+     * instance column
+     */
+    void setColumn(int column, double[] array)
+        throws MatrixIndexException, InvalidMatrixException;
+
+    /**
+     * Returns the entry in the specified row and column.
+     * <p>
+     * Row and column indices start at 0 and must satisfy
+     * <ul>
+     * <li><code>0 <= row < rowDimension</code></li>
+     * <li><code> 0 <= column < columnDimension</code></li>
+     * </ul>
+     * otherwise a <code>MatrixIndexException</code> is thrown.</p>
+     *
+     * @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 MatrixIndexException if the row or column index is not valid
+     */
+    double getEntry(int row, int column) throws MatrixIndexException;
+
+    /**
+     * Set the entry in the specified row and column.
+     * <p>
+     * Row and column indices start at 0 and must satisfy
+     * <ul>
+     * <li><code>0 <= row < rowDimension</code></li>
+     * <li><code> 0 <= column < columnDimension</code></li>
+     * </ul>
+     * otherwise a <code>MatrixIndexException</code> is thrown.</p>
+     *
+     * @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 MatrixIndexException if the row or column index is not valid
+     * @since 2.0
+     */
+    void setEntry(int row, int column, double value) throws MatrixIndexException;
+
+    /**
+     * Change an entry in the specified row and column.
+     * <p>
+     * Row and column indices start at 0 and must satisfy
+     * <ul>
+     * <li><code>0 <= row < rowDimension</code></li>
+     * <li><code> 0 <= column < columnDimension</code></li>
+     * </ul>
+     * otherwise a <code>MatrixIndexException</code> is thrown.</p>
+     *
+     * @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 row,column
+     * @throws MatrixIndexException if the row or column index is not valid
+     * @since 2.0
+     */
+    void addToEntry(int row, int column, double increment) throws MatrixIndexException;
+
+    /**
+     * Change an entry in the specified row and column.
+     * <p>
+     * Row and column indices start at 0 and must satisfy
+     * <ul>
+     * <li><code>0 <= row < rowDimension</code></li>
+     * <li><code> 0 <= column < columnDimension</code></li>
+     * </ul>
+     * otherwise a <code>MatrixIndexException</code> is thrown.</p>
+     *
+     * @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 row,column
+     * @throws MatrixIndexException if the row or column index is not valid
+     * @since 2.0
+     */
+    void multiplyEntry(int row, int column, double factor) throws MatrixIndexException;
+
+    /**
+     * Returns the transpose of this matrix.
+     *
+     * @return transpose matrix
+     */
+    RealMatrix transpose();
+
+    /**
+     * Returns the inverse of this matrix.
+     *
+     * @return inverse matrix
+     * @throws InvalidMatrixException if  this is not invertible
+     * @deprecated as of release 2.0, replaced by <code>
+     * {@link LUDecompositionImpl#LUDecompositionImpl(RealMatrix)
+     * new LUDecompositionImpl(m)}.{@link LUDecomposition#getSolver()
+     * getSolver()}.{@link DecompositionSolver#getInverse()
+     * getInverse()}</code>
+     */
+    @Deprecated
+    RealMatrix inverse() throws InvalidMatrixException;
+
+    /**
+     * Returns the determinant of this matrix.
+     *
+     * @return determinant
+     * @deprecated as of release 2.0, replaced by <code>
+     * {@link LUDecompositionImpl#LUDecompositionImpl(RealMatrix)
+     * new LUDecompositionImpl(m)}.{@link LUDecomposition#getDeterminant()
+     * getDeterminant()}</code>
+     */
+    @Deprecated
+    double getDeterminant();
+
+    /**
+     * Is this a singular matrix?
+     * @return true if the matrix is singular
+     * @deprecated as of release 2.0, replaced by the boolean negation of
+     * <code>{@link LUDecompositionImpl#LUDecompositionImpl(RealMatrix)
+     * new LUDecompositionImpl(m)}.{@link LUDecomposition#getSolver()
+     * getSolver()}.{@link DecompositionSolver#isNonSingular()
+     * isNonSingular()}</code>
+     */
+    @Deprecated
+    boolean isSingular();
+
+    /**
+     * 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
+     */
+    double getTrace() throws NonSquareMatrixException;
+
+    /**
+     * Returns the result of multiplying this by the vector <code>v</code>.
+     *
+     * @param v the vector to operate on
+     * @return this*v
+     * @throws IllegalArgumentException if columnDimension != v.size()
+     */
+    double[] operate(double[] v) throws IllegalArgumentException;
+
+    /**
+     * Returns the result of multiplying this by the vector <code>v</code>.
+     *
+     * @param v the vector to operate on
+     * @return this*v
+     * @throws IllegalArgumentException if columnDimension != v.size()
+     */
+    RealVector operate(RealVector v) throws IllegalArgumentException;
+
+    /**
+     * Returns the (row) vector result of premultiplying this by the vector <code>v</code>.
+     *
+     * @param v the row vector to premultiply by
+     * @return v*this
+     * @throws IllegalArgumentException if rowDimension != v.size()
+     */
+    double[] preMultiply(double[] v) throws IllegalArgumentException;
+
+    /**
+     * Returns the (row) vector result of premultiplying this by the vector <code>v</code>.
+     *
+     * @param v the row vector to premultiply by
+     * @return v*this
+     * @throws IllegalArgumentException if rowDimension != v.size()
+     */
+    RealVector preMultiply(RealVector v) throws IllegalArgumentException;
+
+    /**
+     * 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.</p>
+     * @param visitor visitor used to process all matrix entries
+     * @exception  MatrixVisitorException if the visitor cannot process an entry
+     * @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)
+        throws MatrixVisitorException;
+
+    /**
+     * 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.</p>
+     * @param visitor visitor used to process all matrix entries
+     * @exception  MatrixVisitorException if the visitor cannot process an entry
+     * @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)
+        throws MatrixVisitorException;
+
+    /**
+     * 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.</p>
+     * @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
+     * @exception  MatrixVisitorException if the visitor cannot process an entry
+     * @exception MatrixIndexException  if the indices are not valid
+     * @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 MatrixIndexException, MatrixVisitorException;
+
+    /**
+     * 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.</p>
+     * @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
+     * @exception  MatrixVisitorException if the visitor cannot process an entry
+     * @exception MatrixIndexException  if the indices are not valid
+     * @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 MatrixIndexException, MatrixVisitorException;
+
+    /**
+     * 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.</p>
+     * @param visitor visitor used to process all matrix entries
+     * @exception  MatrixVisitorException if the visitor cannot process an entry
+     * @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)
+        throws MatrixVisitorException;
+
+    /**
+     * 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.</p>
+     * @param visitor visitor used to process all matrix entries
+     * @exception  MatrixVisitorException if the visitor cannot process an entry
+     * @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)
+        throws MatrixVisitorException;
+
+    /**
+     * 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.</p>
+     * @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
+     * @exception  MatrixVisitorException if the visitor cannot process an entry
+     * @exception MatrixIndexException  if the indices are not valid
+     * @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 MatrixIndexException, MatrixVisitorException;
+
+    /**
+     * 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.</p>
+     * @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
+     * @exception  MatrixVisitorException if the visitor cannot process an entry
+     * @exception MatrixIndexException  if the indices are not valid
+     * @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 MatrixIndexException, MatrixVisitorException;
+
+    /**
+     * 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.</p>
+     * @param visitor visitor used to process all matrix entries
+     * @exception  MatrixVisitorException if the visitor cannot process an entry
+     * @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)
+        throws MatrixVisitorException;
+
+    /**
+     * 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.</p>
+     * @param visitor visitor used to process all matrix entries
+     * @exception  MatrixVisitorException if the visitor cannot process an entry
+     * @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)
+        throws MatrixVisitorException;
+
+    /**
+     * 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.</p>
+     * @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)
+     * @exception  MatrixVisitorException if the visitor cannot process an entry
+     * @exception MatrixIndexException  if the indices are not valid
+     * @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 MatrixIndexException, MatrixVisitorException;
+
+    /**
+     * 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.</p>
+     * @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)
+     * @exception  MatrixVisitorException if the visitor cannot process an entry
+     * @exception MatrixIndexException  if the indices are not valid
+     * @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 MatrixIndexException, MatrixVisitorException;
+
+    /**
+     * Returns the solution vector for a linear system with coefficient
+     * matrix = this and constant vector = <code>b</code>.
+     *
+     * @param b  constant vector
+     * @return vector of solution values to AX = b, where A is *this
+     * @throws IllegalArgumentException if this.rowDimension != b.length
+     * @throws InvalidMatrixException if this matrix is not square or is singular
+     * @deprecated as of release 2.0, replaced by {@link DecompositionSolver#solve(double[])}
+     */
+    @Deprecated
+    double[] solve(double[] b) throws IllegalArgumentException, InvalidMatrixException;
+
+    /**
+     * Returns a matrix of (column) solution vectors for linear systems with
+     * coefficient matrix = this and constant vectors = columns of
+     * <code>b</code>.
+     *
+     * @param b  matrix of constant vectors forming RHS of linear systems to
+     * to solve
+     * @return matrix of solution vectors
+     * @throws IllegalArgumentException if this.rowDimension != row dimension
+     * @throws InvalidMatrixException if this matrix is not square or is singular
+     * @deprecated as of release 2.0, replaced by {@link DecompositionSolver#solve(RealMatrix)}
+     */
+    @Deprecated
+    RealMatrix solve(RealMatrix b) throws IllegalArgumentException, InvalidMatrixException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/RealMatrixChangingVisitor.java b/src/main/java/org/apache/commons/math/linear/RealMatrixChangingVisitor.java
new file mode 100644
index 0000000..25e8f96
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/RealMatrixChangingVisitor.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.math.linear;
+
+import org.apache.commons.math.linear.MatrixVisitorException;
+
+/**
+ * Interface defining a visitor for matrix entries.
+ *
+ * @see DefaultRealMatrixChangingVisitor
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @since 2.0
+ */
+public interface RealMatrixChangingVisitor {
+
+    /**
+     * Start visiting a matrix.
+     * <p>This method is called once before any entry of the matrix is visited.</p>
+     * @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
+     * @throws MatrixVisitorException if something wrong occurs
+     */
+    double visit(int row, int column, double value)
+        throws MatrixVisitorException;
+
+    /**
+     * End visiting a matrix.
+     * <p>This method is called once after all entries of the matrix have been visited.</p>
+     * @return the value that the <code>walkInXxxOrder</code> must return
+     */
+    double end();
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/RealMatrixImpl.java b/src/main/java/org/apache/commons/math/linear/RealMatrixImpl.java
new file mode 100644
index 0000000..7dbfdba
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/RealMatrixImpl.java
@@ -0,0 +1,629 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.linear;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.linear.MatrixVisitorException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * Implementation of RealMatrix using a double[][] array to store entries and
+ * <a href="http://www.math.gatech.edu/~bourbaki/math2601/Web-notes/2num.pdf">
+ * LU decomposition</a> to support linear system
+ * solution and inverse.
+ * <p>
+ * The LU decomposition is performed as needed, to support the following operations: <ul>
+ * <li>solve</li>
+ * <li>isSingular</li>
+ * <li>getDeterminant</li>
+ * <li>inverse</li> </ul></p>
+ * <p>
+ * <strong>Usage notes</strong>:<br>
+ * <ul><li>
+ * The LU decomposition is cached and reused on subsequent calls.
+ * If data are modified via references to the underlying array obtained using
+ * <code>getDataRef()</code>, then the stored LU decomposition will not be
+ * discarded.  In this case, you need to explicitly invoke
+ * <code>LUDecompose()</code> to recompute the decomposition
+ * before using any of the methods above.</li>
+ * <li>
+ * As specified in the {@link RealMatrix} 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.</li></ul>
+ * </p>
+ *
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @deprecated as of 2.0 replaced by {@link Array2DRowRealMatrix}
+ */
+@Deprecated
+public class RealMatrixImpl extends AbstractRealMatrix implements Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -1067294169172445528L;
+
+    /** Entries of the matrix */
+    protected double data[][];
+
+    /**
+     * Creates a matrix with no data
+     */
+    public RealMatrixImpl() {
+    }
+
+    /**
+     * 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 IllegalArgumentException if row or column dimension is not
+     *  positive
+     */
+    public RealMatrixImpl(final int rowDimension, final int columnDimension)
+        throws IllegalArgumentException {
+        super(rowDimension, columnDimension);
+        data = new double[rowDimension][columnDimension];
+    }
+
+    /**
+     * Create a new 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 #RealMatrixImpl(double[][], boolean)}
+     * with the second argument set to <code>true</code>.</p>
+     *
+     * @param d data for new matrix
+     * @throws IllegalArgumentException if <code>d</code> is not rectangular
+     *  (not all rows have the same length) or empty
+     * @throws NullPointerException if <code>d</code> is null
+     * @see #RealMatrixImpl(double[][], boolean)
+     */
+    public RealMatrixImpl(final double[][] d)
+        throws IllegalArgumentException, NullPointerException {
+        copyIn(d);
+    }
+
+    /**
+     * Create a new RealMatrix using the input array as the underlying
+     * data array.
+     * <p>If an array is built specially in order to be embedded in a
+     * RealMatrix and not used directly, the <code>copyArray</code> may be
+     * set to <code>false</code. This will prevent the copying and improve
+     * performance as no new array will be built and no data will be copied.</p>
+     * @param d data for new matrix
+     * @param copyArray if true, the input array will be copied, otherwise
+     * it will be referenced
+     * @throws IllegalArgumentException if <code>d</code> is not rectangular
+     *  (not all rows have the same length) or empty
+     * @throws NullPointerException if <code>d</code> is null
+     * @see #RealMatrixImpl(double[][])
+     */
+    public RealMatrixImpl(final double[][] d, final boolean copyArray)
+        throws IllegalArgumentException, NullPointerException {
+        if (copyArray) {
+            copyIn(d);
+        } else {
+            if (d == null) {
+                throw new NullPointerException();
+            }
+            final int nRows = d.length;
+            if (nRows == 0) {
+                throw MathRuntimeException.createIllegalArgumentException(LocalizedFormats.AT_LEAST_ONE_ROW);
+            }
+            final int nCols = d[0].length;
+            if (nCols == 0) {
+                throw MathRuntimeException.createIllegalArgumentException(LocalizedFormats.AT_LEAST_ONE_COLUMN);
+            }
+            for (int r = 1; r < nRows; r++) {
+                if (d[r].length != nCols) {
+                    throw MathRuntimeException.createIllegalArgumentException(
+                            LocalizedFormats.DIFFERENT_ROWS_LENGTHS,
+                            nCols, d[r].length);
+                }
+            }
+            data = d;
+        }
+    }
+
+    /**
+     * Create a new (column) RealMatrix using <code>v</code> as the
+     * data for the unique column of the <code>v.length x 1</code> matrix
+     * created.
+     * <p>The input array is copied, not referenced.</p>
+     *
+     * @param v column vector holding data for new matrix
+     */
+    public RealMatrixImpl(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 IllegalArgumentException {
+        return new RealMatrixImpl(rowDimension, columnDimension);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealMatrix copy() {
+        return new RealMatrixImpl(copyOut(), false);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealMatrix add(final RealMatrix m)
+        throws IllegalArgumentException {
+        try {
+            return add((RealMatrixImpl) m);
+        } catch (ClassCastException cce) {
+            return super.add(m);
+        }
+    }
+
+    /**
+     * Compute the sum of this and <code>m</code>.
+     *
+     * @param m    matrix to be added
+     * @return     this + m
+     * @throws  IllegalArgumentException if m is not the same size as this
+     */
+    public RealMatrixImpl add(final RealMatrixImpl m)
+        throws IllegalArgumentException {
+
+        // 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 RealMatrixImpl(outData, false);
+
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealMatrix subtract(final RealMatrix m)
+        throws IllegalArgumentException {
+        try {
+            return subtract((RealMatrixImpl) m);
+        } catch (ClassCastException cce) {
+            return super.subtract(m);
+        }
+    }
+
+    /**
+     * Compute  this minus <code>m</code>.
+     *
+     * @param m    matrix to be subtracted
+     * @return     this + m
+     * @throws  IllegalArgumentException if m is not the same size as this
+     */
+    public RealMatrixImpl subtract(final RealMatrixImpl m)
+        throws IllegalArgumentException {
+
+        // safety check
+        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 RealMatrixImpl(outData, false);
+
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealMatrix multiply(final RealMatrix m)
+        throws IllegalArgumentException {
+        try {
+            return multiply((RealMatrixImpl) m);
+        } catch (ClassCastException cce) {
+            return super.multiply(m);
+        }
+    }
+
+    /**
+     * Returns the result of postmultiplying this by <code>m</code>.
+     * @param m    matrix to postmultiply by
+     * @return     this*m
+     * @throws     IllegalArgumentException
+     *             if columnDimension(this) != rowDimension(m)
+     */
+    public RealMatrixImpl multiply(final RealMatrixImpl m)
+        throws IllegalArgumentException {
+
+        // safety check
+        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];
+        for (int row = 0; row < nRows; row++) {
+            final double[] dataRow    = data[row];
+            final double[] outDataRow = outData[row];
+            for (int col = 0; col < nCols; col++) {
+                double sum = 0;
+                for (int i = 0; i < nSum; i++) {
+                    sum += dataRow[i] * m.data[i][col];
+                }
+                outDataRow[col] = sum;
+            }
+        }
+
+        return new RealMatrixImpl(outData, false);
+
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double[][] getData() {
+        return copyOut();
+    }
+
+    /**
+     * Returns a reference to the underlying data array.
+     * <p>
+     * Does <strong>not</strong> make a fresh copy of the underlying data.</p>
+     *
+     * @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 MatrixIndexException {
+        if (data == null) {
+            if (row > 0) {
+                throw MathRuntimeException.createIllegalStateException(
+                        LocalizedFormats.FIRST_ROWS_NOT_INITIALIZED_YET,
+                        row);
+            }
+            if (column > 0) {
+                throw MathRuntimeException.createIllegalStateException(
+                        LocalizedFormats.FIRST_COLUMNS_NOT_INITIALIZED_YET,
+                        column);
+            }
+            final int nRows = subMatrix.length;
+            if (nRows == 0) {
+                throw MathRuntimeException.createIllegalArgumentException(LocalizedFormats.AT_LEAST_ONE_ROW);
+            }
+
+            final int nCols = subMatrix[0].length;
+            if (nCols == 0) {
+                throw MathRuntimeException.createIllegalArgumentException(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 MathRuntimeException.createIllegalArgumentException(
+                            LocalizedFormats.DIFFERENT_ROWS_LENGTHS,
+                            nCols, subMatrix[i].length);
+                }
+                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 MatrixIndexException {
+        try {
+            return data[row][column];
+        } catch (ArrayIndexOutOfBoundsException e) {
+            throw new MatrixIndexException(
+                    LocalizedFormats.NO_SUCH_MATRIX_ENTRY,
+                    row, column, getRowDimension(), getColumnDimension());
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setEntry(final int row, final int column, final double value)
+        throws MatrixIndexException {
+        try {
+            data[row][column] = value;
+        } catch (ArrayIndexOutOfBoundsException e) {
+            throw new MatrixIndexException(
+                    LocalizedFormats.NO_SUCH_MATRIX_ENTRY,
+                    row, column, getRowDimension(), getColumnDimension());
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addToEntry(final int row, final int column, final double increment)
+        throws MatrixIndexException {
+        try {
+            data[row][column] += increment;
+        } catch (ArrayIndexOutOfBoundsException e) {
+            throw new MatrixIndexException(
+                    LocalizedFormats.NO_SUCH_MATRIX_ENTRY,
+                    row, column, getRowDimension(), getColumnDimension());
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void multiplyEntry(final int row, final int column, final double factor)
+        throws MatrixIndexException {
+        try {
+            data[row][column] *= factor;
+        } catch (ArrayIndexOutOfBoundsException e) {
+            throw new MatrixIndexException(
+                    LocalizedFormats.NO_SUCH_MATRIX_ENTRY,
+                    row, column, getRowDimension(), getColumnDimension());
+        }
+    }
+
+    /** {@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 IllegalArgumentException {
+        final int nRows = this.getRowDimension();
+        final int nCols = this.getColumnDimension();
+        if (v.length != nCols) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.VECTOR_LENGTH_MISMATCH,
+                    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 IllegalArgumentException {
+
+        final int nRows = getRowDimension();
+        final int nCols = getColumnDimension();
+        if (v.length != nRows) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.VECTOR_LENGTH_MISMATCH,
+                    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)
+        throws MatrixVisitorException {
+        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)
+        throws MatrixVisitorException {
+        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 MatrixIndexException, MatrixVisitorException {
+        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 MatrixIndexException, MatrixVisitorException {
+        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)
+        throws MatrixVisitorException {
+        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)
+        throws MatrixVisitorException {
+        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 MatrixIndexException, MatrixVisitorException {
+        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 MatrixIndexException, MatrixVisitorException {
+        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();
+    }
+
+    /**
+     * Returns 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;
+    }
+
+    /**
+     * Replaces data with a fresh copy of the input array.
+     * <p>
+     * Verifies that the input array is rectangular and non-empty.</p>
+     *
+     * @param in data to copy in
+     * @throws IllegalArgumentException if input array is empty or not
+     *    rectangular
+     * @throws NullPointerException if input array is null
+     */
+    private void copyIn(final double[][] in) {
+        setSubMatrix(in, 0, 0);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/RealMatrixPreservingVisitor.java b/src/main/java/org/apache/commons/math/linear/RealMatrixPreservingVisitor.java
new file mode 100644
index 0000000..2186b79
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/RealMatrixPreservingVisitor.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.math.linear;
+
+import org.apache.commons.math.linear.MatrixVisitorException;
+
+/**
+ * Interface defining a visitor for matrix entries.
+ *
+ * @see DefaultRealMatrixPreservingVisitor
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @since 2.0
+ */
+public interface RealMatrixPreservingVisitor {
+
+    /**
+     * Start visiting a matrix.
+     * <p>This method is called once before any entry of the matrix is visited.</p>
+     * @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
+     * @throws MatrixVisitorException if something wrong occurs
+     */
+    void visit(int row, int column, double value)
+        throws MatrixVisitorException;
+
+    /**
+     * End visiting a matrix.
+     * <p>This method is called once after all entries of the matrix have been visited.</p>
+     * @return the value that the <code>walkInXxxOrder</code> must return
+     */
+    double end();
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/RealVector.java b/src/main/java/org/apache/commons/math/linear/RealVector.java
new file mode 100644
index 0000000..0d7982b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/RealVector.java
@@ -0,0 +1,1005 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.linear;
+
+import java.util.Iterator;
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+
+
+/**
+ * Interface defining a real-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>
+ * <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:
+ * </p>
+ * <pre>
+ *   RealVector result = v.mapAddToSelf(3.0).mapTanToSelf().mapSquareToSelf();
+ * </pre>
+ * <p>
+ *  Remark on the deprecated {@code mapXxx} and {@code mapXxxToSelf} methods: In
+ *  Commons-Math v3.0, the same functionality will be achieved by directly using the
+ *  {@link #map(UnivariateRealFunction)} and {@link #mapToSelf(UnivariateRealFunction)}
+ *  together with new function objects (not available in v2.2).
+ * </p>
+ *
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ * @since 2.0
+ */
+public interface RealVector {
+    /**
+     * 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>
+     *
+     * @param function Function to apply to each entry.
+     * @return this vector.
+     * @throws FunctionEvaluationException if the function throws it.
+     */
+    RealVector mapToSelf(UnivariateRealFunction function) throws FunctionEvaluationException;
+
+    /**
+     * Acts as if implemented as:
+     * <pre>
+     *  return copy().map(function);
+     * </pre>
+     *
+     * @param function Function to apply to each entry.
+     * @return a new vector.
+     * @throws FunctionEvaluationException if the function throws it.
+     */
+    RealVector map(UnivariateRealFunction function) throws FunctionEvaluationException;
+
+    /** Class representing a modifiable entry in the vector. */
+    public abstract class Entry {
+        /** Index of the entry. */
+        private int index;
+
+        /**
+         * Get the value of the entry.
+         *
+         * @return the value of the entry.
+         */
+        public abstract double getValue();
+        /**
+         * Set the value of the entry.
+         *
+         * @param value New value for the entry.
+         */
+        public abstract void setValue(double 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;
+        }
+    }
+
+    /**
+     * Generic dense iterator.
+     * It iterates in increasing order of the vector index.
+     *
+     * @return a dense iterator
+     */
+    Iterator<Entry> iterator();
+
+    /**
+     * Specialized implementations may choose to not iterate over all
+     * dimensions, either because those values are unset, or are equal
+     * to defaultValue(), or are small enough to be ignored for the
+     * purposes of iteration.
+     * No guarantees are made about order of iteration.
+     * In dense implementations, this method will often delegate to
+     * {@link #iterator()}.
+     *
+     * @return a sparse iterator
+     */
+    Iterator<Entry> sparseIterator();
+
+    /**
+     * Returns a (deep) copy of this vector.
+     *
+     * @return a vector copy.
+     */
+    RealVector copy();
+
+    /**
+     * Compute the sum of this vector and {@code v}.
+     *
+     * @param v Vector to be added.
+     * @return {@code this} + {@code v}.
+     * @throws org.apache.commons.math.exception.DimensionMismatchException
+     * if {@code v} is not the same size as this vector.
+     */
+    RealVector add(RealVector v);
+
+    /**
+     * Compute the sum of this vector and {@code v}.
+     *
+     * @param v Vector to be added.
+     * @return {@code this} + {@code v}.
+     * @throws org.apache.commons.math.exception.DimensionMismatchException
+     * if {@code v} is not the same size as this vector.
+     */
+    RealVector add(double[] v);
+
+
+    /**
+     * Subtract {@code v} from this vector.
+     *
+     * @param v Vector to be subtracted.
+     * @return {@code this} - {@code v}.
+     * @throws org.apache.commons.math.exception.DimensionMismatchException
+     * if {@code v} is not the same size as this vector.
+     */
+    RealVector subtract(RealVector v);
+
+    /**
+     * Subtract {@code v} from this vector.
+     *
+     * @param v Vector to be subtracted.
+     * @return {@code this} - {@code v}.
+     * @throws org.apache.commons.math.exception.DimensionMismatchException
+     * if {@code v} is not the same size as this vector.
+     */
+    RealVector subtract(double[] v);
+
+    /**
+     * Add a value to each entry.
+     *
+     * @param d Value to be added to each entry.
+     * @return {@code this} + {@code d}.
+     */
+    RealVector mapAdd(double 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}.
+     */
+    RealVector mapAddToSelf(double d);
+
+    /**
+     * Subtract a value from each entry.
+     *
+     * @param d Value to be subtracted.
+     * @return {@code this} - {@code d}.
+     */
+    RealVector mapSubtract(double d);
+
+    /**
+     * Subtract a value from each entry.
+     * The instance is changed in-place.
+     *
+     * @param d Value to be subtracted.
+     * @return {@code this}.
+     */
+    RealVector mapSubtractToSelf(double d);
+
+    /**
+     * Multiply each entry.
+     *
+     * @param d Multiplication factor.
+     * @return {@code this} * {@code d}.
+     */
+    RealVector mapMultiply(double d);
+
+    /**
+     * Multiply each entry.
+     * The instance is changed in-place.
+     *
+     * @param d Multiplication factor.
+     * @return {@code this}.
+     */
+    RealVector mapMultiplyToSelf(double d);
+
+    /**
+     * Divide each entry.
+     *
+     * @param d Value to divide by.
+     * @return {@code this} / {@code d}.
+     */
+    RealVector mapDivide(double d);
+
+    /**
+     * Divide each entry.
+     * The instance is changed in-place.
+     *
+     * @param d Value to divide by.
+     * @return {@code this}.
+     */
+    RealVector mapDivideToSelf(double d);
+
+    /**
+     * Map a power operation to each entry.
+     *
+     * @param d Operator value.
+     * @return a mapped copy of the vector.
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapPow(double d);
+
+    /**
+     * Map a power operation to each entry.
+     * The instance is changed in-place.
+     *
+     * @param d Operator value.
+     * @return the mapped vector.
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapPowToSelf(double d);
+
+    /**
+     * Map the {@link Math#exp(double)} function to each entry.
+     *
+     * @return a mapped copy of the vector.
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapExp();
+
+    /**
+     * Map {@link Math#exp(double)} operation to each entry.
+     * The instance is changed in-place.
+     *
+     * @return the mapped vector.
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapExpToSelf();
+
+    /**
+     * Map the {@link Math#expm1(double)} function to each entry.
+     * @return a vector containing the result of applying the function to each entry
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapExpm1();
+
+    /**
+     * Map the {@link Math#expm1(double)} function to each entry.
+     * <p>The instance <strong>is</strong> changed by this method.</p>
+     * @return for convenience, return this
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapExpm1ToSelf();
+
+    /**
+     * Map the {@link Math#log(double)} function to each entry.
+     * @return a vector containing the result of applying the function to each entry
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapLog();
+
+    /**
+     * Map the {@link Math#log(double)} function to each entry.
+     * <p>The instance <strong>is</strong> changed by this method.</p>
+     * @return for convenience, return this
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapLogToSelf();
+
+    /**
+     * Map the {@link Math#log10(double)} function to each entry.
+     * @return a vector containing the result of applying the function to each entry
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapLog10();
+
+    /**
+     * Map the {@link Math#log10(double)} function to each entry.
+     * <p>The instance <strong>is</strong> changed by this method.</p>
+     * @return for convenience, return this
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapLog10ToSelf();
+
+    /**
+     * Map the {@link Math#log1p(double)} function to each entry.
+     * @return a vector containing the result of applying the function to each entry
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapLog1p();
+
+    /**
+     * Map the {@link Math#log1p(double)} function to each entry.
+     * <p>The instance <strong>is</strong> changed by this method.</p>
+     * @return for convenience, return this
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapLog1pToSelf();
+
+    /**
+     * Map the {@link Math#cosh(double)} function to each entry.
+     * @return a vector containing the result of applying the function to each entry
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapCosh();
+
+    /**
+     * Map the {@link Math#cosh(double)} function to each entry.
+     * <p>The instance <strong>is</strong> changed by this method.</p>
+     * @return for convenience, return this
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapCoshToSelf();
+
+    /**
+     * Map the {@link Math#sinh(double)} function to each entry.
+     * @return a vector containing the result of applying the function to each entry
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapSinh();
+
+    /**
+     * Map the {@link Math#sinh(double)} function to each entry.
+     * <p>The instance <strong>is</strong> changed by this method.</p>
+     * @return for convenience, return this
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapSinhToSelf();
+
+    /**
+     * Map the {@link Math#tanh(double)} function to each entry.
+     * @return a vector containing the result of applying the function to each entry
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapTanh();
+
+    /**
+     * Map the {@link Math#tanh(double)} function to each entry.
+     * <p>The instance <strong>is</strong> changed by this method.</p>
+     * @return for convenience, return this
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapTanhToSelf();
+
+    /**
+     * Map the {@link Math#cos(double)} function to each entry.
+     * @return a vector containing the result of applying the function to each entry
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapCos();
+
+    /**
+     * Map the {@link Math#cos(double)} function to each entry.
+     * <p>The instance <strong>is</strong> changed by this method.</p>
+     * @return for convenience, return this
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapCosToSelf();
+
+    /**
+     * Map the {@link Math#sin(double)} function to each entry.
+     * @return a vector containing the result of applying the function to each entry
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapSin();
+
+    /**
+     * Map the {@link Math#sin(double)} function to each entry.
+     * <p>The instance <strong>is</strong> changed by this method.</p>
+     * @return for convenience, return this
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapSinToSelf();
+
+    /**
+     * Map the {@link Math#tan(double)} function to each entry.
+     * @return a vector containing the result of applying the function to each entry
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapTan();
+
+    /**
+     * Map the {@link Math#tan(double)} function to each entry.
+     * <p>The instance <strong>is</strong> changed by this method.</p>
+     * @return for convenience, return this
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapTanToSelf();
+
+    /**
+     * Map the {@link Math#acos(double)} function to each entry.
+     * @return a vector containing the result of applying the function to each entry
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapAcos();
+
+    /**
+     * Map the {@link Math#acos(double)} function to each entry.
+     * <p>The instance <strong>is</strong> changed by this method.</p>
+     * @return for convenience, return this
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapAcosToSelf();
+
+    /**
+     * Map the {@link Math#asin(double)} function to each entry.
+     * @return a vector containing the result of applying the function to each entry
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapAsin();
+
+    /**
+     * Map the {@link Math#asin(double)} function to each entry.
+     * <p>The instance <strong>is</strong> changed by this method.</p>
+     * @return for convenience, return this
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapAsinToSelf();
+
+    /**
+     * Map the {@link Math#atan(double)} function to each entry.
+     * @return a vector containing the result of applying the function to each entry
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapAtan();
+
+    /**
+     * Map the {@link Math#atan(double)} function to each entry.
+     * <p>The instance <strong>is</strong> changed by this method.</p>
+     * @return for convenience, return this
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapAtanToSelf();
+
+    /**
+     * Map the 1/x function to each entry.
+     * @return a vector containing the result of applying the function to each entry
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapInv();
+
+    /**
+     * Map the 1/x function to each entry.
+     * <p>The instance <strong>is</strong> changed by this method.</p>
+     * @return for convenience, return this
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapInvToSelf();
+
+    /**
+     * Map the {@link Math#abs(double)} function to each entry.
+     * @return a vector containing the result of applying the function to each entry
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapAbs();
+
+    /**
+     * Map the {@link Math#abs(double)} function to each entry.
+     * <p>The instance <strong>is</strong> changed by this method.</p>
+     * @return for convenience, return this
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapAbsToSelf();
+
+    /**
+     * Map the {@link Math#sqrt(double)} function to each entry.
+     * @return a vector containing the result of applying the function to each entry
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapSqrt();
+
+    /**
+     * Map the {@link Math#sqrt(double)} function to each entry.
+     * <p>The instance <strong>is</strong> changed by this method.</p>
+     * @return for convenience, return this
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapSqrtToSelf();
+
+    /**
+     * Map the {@link Math#cbrt(double)} function to each entry.
+     * @return a vector containing the result of applying the function to each entry
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapCbrt();
+
+    /**
+     * Map the {@link Math#cbrt(double)} function to each entry.
+     * <p>The instance <strong>is</strong> changed by this method.</p>
+     * @return for convenience, return this
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapCbrtToSelf();
+
+    /**
+     * Map the {@link Math#ceil(double)} function to each entry.
+     * @return a vector containing the result of applying the function to each entry
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapCeil();
+
+    /**
+     * Map the {@link Math#ceil(double)} function to each entry.
+     * <p>The instance <strong>is</strong> changed by this method.</p>
+     * @return for convenience, return this
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapCeilToSelf();
+
+    /**
+     * Map the {@link Math#floor(double)} function to each entry.
+     * @return a vector containing the result of applying the function to each entry
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapFloor();
+
+    /**
+     * Map the {@link Math#floor(double)} function to each entry.
+     * <p>The instance <strong>is</strong> changed by this method.</p>
+     * @return for convenience, return this
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapFloorToSelf();
+
+    /**
+     * Map the {@link Math#rint(double)} function to each entry.
+     * @return a vector containing the result of applying the function to each entry
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapRint();
+
+    /**
+     * Map the {@link Math#rint(double)} function to each entry.
+     * <p>The instance <strong>is</strong> changed by this method.</p>
+     * @return for convenience, return this
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapRintToSelf();
+
+    /**
+     * Map the {@link Math#signum(double)} function to each entry.
+     * @return a vector containing the result of applying the function to each entry
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapSignum();
+
+    /**
+     * Map the {@link Math#signum(double)} function to each entry.
+     * <p>The instance <strong>is</strong> changed by this method.</p>
+     * @return for convenience, return this
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapSignumToSelf();
+
+    /**
+     * Map the {@link Math#ulp(double)} function to each entry.
+     * @return a vector containing the result of applying the function to each entry
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapUlp();
+
+    /**
+     * Map the {@link Math#ulp(double)} function to each entry.
+     * <p>The instance <strong>is</strong> changed by this method.</p>
+     * @return for convenience, return this
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    RealVector mapUlpToSelf();
+
+    /**
+     * 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 org.apache.commons.math.exception.DimensionMismatchException
+     * if {@code v} is not the same size as this vector.
+     */
+    RealVector ebeMultiply(RealVector v);
+
+    /**
+     * 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 org.apache.commons.math.exception.DimensionMismatchException
+     * if {@code v} is not the same size as this vector.
+     */
+    RealVector ebeMultiply(double[] v);
+
+    /**
+     * 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 org.apache.commons.math.exception.DimensionMismatchException
+     * if {@code v} is not the same size as this vector.
+     */
+    RealVector ebeDivide(RealVector v);
+
+    /**
+     * 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 org.apache.commons.math.exception.DimensionMismatchException
+     * if {@code v} is not the same size as this vector.
+     */
+    RealVector ebeDivide(double[] v);
+
+    /**
+     * Returns vector entries as a double array.
+     * @return double array of entries
+     */
+     double[] getData();
+
+    /**
+     * Compute the dot product.
+     * @param v vector with which dot product should be computed
+     * @return the scalar dot product between instance and v
+     * @throws org.apache.commons.math.exception.DimensionMismatchException
+     * if {@code v} is not the same size as this vector.
+     */
+    double dotProduct(RealVector v);
+
+    /**
+     * Compute the dot product.
+     * @param v vector with which dot product should be computed
+     * @return the scalar dot product between instance and v
+     * @throws org.apache.commons.math.exception.DimensionMismatchException
+     * if {@code v} is not the same size as this vector.
+     */
+    double dotProduct(double[] v);
+
+    /**
+     * 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.</p>
+     * @return norm
+     * @see #getL1Norm()
+     * @see #getLInfNorm()
+     * @see #getDistance(RealVector)
+     */
+    double getNorm();
+
+    /**
+     * 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 elements.</p>
+     * @return norm
+     * @see #getNorm()
+     * @see #getLInfNorm()
+     * @see #getL1Distance(RealVector)
+     */
+    double getL1Norm();
+
+    /**
+     * 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 elements.</p>
+     * @return norm
+     * @see #getNorm()
+     * @see #getL1Norm()
+     * @see #getLInfDistance(RealVector)
+     */
+    double getLInfNorm();
+
+    /**
+     * 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
+     * elements differences, or euclidian distance.</p>
+     * @param v vector to which distance is requested
+     * @return distance between two vectors.
+     * @throws org.apache.commons.math.exception.DimensionMismatchException
+     * if {@code v} is not the same size as this vector.
+     * @see #getL1Distance(RealVector)
+     * @see #getLInfDistance(RealVector)
+     * @see #getNorm()
+     */
+    double getDistance(RealVector v);
+
+    /**
+     * 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
+     * elements differences, or euclidian distance.</p>
+     * @param v vector to which distance is requested
+     * @return distance between two vectors.
+     * @throws org.apache.commons.math.exception.DimensionMismatchException
+     * if {@code v} is not the same size as this vector.
+     * @see #getL1Distance(double[])
+     * @see #getLInfDistance(double[])
+     * @see #getNorm()
+     */
+    double getDistance(double[] v);
+
+    /**
+     * 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
+     * elements differences.</p>
+     * @param v vector to which distance is requested
+     * @return distance between two vectors.
+     * @throws org.apache.commons.math.exception.DimensionMismatchException
+     * if {@code v} is not the same size as this vector.
+     * @see #getDistance(RealVector)
+     * @see #getLInfDistance(RealVector)
+     * @see #getL1Norm()
+     */
+    double getL1Distance(RealVector v);
+
+    /**
+     * 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
+     * elements differences.</p>
+     * @param v vector to which distance is requested
+     * @return distance between two vectors.
+     * @throws org.apache.commons.math.exception.DimensionMismatchException
+     * if {@code v} is not the same size as this vector.
+     * @see #getDistance(double[])
+     * @see #getLInfDistance(double[])
+     * @see #getL1Norm()
+     */
+    double getL1Distance(double[] v);
+
+    /**
+     * 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
+     * elements differences.</p>
+     * @param v vector to which distance is requested
+     * @return distance between two vectors.
+     * @throws org.apache.commons.math.exception.DimensionMismatchException
+     * if {@code v} is not the same size as this vector.
+     * @see #getDistance(RealVector)
+     * @see #getL1Distance(RealVector)
+     * @see #getLInfNorm()
+     */
+    double getLInfDistance(RealVector v);
+
+    /**
+     * 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
+     * elements differences.</p>
+     * @param v vector to which distance is requested
+     * @return distance between two vectors.
+     * @throws org.apache.commons.math.exception.DimensionMismatchException
+     * if {@code v} is not the same size as this vector.
+     * @see #getDistance(double[])
+     * @see #getL1Distance(double[])
+     * @see #getLInfNorm()
+     */
+    double getLInfDistance(double[] v);
+
+    /** Creates a unit vector pointing in the direction of this vector.
+     * <p>The instance is not changed by this method.</p>
+     * @return a unit vector pointing in direction of this vector
+     * @exception ArithmeticException if the norm is null
+     */
+    RealVector unitVector();
+
+    /** Converts this vector into a unit vector.
+     * <p>The instance itself is changed by this method.</p>
+     * @throws ArithmeticException
+     * if the norm is zero.
+     */
+    void unitize();
+
+    /** 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 v
+     * @throws org.apache.commons.math.exception.DimensionMismatchException
+     * if {@code v} is not the same size as this vector.
+     */
+    RealVector projection(RealVector v);
+
+    /** 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 v
+     * @throws org.apache.commons.math.exception.DimensionMismatchException
+     * if {@code v} is not the same size as this vector.
+     */
+    RealVector projection(double[] v);
+
+    /**
+     * Compute the outer product.
+     * @param v vector with which outer product should be computed
+     * @return the square matrix outer product between instance and v
+     * @throws org.apache.commons.math.exception.DimensionMismatchException
+     * if {@code v} is not the same size as this vector.
+     */
+    RealMatrix outerProduct(RealVector v);
+
+    /**
+     * Compute the outer product.
+     * @param v vector with which outer product should be computed
+     * @return the square matrix outer product between instance and v
+     * @throws org.apache.commons.math.exception.DimensionMismatchException
+     * if {@code v} is not the same size as this vector.
+     */
+    RealMatrix outerProduct(double[] 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 org.apache.commons.math.exception.OutOfRangeException
+     * if the index is not valid.
+     * @see #setEntry(int, double)
+     */
+    double getEntry(int index);
+
+    /**
+     * Set a single element.
+     * @param index element index.
+     * @param value new value for the element.
+     * @throws org.apache.commons.math.exception.OutOfRangeException
+     * if the index is not valid.
+     * @see #getEntry(int)
+     */
+    void setEntry(int index, double value);
+
+    /**
+     * 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
+     */
+    RealVector append(RealVector v);
+
+    /**
+     * Construct a vector by appending a double to this vector.
+     * @param d double to append.
+     * @return a new vector
+     */
+    RealVector append(double d);
+
+    /**
+     * Construct a vector by appending a double array to this vector.
+     * @param a double array to append.
+     * @return a new vector
+     */
+    RealVector append(double[] a);
+
+    /**
+     * 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 org.apache.commons.math.exception.OutOfRangeException
+     * if the index is not valid.
+     */
+    RealVector getSubVector(int index, int n);
+
+    /**
+     * Set a set of consecutive elements.
+     * @param index index of first element to be set.
+     * @param v vector containing the values to set.
+     * @throws org.apache.commons.math.exception.OutOfRangeException
+     * if the index is not valid.
+     * @see #setSubVector(int, double[])
+     */
+    void setSubVector(int index, RealVector v);
+
+    /**
+     * Set a set of consecutive elements.
+     * @param index index of first element to be set.
+     * @param v vector containing the values to set.
+     * @throws org.apache.commons.math.exception.OutOfRangeException
+     * if the index is not valid.
+     * @see #setSubVector(int, RealVector)
+     */
+    void setSubVector(int index, double[] v);
+
+    /**
+     * Set all elements to a single value.
+     * @param value single value to set for all elements
+     */
+    void set(double value);
+
+    /**
+     * Convert the vector to a double array.
+     * <p>The array is independent from vector data, it's elements
+     * are copied.</p>
+     * @return array containing a copy of vector elements
+     */
+    double[] toArray();
+
+    /**
+     * 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.
+     */
+    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.
+     */
+    boolean isInfinite();
+}
diff --git a/src/main/java/org/apache/commons/math/linear/RealVectorFormat.java b/src/main/java/org/apache/commons/math/linear/RealVectorFormat.java
new file mode 100644
index 0000000..ecefba2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/RealVectorFormat.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.math.linear;
+
+import java.text.FieldPosition;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.util.CompositeFormat;
+
+/**
+ * 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>
+ * <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>
+ *
+ * @version $Revision: 1003886 $ $Date: 2010-10-02 23:04:44 +0200 (sam. 02 oct. 2010) $
+ * @since 2.0
+ */
+public class RealVectorFormat extends CompositeFormat {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -708767813036157690L;
+
+    /** 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.</p>
+     */
+    public RealVectorFormat() {
+        this(DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_SEPARATOR, 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, 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.</p>
+     * @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(getDefaultNumberFormat(locale));
+    }
+
+    /**
+     * This static method calls {@link #format(Object)} on a default instance of
+     * RealVectorFormat.
+     *
+     * @param v RealVector object to format
+     * @return A formatted vector
+     */
+    public static String formatRealVector(RealVector v) {
+        return getInstance().format(v);
+    }
+
+    /**
+     * 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);
+            }
+            formatDouble(vector.getEntry(i), format, toAppendTo, pos);
+        }
+
+        // format suffix
+        toAppendTo.append(suffix);
+
+        return toAppendTo;
+
+    }
+
+    /**
+     * Formats a object to produce a string.
+     * <p><code>obj</code> must be a  {@link RealVector} object. Any other type of
+     * object will result in an {@link IllegalArgumentException} being thrown.</p>
+     * @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 IllegalArgumentException is <code>obj</code> is not a valid type.
+     */
+    @Override
+    public StringBuffer format(Object obj, StringBuffer toAppendTo,
+                               FieldPosition pos) {
+
+        if (obj instanceof RealVector) {
+            return format( (RealVector)obj, toAppendTo, pos);
+        }
+
+        throw MathRuntimeException.createIllegalArgumentException(
+              LocalizedFormats.CANNOT_FORMAT_INSTANCE_AS_REAL_VECTOR,
+              obj.getClass().getName());
+
+    }
+
+    /**
+     * Parses a string to produce a {@link RealVector} object.
+     * @param source the string to parse
+     * @return the parsed {@link RealVector} object.
+     * @exception ParseException if the beginning of the specified string
+     *            cannot be parsed.
+     */
+    public ArrayRealVector parse(String source) throws ParseException {
+        ParsePosition parsePosition = new ParsePosition(0);
+        ArrayRealVector result = parse(source, parsePosition);
+        if (parsePosition.getIndex() == 0) {
+            throw MathRuntimeException.createParseException(
+                    parsePosition.getErrorIndex(),
+                    LocalizedFormats.UNPARSEABLE_REAL_VECTOR, source);
+        }
+        return result;
+    }
+
+    /**
+     * Parses a string to produce a {@link RealVector} object.
+     * @param source the 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
+        parseAndIgnoreWhitespace(source, pos);
+        if (!parseFixedstring(source, trimmedPrefix, pos)) {
+            return null;
+        }
+
+        // parse components
+        List<Number> components = new ArrayList<Number>();
+        for (boolean loop = true; loop;){
+
+            if (!components.isEmpty()) {
+                parseAndIgnoreWhitespace(source, pos);
+                if (!parseFixedstring(source, trimmedSeparator, pos)) {
+                    loop = false;
+                }
+            }
+
+            if (loop) {
+                parseAndIgnoreWhitespace(source, pos);
+                Number component = 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
+        parseAndIgnoreWhitespace(source, pos);
+        if (!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);
+
+    }
+
+    /**
+     * Parses a string to produce a object.
+     * @param source the string to parse
+     * @param pos input/ouput parsing parameter.
+     * @return the parsed object.
+     * @see java.text.Format#parseObject(java.lang.String, java.text.ParsePosition)
+     */
+    @Override
+    public Object parseObject(String source, ParsePosition pos) {
+        return parse(source, pos);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/SingularMatrixException.java b/src/main/java/org/apache/commons/math/linear/SingularMatrixException.java
new file mode 100644
index 0000000..a14cf69
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/SingularMatrixException.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.math.linear;
+
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+
+/**
+ * Thrown when a matrix is singular.
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ * @since 2.0
+ */
+public class SingularMatrixException extends InvalidMatrixException {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -7379143356784298432L;
+
+    /**
+     * Construct an exception with a default message.
+     */
+    public SingularMatrixException() {
+        super(LocalizedFormats.SINGULAR_MATRIX);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/SingularValueDecomposition.java b/src/main/java/org/apache/commons/math/linear/SingularValueDecomposition.java
new file mode 100644
index 0000000..5b45cde
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/SingularValueDecomposition.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.linear;
+
+
+
+/**
+ * An interface to classes that implement an algorithm to calculate the
+ * Singular Value Decomposition of a real 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>
+ * <p>This interface 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:</p>
+ * <ul>
+ *   <li>the <code>norm2</code> method which has been renamed as {@link #getNorm()
+ *   getNorm},</li>
+ *   <li>the <code>cond</code> method which has been renamed as {@link
+ *   #getConditionNumber() getConditionNumber},</li>
+ *   <li>the <code>rank</code> method which has been renamed as {@link #getRank()
+ *   getRank},</li>
+ *   <li>a {@link #getUT() getUT} method has been added,</li>
+ *   <li>a {@link #getVT() getVT} method has been added,</li>
+ *   <li>a {@link #getSolver() getSolver} method has been added,</li>
+ *   <li>a {@link #getCovariance(double) getCovariance} method has been added.</li>
+ * </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>
+ * @version $Revision: 928081 $ $Date: 2010-03-26 23:36:38 +0100 (ven. 26 mars 2010) $
+ * @since 2.0
+ */
+public interface SingularValueDecomposition {
+
+    /**
+     * Returns the matrix U of the decomposition.
+     * <p>U is an orthogonal matrix, i.e. its transpose is also its inverse.</p>
+     * @return the U matrix
+     * @see #getUT()
+     */
+    RealMatrix getU();
+
+    /**
+     * Returns the transpose of the matrix U of the decomposition.
+     * <p>U is an orthogonal matrix, i.e. its transpose is also its inverse.</p>
+     * @return the U matrix (or null if decomposed matrix is singular)
+     * @see #getU()
+     */
+    RealMatrix getUT();
+
+    /**
+     * 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.</p>
+     * @return the &Sigma; matrix
+     */
+    RealMatrix getS();
+
+    /**
+     * 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.</p>
+     * @return the diagonal elements of the &Sigma; matrix
+     */
+    double[] getSingularValues();
+
+    /**
+     * Returns the matrix V of the decomposition.
+     * <p>V is an orthogonal matrix, i.e. its transpose is also its inverse.</p>
+     * @return the V matrix (or null if decomposed matrix is singular)
+     * @see #getVT()
+     */
+    RealMatrix getV();
+
+    /**
+     * Returns the transpose of the matrix V of the decomposition.
+     * <p>V is an orthogonal matrix, i.e. its transpose is also its inverse.</p>
+     * @return the V matrix (or null if decomposed matrix is singular)
+     * @see #getV()
+     */
+    RealMatrix getVT();
+
+    /**
+     * 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.</p>
+     * @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
+     */
+    RealMatrix getCovariance(double minSingularValue) throws IllegalArgumentException;
+
+    /**
+     * 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).</p>
+     * @return norm
+     */
+    double getNorm();
+
+    /**
+     * Return the condition number of the matrix.
+     * @return condition number of the matrix
+     */
+    double getConditionNumber();
+
+    /**
+     * 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.</p>
+     * @return effective numerical matrix rank
+     */
+    int getRank();
+
+    /**
+     * Get a solver for finding the A &times; X = B solution in least square sense.
+     * @return a solver
+     */
+    DecompositionSolver getSolver();
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/SingularValueDecompositionImpl.java b/src/main/java/org/apache/commons/math/linear/SingularValueDecompositionImpl.java
new file mode 100644
index 0000000..d776b66
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/SingularValueDecompositionImpl.java
@@ -0,0 +1,379 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.linear;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * 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>
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ * @since 2.0
+ */
+public class SingularValueDecompositionImpl implements
+        SingularValueDecomposition {
+
+    /** Number of rows of the initial matrix. */
+    private int m;
+
+    /** Number of columns of the initial matrix. */
+    private int n;
+
+    /** Eigen decomposition of the tridiagonal matrix. */
+    private EigenDecomposition eigenDecomposition;
+
+    /** Singular values. */
+    private double[] singularValues;
+
+    /** Cached value of U. */
+    private RealMatrix cachedU;
+
+    /** Cached value of U<sup>T</sup>. */
+    private RealMatrix cachedUt;
+
+    /** Cached value of S. */
+    private RealMatrix cachedS;
+
+    /** Cached value of V. */
+    private RealMatrix cachedV;
+
+    /** Cached value of V<sup>T</sup>. */
+    private RealMatrix cachedVt;
+
+    /**
+     * Calculates the compact Singular Value Decomposition of the given matrix.
+     * @param matrix
+     *            The matrix to decompose.
+     * @exception InvalidMatrixException
+     *                (wrapping a
+     *                {@link org.apache.commons.math.ConvergenceException} if
+     *                algorithm fails to converge
+     */
+    public SingularValueDecompositionImpl(final RealMatrix matrix)
+            throws InvalidMatrixException {
+
+        m = matrix.getRowDimension();
+        n = matrix.getColumnDimension();
+
+        cachedU = null;
+        cachedS = null;
+        cachedV = null;
+        cachedVt = null;
+
+        double[][] localcopy = matrix.getData();
+        double[][] matATA = new double[n][n];
+        //
+        // create A^T*A
+        //
+        for (int i = 0; i < n; i++) {
+            for (int j = i; j < n; j++) {
+                matATA[i][j] = 0.0;
+                for (int k = 0; k < m; k++) {
+                    matATA[i][j] += localcopy[k][i] * localcopy[k][j];
+                }
+                matATA[j][i]=matATA[i][j];
+            }
+        }
+
+        double[][] matAAT = new double[m][m];
+        //
+        // create A*A^T
+        //
+        for (int i = 0; i < m; i++) {
+            for (int j = i; j < m; j++) {
+                matAAT[i][j] = 0.0;
+                for (int k = 0; k < n; k++) {
+                    matAAT[i][j] += localcopy[i][k] * localcopy[j][k];
+                }
+                 matAAT[j][i]=matAAT[i][j];
+            }
+        }
+        int p;
+        if (m>=n) {
+            p=n;
+            // compute eigen decomposition of A^T*A
+            eigenDecomposition = new EigenDecompositionImpl(
+                    new Array2DRowRealMatrix(matATA),1.0);
+            singularValues = eigenDecomposition.getRealEigenvalues();
+            cachedV = eigenDecomposition.getV();
+            // compute eigen decomposition of A*A^T
+            eigenDecomposition = new EigenDecompositionImpl(
+                    new Array2DRowRealMatrix(matAAT),1.0);
+            cachedU = eigenDecomposition.getV().getSubMatrix(0, m - 1, 0, p - 1);
+        } else {
+            p=m;
+            // compute eigen decomposition of A*A^T
+            eigenDecomposition = new EigenDecompositionImpl(
+                    new Array2DRowRealMatrix(matAAT),1.0);
+            singularValues = eigenDecomposition.getRealEigenvalues();
+            cachedU = eigenDecomposition.getV();
+
+            // compute eigen decomposition of A^T*A
+            eigenDecomposition = new EigenDecompositionImpl(
+                    new Array2DRowRealMatrix(matATA),1.0);
+            cachedV = eigenDecomposition.getV().getSubMatrix(0,n-1,0,p-1);
+        }
+        for (int i = 0; i < p; i++) {
+            singularValues[i] = FastMath.sqrt(FastMath.abs(singularValues[i]));
+        }
+        // Up to this point, U and V are computed independently of each other.
+        // There still a sign indetermination of each column of, say, U.
+        // The sign is set such that A.V_i=sigma_i.U_i (i<=p)
+        // The right sign corresponds to a positive dot product of A.V_i and U_i
+        for (int i = 0; i < p; i++) {
+          RealVector tmp = cachedU.getColumnVector(i);
+          double product=matrix.operate(cachedV.getColumnVector(i)).dotProduct(tmp);
+          if (product<0) {
+            cachedU.setColumnVector(i, tmp.mapMultiply(-1.0));
+          }
+        }
+    }
+
+    /** {@inheritDoc} */
+    public RealMatrix getU() throws InvalidMatrixException {
+        // return the cached matrix
+        return cachedU;
+
+    }
+
+    /** {@inheritDoc} */
+    public RealMatrix getUT() throws InvalidMatrixException {
+
+        if (cachedUt == null) {
+            cachedUt = getU().transpose();
+        }
+
+        // return the cached matrix
+        return cachedUt;
+
+    }
+
+    /** {@inheritDoc} */
+    public RealMatrix getS() throws InvalidMatrixException {
+
+        if (cachedS == null) {
+
+            // cache the matrix for subsequent calls
+            cachedS = MatrixUtils.createRealDiagonalMatrix(singularValues);
+
+        }
+        return cachedS;
+    }
+
+    /** {@inheritDoc} */
+    public double[] getSingularValues() throws InvalidMatrixException {
+        return singularValues.clone();
+    }
+
+    /** {@inheritDoc} */
+    public RealMatrix getV() throws InvalidMatrixException {
+        // return the cached matrix
+        return cachedV;
+
+    }
+
+    /** {@inheritDoc} */
+    public RealMatrix getVT() throws InvalidMatrixException {
+
+        if (cachedVt == null) {
+            cachedVt = getV().transpose();
+        }
+
+        // return the cached matrix
+        return cachedVt;
+
+    }
+
+    /** {@inheritDoc} */
+    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 MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.TOO_LARGE_CUTOFF_SINGULAR_VALUE,
+                    minSingularValue, singularValues[0]);
+        }
+
+        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);
+
+    }
+
+    /** {@inheritDoc} */
+    public double getNorm() throws InvalidMatrixException {
+        return singularValues[0];
+    }
+
+    /** {@inheritDoc} */
+    public double getConditionNumber() throws InvalidMatrixException {
+        return singularValues[0] / singularValues[singularValues.length - 1];
+    }
+
+    /** {@inheritDoc} */
+    public int getRank() throws IllegalStateException {
+
+        final double threshold = FastMath.max(m, n) * FastMath.ulp(singularValues[0]);
+
+        for (int i = singularValues.length - 1; i >= 0; --i) {
+            if (singularValues[i] > threshold) {
+                return i + 1;
+            }
+        }
+        return 0;
+
+    }
+
+    /** {@inheritDoc} */
+    public DecompositionSolver getSolver() {
+        return new Solver(singularValues, getUT(), getV(), getRank() == Math
+                .max(m, n));
+    }
+
+    /** 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
+         *            singularValues
+         * @param uT
+         *            U<sup>T</sup> matrix of the decomposition
+         * @param v
+         *            V matrix of the decomposition
+         * @param nonSingular
+         *            singularity indicator
+         */
+        private Solver(final double[] singularValues, final RealMatrix uT,
+                final RealMatrix v, final boolean nonSingular) {
+            double[][] suT = uT.getData();
+            for (int i = 0; i < singularValues.length; ++i) {
+                final double a;
+                if (singularValues[i]>0) {
+                 a=1.0 / singularValues[i];
+                } else {
+                 a=0.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.
+         * </p>
+         * @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
+         * @exception IllegalArgumentException
+         *                if matrices dimensions don't match
+         */
+        public double[] solve(final double[] b) throws IllegalArgumentException {
+            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.
+         * </p>
+         * @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
+         * @exception IllegalArgumentException
+         *                if matrices dimensions don't match
+         */
+        public RealVector solve(final RealVector b)
+                throws IllegalArgumentException {
+            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.
+         * </p>
+         * @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
+         * @exception IllegalArgumentException
+         *                if matrices dimensions don't match
+         */
+        public RealMatrix solve(final RealMatrix b)
+                throws IllegalArgumentException {
+            return pseudoInverse.multiply(b);
+        }
+
+        /**
+         * Check if the decomposed matrix is non-singular.
+         * @return true if the decomposed matrix is non-singular
+         */
+        public boolean isNonSingular() {
+            return nonSingular;
+        }
+
+        /**
+         * Get the pseudo-inverse of the decomposed matrix.
+         * @return inverse matrix
+         */
+        public RealMatrix getInverse() {
+            return pseudoInverse;
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/SparseFieldMatrix.java b/src/main/java/org/apache/commons/math/linear/SparseFieldMatrix.java
new file mode 100644
index 0000000..1fbfb5c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/SparseFieldMatrix.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.math.linear;
+
+import org.apache.commons.math.Field;
+import org.apache.commons.math.FieldElement;
+import org.apache.commons.math.util.OpenIntToFieldHashMap;
+
+/**
+ * Sparse matrix implementation based on an open addressed map.
+ *
+ * @param <T> the type of the field elements
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ * @since 2.0
+ */
+public class SparseFieldMatrix<T extends FieldElement<T>> extends AbstractFieldMatrix<T> {
+    /**
+     *  Serial id
+     */
+    private static final long serialVersionUID = 9078068119297757342L;
+    /** Storage for (sparse) matrix elements. */
+    private final OpenIntToFieldHashMap<T> entries;
+    /**
+     * row dimension
+     */
+    private final int rows;
+    /**
+     * column dimension
+     */
+    private final int columns;
+
+
+    /**
+     * Creates 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  the number of rows in the new matrix
+     * @param columnDimension  the number of columns in the new matrix
+     * @throws IllegalArgumentException if row or column dimension is not positive
+     */
+    public SparseFieldMatrix(final Field<T> field,
+                             final int rowDimension, final int columnDimension)
+        throws IllegalArgumentException {
+        super(field, rowDimension, columnDimension);
+        this.rows = rowDimension;
+        this.columns = columnDimension;
+        entries = new OpenIntToFieldHashMap<T>(field);
+    }
+
+    /**
+     * Copy constructor.
+     * @param other The 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 The 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)
+            throws MatrixIndexException {
+        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)
+            throws IllegalArgumentException {
+        return new SparseFieldMatrix<T>(getField(), rowDimension, columnDimension);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getColumnDimension() {
+        return columns;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public T getEntry(int row, int column) throws MatrixIndexException {
+        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)
+            throws MatrixIndexException {
+        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)
+            throws MatrixIndexException {
+        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 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/math/linear/SparseFieldVector.java b/src/main/java/org/apache/commons/math/linear/SparseFieldVector.java
new file mode 100644
index 0000000..7f4848c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/SparseFieldVector.java
@@ -0,0 +1,657 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.linear;
+
+import java.io.Serializable;
+import java.lang.reflect.Array;
+
+import org.apache.commons.math.Field;
+import org.apache.commons.math.FieldElement;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.util.OpenIntToFieldHashMap;
+
+/**
+ * This class implements the {@link FieldVector} interface with a {@link OpenIntToFieldHashMap} backing store.
+ * @param <T> the type of the field elements
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ * @since 2.0
+ */
+public class SparseFieldVector<T extends FieldElement<T>> implements FieldVector<T>, Serializable {
+
+    /**
+     *  Serial version id
+     */
+    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.
+     * <p>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</code> method ({@link #append(FieldElement)},
+     * {@link #append(FieldElement[])}, {@link #append(FieldVector)},
+     * {@link #append(SparseFieldVector)}) to gather data into this vector.</p>
+     * @param field field to which the elements belong
+     */
+    public SparseFieldVector(Field<T> field) {
+        this(field, 0);
+    }
+
+
+    /**
+     * Construct a (dimension)-length vector of zeros.
+     * @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 The original vector
+     * @param resize The amount to resize it
+     */
+    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 The size of the vector
+     * @param expectedSize The 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 The set of values to create from
+     */
+    public SparseFieldVector(Field<T> field, T[] 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 The instance to copy from
+     */
+    public SparseFieldVector(SparseFieldVector<T> v) {
+        field = v.field;
+        virtualSize = v.getDimension();
+        entries = new OpenIntToFieldHashMap<T>(v.getEntries());
+    }
+
+    /**
+     * Get the entries of this instance.
+     * @return entries of this instance
+     */
+    private OpenIntToFieldHashMap<T> getEntries() {
+        return entries;
+    }
+
+    /**
+     * Optimized method to add sparse vectors.
+     * @param v vector to add
+     * @return The sum of <code>this</code> and <code>v</code>
+     * @throws IllegalArgumentException If the dimensions don't match
+     */
+    public FieldVector<T> add(SparseFieldVector<T> v) throws IllegalArgumentException {
+        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;
+
+    }
+
+
+    /** {@inheritDoc} */
+    public FieldVector<T> add(T[] v) throws IllegalArgumentException {
+        checkVectorDimensions(v.length);
+        SparseFieldVector<T> res = new SparseFieldVector<T>(field,getDimension());
+        for (int i = 0; i < v.length; i++) {
+            res.setEntry(i, v[i].add(getEntry(i)));
+        }
+        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 {
+            return append(v.toArray());
+        }
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> append(T d) {
+        FieldVector<T> res = new SparseFieldVector<T>(this, 1);
+        res.setEntry(virtualSize, d);
+        return res;
+     }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> append(T[] a) {
+        FieldVector<T> res = new SparseFieldVector<T>(this, a.length);
+        for (int i = 0; i < a.length; i++) {
+            res.setEntry(i + virtualSize, a[i]);
+        }
+        return res;
+     }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> copy() {
+        return new SparseFieldVector<T>(this);
+   }
+
+    /** {@inheritDoc} */
+    public T dotProduct(FieldVector<T> v) throws IllegalArgumentException {
+        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 T dotProduct(T[] v) throws IllegalArgumentException {
+        checkVectorDimensions(v.length);
+        T res = field.getZero();
+        OpenIntToFieldHashMap<T>.Iterator iter = entries.iterator();
+        while (iter.hasNext()) {
+            int idx = iter.key();
+            T value = field.getZero();
+            if (idx < v.length) {
+                value = v[idx];
+            }
+            res = res.add(value.multiply(iter.value()));
+        }
+        return res;
+     }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> ebeDivide(FieldVector<T> v)
+        throws IllegalArgumentException {
+        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> ebeDivide(T[] v) throws IllegalArgumentException {
+        checkVectorDimensions(v.length);
+        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[iter.key()]));
+        }
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> ebeMultiply(FieldVector<T> v)throws IllegalArgumentException {
+        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} */
+     public FieldVector<T> ebeMultiply(T[] v) throws IllegalArgumentException {
+        checkVectorDimensions(v.length);
+        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[iter.key()]));
+        }
+        return res;
+    }
+
+     /** {@inheritDoc} */
+     public T[] getData() {
+        T[] res = buildArray(virtualSize);
+        OpenIntToFieldHashMap<T>.Iterator iter = entries.iterator();
+        while (iter.hasNext()) {
+            iter.advance();
+            res[iter.key()] = iter.value();
+        }
+        return res;
+     }
+
+     /** {@inheritDoc} */
+     public int getDimension() {
+        return virtualSize;
+    }
+
+     /** {@inheritDoc} */
+     public T getEntry(int index) throws MatrixIndexException {
+        checkIndex(index);
+        return entries.get(index);
+   }
+
+     /** {@inheritDoc} */
+     public Field<T> getField() {
+        return field;
+    }
+
+     /** {@inheritDoc} */
+     public FieldVector<T> getSubVector(int index, int n)
+            throws MatrixIndexException {
+        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) {
+        return copy().mapAddToSelf(d);
+   }
+
+     /** {@inheritDoc} */
+     public FieldVector<T> mapAddToSelf(T d) {
+        for (int i = 0; i < virtualSize; i++) {
+            setEntry(i, getEntry(i).add(d));
+        }
+        return this;
+    }
+
+     /** {@inheritDoc} */
+     public FieldVector<T> mapDivide(T d) {
+        return copy().mapDivideToSelf(d);
+    }
+
+     /** {@inheritDoc} */
+     public FieldVector<T> mapDivideToSelf(T d) {
+        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() {
+        return copy().mapInvToSelf();
+   }
+
+     /** {@inheritDoc} */
+     public FieldVector<T> mapInvToSelf() {
+        for (int i = 0; i < virtualSize; i++) {
+            setEntry(i, field.getOne().divide(getEntry(i)));
+        }
+        return this;
+   }
+
+     /** {@inheritDoc} */
+     public FieldVector<T> mapMultiply(T d) {
+        return copy().mapMultiplyToSelf(d);
+    }
+
+     /** {@inheritDoc} */
+     public FieldVector<T> mapMultiplyToSelf(T d) {
+        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) {
+        return copy().mapSubtractToSelf(d);
+    }
+
+     /** {@inheritDoc} */
+     public FieldVector<T> mapSubtractToSelf(T d) {
+        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 square matrix outer product between instance and v
+      * @throws IllegalArgumentException if v is not the same size as {@code this}
+      */
+    public FieldMatrix<T> outerProduct(SparseFieldVector<T> v)
+            throws IllegalArgumentException {
+        checkVectorDimensions(v.getDimension());
+        SparseFieldMatrix<T> res = new SparseFieldMatrix<T>(field, virtualSize, virtualSize);
+        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(T[] v) throws IllegalArgumentException {
+        checkVectorDimensions(v.length);
+        FieldMatrix<T> res = new SparseFieldMatrix<T>(field, virtualSize, virtualSize);
+        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 < virtualSize; col++) {
+                res.setEntry(row, col, value.multiply(v[col]));
+            }
+        }
+        return res;
+     }
+
+    /** {@inheritDoc} */
+    public FieldMatrix<T> outerProduct(FieldVector<T> v)
+    throws IllegalArgumentException {
+        if(v instanceof SparseFieldVector<?>)
+            return outerProduct((SparseFieldVector<T>)v);
+        else
+            return outerProduct(v.toArray());
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> projection(FieldVector<T> v)
+    throws IllegalArgumentException {
+        checkVectorDimensions(v.getDimension());
+        return v.mapMultiply(dotProduct(v).divide(v.dotProduct(v)));
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> projection(T[] v) throws IllegalArgumentException {
+        checkVectorDimensions(v.length);
+        return projection(new SparseFieldVector<T>(field,v));
+    }
+
+    /** {@inheritDoc} */
+    public void set(T value) {
+        for (int i = 0; i < virtualSize; i++) {
+            setEntry(i, value);
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void setEntry(int index, T value) throws MatrixIndexException {
+        checkIndex(index);
+        entries.put(index, value);
+   }
+
+    /** {@inheritDoc} */
+    public void setSubVector(int index, FieldVector<T> v)
+            throws MatrixIndexException {
+        checkIndex(index);
+        checkIndex(index + v.getDimension() - 1);
+        setSubVector(index, v.getData());
+    }
+
+    /** {@inheritDoc} */
+    public void setSubVector(int index, T[] v) throws MatrixIndexException {
+        checkIndex(index);
+        checkIndex(index + v.length - 1);
+        for (int i = 0; i < v.length; i++) {
+            setEntry(i + index, v[i]);
+        }
+
+    }
+
+    /**
+     * Optimized method to subtract SparseRealVectors.
+     * @param v The vector to subtract from <code>this</code>
+     * @return The difference of <code>this</code> and <code>v</code>
+     * @throws IllegalArgumentException If the dimensions don't match
+     */
+    public SparseFieldVector<T> subtract(SparseFieldVector<T> v) throws IllegalArgumentException{
+        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 IllegalArgumentException {
+        if(v instanceof SparseFieldVector<?>)
+            return subtract((SparseFieldVector<T>)v);
+        else
+            return subtract(v.toArray());
+    }
+
+    /** {@inheritDoc} */
+    public FieldVector<T> subtract(T[] v) throws IllegalArgumentException {
+        checkVectorDimensions(v.length);
+        SparseFieldVector<T> res = new SparseFieldVector<T>(this);
+        for (int i = 0; i < v.length; i++) {
+            if (entries.containsKey(i)) {
+                res.setEntry(i, entries.get(i).subtract(v[i]));
+            } else {
+                res.setEntry(i, field.getZero().subtract(v[i]));
+            }
+        }
+        return res;
+    }
+
+    /** {@inheritDoc} */
+    public T[] toArray() {
+        return getData();
+    }
+
+    /**
+     * Check if an index is valid.
+     *
+     * @param index
+     *            index to check
+     * @exception MatrixIndexException
+     *                if index is not valid
+     */
+    private void checkIndex(final int index) throws MatrixIndexException {
+        if (index < 0 || index >= getDimension()) {
+            throw new MatrixIndexException(LocalizedFormats.INDEX_OUT_OF_RANGE,
+                                           index, 0, getDimension() - 1);
+        }
+    }
+
+    /**
+     * Check if instance dimension is equal to some expected value.
+     *
+     * @param n
+     *            expected dimension.
+     * @exception IllegalArgumentException
+     *                if the dimension is inconsistent with vector size
+     */
+    protected void checkVectorDimensions(int n) throws IllegalArgumentException {
+        if (getDimension() != n) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.VECTOR_LENGTH_MISMATCH,
+                    getDimension(), n);
+        }
+    }
+
+
+    /** {@inheritDoc} */
+    public FieldVector<T> add(FieldVector<T> v) throws IllegalArgumentException {
+        if (v instanceof SparseFieldVector<?>) {
+            return add((SparseFieldVector<T>)v);
+        } else {
+            return add(v.toArray());
+        }
+    }
+
+    /** Build an array of elements.
+     * @param length size of the array to build
+     * @return a new array
+     */
+    @SuppressWarnings("unchecked") // field is type T
+    private T[] buildArray(final int length) {
+        return (T[]) Array.newInstance(field.getZero().getClass(), length);
+    }
+
+
+    /** {@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/math/linear/SparseRealMatrix.java b/src/main/java/org/apache/commons/math/linear/SparseRealMatrix.java
new file mode 100644
index 0000000..ad1fa19
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/SparseRealMatrix.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.math.linear;
+
+/**
+ * Marker interface for {@link RealMatrix} implementations that require sparse backing storage
+ *
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ * @since 2.0
+ *
+ */
+public interface SparseRealMatrix extends RealMatrix {
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/SparseRealVector.java b/src/main/java/org/apache/commons/math/linear/SparseRealVector.java
new file mode 100644
index 0000000..adba604
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/SparseRealVector.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.
+ */
+package org.apache.commons.math.linear;
+
+/**
+ * Marker interface for RealVectors that require sparse backing storage
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ * @since 2.0
+ *
+ */
+public interface SparseRealVector extends RealVector {
+
+}
diff --git a/src/main/java/org/apache/commons/math/linear/TriDiagonalTransformer.java b/src/main/java/org/apache/commons/math/linear/TriDiagonalTransformer.java
new file mode 100644
index 0000000..9260aa0
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/TriDiagonalTransformer.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.math.linear;
+
+import java.util.Arrays;
+
+import org.apache.commons.math.util.FastMath;
+
+
+/**
+ * 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>
+ * <p>This implementation only uses the upper part of the matrix, the part below the
+ * diagonal is not accessed at all.</p>
+ * <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.</p>
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ * @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.</p>
+     * @param matrix the symmetrical matrix to transform.
+     * @exception InvalidMatrixException if matrix is not square
+     */
+    public TriDiagonalTransformer(RealMatrix matrix)
+        throws InvalidMatrixException {
+        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.</p>
+     * @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.</p>
+     * @return the Q matrix
+     */
+    public RealMatrix getQT() {
+
+        if (cachedQt == null) {
+
+            final int m = householderVectors.length;
+            cachedQt = MatrixUtils.createRealMatrix(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];
+                final double inv = 1.0 / (secondary[k - 1] * hK[k]);
+                cachedQt.setEntry(k, k, 1);
+                if (hK[k] != 0.0) {
+                    double beta = 1.0 / secondary[k - 1];
+                    cachedQt.setEntry(k, k, 1 + beta * hK[k]);
+                    for (int i = k + 1; i < m; ++i) {
+                        cachedQt.setEntry(k, i, beta * hK[i]);
+                    }
+                    for (int j = k + 1; j < m; ++j) {
+                        beta = 0;
+                        for (int i = k + 1; i < m; ++i) {
+                            beta += cachedQt.getEntry(j, i) * hK[i];
+                        }
+                        beta *= inv;
+                        cachedQt.setEntry(j, k, beta * hK[k]);
+                        for (int i = k + 1; i < m; ++i) {
+                            cachedQt.addToEntry(j, i, beta * hK[i]);
+                        }
+                    }
+                }
+            }
+            cachedQt.setEntry(0, 0, 1);
+
+        }
+
+        // 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;
+            cachedT = MatrixUtils.createRealMatrix(m, m);
+            for (int i = 0; i < m; ++i) {
+                cachedT.setEntry(i, i, main[i]);
+                if (i > 0) {
+                    cachedT.setEntry(i, i - 1, secondary[i - 1]);
+                }
+                if (i < main.length - 1) {
+                    cachedT.setEntry(i, i + 1, secondary[i]);
+                }
+            }
+
+        }
+
+        // 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.</p>
+     * @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.</p>
+     * @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.</p>
+     * @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.</p>
+     */
+    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/math/linear/package.html b/src/main/java/org/apache/commons/math/linear/package.html
new file mode 100644
index 0000000..fc1034e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/linear/package.html
@@ -0,0 +1,20 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 480440 $ $Date: 2006-11-29 08:14:12 +0100 (mer. 29 nov. 2006) $ -->
+    <body>Linear algebra support.</body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/ode/AbstractIntegrator.java b/src/main/java/org/apache/commons/math/ode/AbstractIntegrator.java
new file mode 100644
index 0000000..c17f436
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/AbstractIntegrator.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.math.ode;
+
+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;
+
+import org.apache.commons.math.ConvergenceException;
+import org.apache.commons.math.MaxEvaluationsExceededException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.ode.events.CombinedEventsManager;
+import org.apache.commons.math.ode.events.EventException;
+import org.apache.commons.math.ode.events.EventHandler;
+import org.apache.commons.math.ode.events.EventState;
+import org.apache.commons.math.ode.sampling.AbstractStepInterpolator;
+import org.apache.commons.math.ode.sampling.StepHandler;
+import org.apache.commons.math.util.FastMath;
+import org.apache.commons.math.util.MathUtils;
+
+/**
+ * Base class managing common boilerplate for all integrators.
+ * @version $Revision: 1073267 $ $Date: 2011-02-22 10:06:20 +0100 (mar. 22 févr. 2011) $
+ * @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;
+
+    /** Maximal number of evaluations allowed. */
+    private int maxEvaluations;
+
+    /** Number of evaluations already performed. */
+    private int evaluations;
+
+    /** Differential equations to integrate. */
+    private transient FirstOrderDifferentialEquations equations;
+
+    /** 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;
+        setMaxEvaluations(-1);
+        resetEvaluations();
+    }
+
+    /** 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) {
+        eventsStates.add(new EventState(handler, maxCheckInterval, convergence, maxIterationCount));
+    }
+
+    /** {@inheritDoc} */
+    public Collection<EventHandler> getEventHandlers() {
+        final List<EventHandler> list = new ArrayList<EventHandler>();
+        for (EventState state : eventsStates) {
+            list.add(state.getEventHandler());
+        }
+        return Collections.unmodifiableCollection(list);
+    }
+
+    /** {@inheritDoc} */
+    public void clearEventHandlers() {
+        eventsStates.clear();
+    }
+
+    /** Check if dense output is needed.
+     * @return true if there is at least one event handler or if
+     * one of the step handlers requires dense output
+     */
+    protected boolean requiresDenseOutput() {
+        if (!eventsStates.isEmpty()) {
+            return true;
+        }
+        for (StepHandler handler : stepHandlers) {
+            if (handler.requiresDenseOutput()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    public double getCurrentStepStart() {
+        return stepStart;
+    }
+
+    /** {@inheritDoc} */
+    public double getCurrentSignedStepsize() {
+        return stepSize;
+    }
+
+    /** {@inheritDoc} */
+    public void setMaxEvaluations(int maxEvaluations) {
+        this.maxEvaluations = (maxEvaluations < 0) ? Integer.MAX_VALUE : maxEvaluations;
+    }
+
+    /** {@inheritDoc} */
+    public int getMaxEvaluations() {
+        return maxEvaluations;
+    }
+
+    /** {@inheritDoc} */
+    public int getEvaluations() {
+        return evaluations;
+    }
+
+    /** Reset the number of evaluations to zero.
+     */
+    protected void resetEvaluations() {
+        evaluations = 0;
+    }
+
+    /** Set the differential equations.
+     * @param equations differential equations to integrate
+     * @see #computeDerivatives(double, double[], double[])
+     */
+    protected void setEquations(final FirstOrderDifferentialEquations equations) {
+        this.equations = equations;
+    }
+
+    /** 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
+     * @throws DerivativeException this user-defined exception should be used if an error is
+     * is triggered by user code
+     */
+    public void computeDerivatives(final double t, final double[] y, final double[] yDot)
+        throws DerivativeException {
+        if (++evaluations > maxEvaluations) {
+            throw new DerivativeException(new MaxEvaluationsExceededException(maxEvaluations));
+        }
+        equations.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.</p>
+     * @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
+     * @throws DerivativeException this exception is propagated to the caller if
+     * the underlying user function triggers one
+     * @exception IntegratorException if the value of one event state cannot be evaluated
+     * @since 2.2
+     */
+    protected double acceptStep(final AbstractStepInterpolator interpolator,
+                                final double[] y, final double[] yDot, final double tEnd)
+        throws DerivativeException, IntegratorException {
+
+        try {
+            double previousT = interpolator.getGlobalPreviousTime();
+            final double currentT = interpolator.getGlobalCurrentTime();
+            resetOccurred = false;
+
+            // 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> occuringEvents = 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
+                    occuringEvents.add(state);
+                }
+            }
+
+            while (!occuringEvents.isEmpty()) {
+
+                // handle the chronologically first event
+                final Iterator<EventState> iterator = occuringEvents.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);
+
+                // trigger the event
+                interpolator.setInterpolatedTime(eventT);
+                final double[] eventY = interpolator.getInterpolatedState();
+                currentEvent.stepAccepted(eventT, eventY);
+                isLastStep = currentEvent.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(eventY, 0, y, 0, y.length);
+                    return eventT;
+                }
+
+                if (currentEvent.reset(eventT, eventY)) {
+                    // some event handler has triggered changes that
+                    // invalidate the derivatives, we need to recompute them
+                    System.arraycopy(eventY, 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
+                    occuringEvents.add(currentEvent);
+                }
+
+            }
+
+            interpolator.setInterpolatedTime(currentT);
+            final double[] currentY = interpolator.getInterpolatedState();
+            for (final EventState state : eventsStates) {
+                state.stepAccepted(currentT, currentY);
+                isLastStep = isLastStep || state.stop();
+            }
+            isLastStep = isLastStep || MathUtils.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;
+        } catch (EventException se) {
+            final Throwable cause = se.getCause();
+            if ((cause != null) && (cause instanceof DerivativeException)) {
+                throw (DerivativeException) cause;
+            }
+            throw new IntegratorException(se);
+        } catch (ConvergenceException ce) {
+            throw new IntegratorException(ce);
+        }
+
+    }
+
+    /** Perform some sanity checks on the integration parameters.
+     * @param ode differential equations set
+     * @param t0 start time
+     * @param y0 state vector at t0
+     * @param t target time for the integration
+     * @param y placeholder where to put the state vector
+     * @exception IntegratorException if some inconsistency is detected
+     */
+    protected void sanityChecks(final FirstOrderDifferentialEquations ode,
+                                final double t0, final double[] y0,
+                                final double t, final double[] y)
+        throws IntegratorException {
+
+        if (ode.getDimension() != y0.length) {
+            throw new IntegratorException(
+                    LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, ode.getDimension(), y0.length);
+        }
+
+        if (ode.getDimension() != y.length) {
+            throw new IntegratorException(
+                    LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, ode.getDimension(), y.length);
+        }
+
+        if (FastMath.abs(t - t0) <= 1.0e-12 * FastMath.max(FastMath.abs(t0), FastMath.abs(t))) {
+            throw new IntegratorException(
+                    LocalizedFormats.TOO_SMALL_INTEGRATION_INTERVAL,
+                    FastMath.abs(t - t0));
+        }
+
+    }
+
+    /** Add an event handler for end time checking.
+     * <p>This method can be used to simplify handling of integration end time.
+     * It leverages the nominal stop condition with the exceptional stop
+     * conditions.</p>
+     * @param startTime integration start time
+     * @param endTime desired end time
+     * @param manager manager containing the user-defined handlers
+     * @return a new manager containing all the user-defined handlers plus a
+     * dedicated manager triggering a stop event at entTime
+     * @deprecated as of 2.2, this method is not used any more
+     */
+    @Deprecated
+    protected CombinedEventsManager addEndTimeChecker(final double startTime,
+                                                      final double endTime,
+                                                      final CombinedEventsManager manager) {
+        CombinedEventsManager newManager = new CombinedEventsManager();
+        for (final EventState state : manager.getEventsStates()) {
+            newManager.addEventHandler(state.getEventHandler(),
+                                       state.getMaxCheckInterval(),
+                                       state.getConvergence(),
+                                       state.getMaxIterationCount());
+        }
+        newManager.addEventHandler(new EndTimeChecker(endTime),
+                                   Double.POSITIVE_INFINITY,
+                                   FastMath.ulp(FastMath.max(FastMath.abs(startTime), FastMath.abs(endTime))),
+                                   100);
+        return newManager;
+    }
+
+    /** Specialized event handler to stop integration.
+     * @deprecated as of 2.2, this class is not used anymore
+     */
+    @Deprecated
+    private static class EndTimeChecker implements EventHandler {
+
+        /** Desired end time. */
+        private final double endTime;
+
+        /** Build an instance.
+         * @param endTime desired time
+         */
+        public EndTimeChecker(final double endTime) {
+            this.endTime = endTime;
+        }
+
+        /** {@inheritDoc} */
+        public int eventOccurred(double t, double[] y, boolean increasing) {
+            return STOP;
+        }
+
+        /** {@inheritDoc} */
+        public double g(double t, double[] y) {
+            return t - endTime;
+        }
+
+        /** {@inheritDoc} */
+        public void resetState(double t, double[] y) {
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/ode/ContinuousOutputModel.java b/src/main/java/org/apache/commons/math/ode/ContinuousOutputModel.java
new file mode 100644
index 0000000..acafdcb
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/ContinuousOutputModel.java
@@ -0,0 +1,379 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.ode;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.io.Serializable;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.ode.sampling.StepHandler;
+import org.apache.commons.math.ode.sampling.StepInterpolator;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * 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>
+ *
+ * <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>
+ *
+ * <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>
+ *
+ * <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>
+ *
+ * <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.math.ode.nonstiff.AdaptiveStepsizeIntegrator adaptive
+ * step size integrators}).</p>
+ *
+ * @see StepHandler
+ * @see StepInterpolator
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @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>();
+    reset();
+  }
+
+  /** Append another model at the end of the instance.
+   * @param model model to add at the end of the instance
+   * @exception DerivativeException if user code called from step interpolator
+   * finalization triggers one
+   * @exception IllegalArgumentException if the model to append is not
+   * compatible with the instance (dimension of the state vector,
+   * propagation direction, hole between the dates)
+   */
+  public void append(final ContinuousOutputModel model)
+    throws DerivativeException {
+
+    if (model.steps.size() == 0) {
+      return;
+    }
+
+    if (steps.size() == 0) {
+      initialTime = model.initialTime;
+      forward     = model.forward;
+    } else {
+
+      if (getInterpolatedState().length != model.getInterpolatedState().length) {
+          throw MathRuntimeException.createIllegalArgumentException(
+                LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE,
+                getInterpolatedState().length, model.getInterpolatedState().length);
+      }
+
+      if (forward ^ model.forward) {
+          throw MathRuntimeException.createIllegalArgumentException(
+                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 MathRuntimeException.createIllegalArgumentException(
+              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();
+
+  }
+
+  /** Determines whether this handler needs dense output.
+   * <p>The essence of this class is to provide dense output over all
+   * steps, hence it requires the internal steps to provide themselves
+   * dense output. The method therefore returns always true.</p>
+   * @return always true
+   */
+  public boolean requiresDenseOutput() {
+    return true;
+  }
+
+  /** Reset the step handler.
+   * Initialize the internal data as required before the first step is
+   * handled.
+   */
+  public void reset() {
+    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 DerivativeException if user code called from step interpolator
+   * finalization triggers one
+   */
+  public void handleStep(final StepInterpolator interpolator, final boolean isLast)
+    throws DerivativeException {
+
+    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>
+   * <p>Setting the time outside of the integration interval is now
+   * allowed (it was not allowed up to version 5.9 of Mantissa), 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>
+   * @param time time of the interpolated point
+   */
+  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.
+   * @return state vector at time {@link #getInterpolatedTime}
+   * @exception DerivativeException if user code called from step interpolator
+   * finalization triggers one
+   */
+  public double[] getInterpolatedState() throws DerivativeException {
+    return steps.get(index).getInterpolatedState();
+  }
+
+  /** 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/math/ode/DerivativeException.java b/src/main/java/org/apache/commons/math/ode/DerivativeException.java
new file mode 100644
index 0000000..28251ed
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/DerivativeException.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.math.ode;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.exception.util.DummyLocalizable;
+import org.apache.commons.math.exception.util.Localizable;
+
+/**
+ * This exception is made available to users to report
+ * the error conditions that are triggered while computing
+ * the differential equations.
+ * @version $Revision: 1072413 $ $Date: 2011-02-19 19:59:39 +0100 (sam. 19 févr. 2011) $
+ * @since 1.2
+ */
+public class DerivativeException extends MathException {
+
+  /** Serializable version identifier */
+  private static final long serialVersionUID = 5666710788967425123L;
+
+  /** 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)
+   */
+  public DerivativeException(final String specifier, final Object ... parts) {
+    this(new DummyLocalizable(specifier), parts);
+  }
+
+  /** 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 DerivativeException(final Localizable specifier, final Object ... parts) {
+    super(specifier, parts);
+  }
+
+ /** Build an instance from an underlying cause.
+   * @param cause cause for the exception
+   */
+  public DerivativeException(final Throwable cause) {
+    super(cause);
+  }
+
+}
diff --git a/src/main/java/org/apache/commons/math/ode/ExtendedFirstOrderDifferentialEquations.java b/src/main/java/org/apache/commons/math/ode/ExtendedFirstOrderDifferentialEquations.java
new file mode 100644
index 0000000..8d0d634
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/ExtendedFirstOrderDifferentialEquations.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.math.ode;
+
+
+/** This interface represents a first order differential equations set
+ * with a main set of equations and an extension set.
+ *
+ * <p>
+ * This interface is a simple extension on the {@link
+ * FirstOrderDifferentialEquations} that allows to identify which part
+ * of a complete set of differential equations correspond to the main
+ * set and which part correspond to the extension set.
+ * </p>
+ * <p>
+ * One typical use case is the computation of Jacobians. The main
+ * set of equations correspond to the raw ode, and we add to this set
+ * another bunch of equations which represent the jacobians of the
+ * main set. In that case, we want the integrator to use <em>only</em>
+ * the main set to estimate the errors and hence the step sizes. It should
+ * <em>not</em> use the additional equations in this computation. If the
+ * complete ode implements this interface, the {@link FirstOrderIntegrator
+ * integrator} will be able to know where the main set ends and where the
+ * extended set begins.
+ * </p>
+ * <p>
+ * We consider that the main set always corresponds to the first equations
+ * and the extended set to the last equations.
+ * </p>
+ *
+ * @see FirstOrderDifferentialEquations
+ *
+ * @version $Revision: 980981 $ $Date: 2010-07-31 00:03:04 +0200 (sam. 31 juil. 2010) $
+ * @since 2.2
+ */
+
+public interface ExtendedFirstOrderDifferentialEquations extends FirstOrderDifferentialEquations {
+
+    /** Return the dimension of the main set of equations.
+     * <p>
+     * The main set of equations represent the first part of an ODE state.
+     * The error estimations and adaptive step size computation should be
+     * done on this first part only, not on the final part of the state
+     * which represent an extension set of equations which are considered
+     * secondary.
+     * </p>
+     * @return dimension of the main set of equations, must be lesser than or
+     * equal to the {@link #getDimension() total dimension}
+     */
+    int getMainSetDimension();
+
+}
diff --git a/src/main/java/org/apache/commons/math/ode/FirstOrderConverter.java b/src/main/java/org/apache/commons/math/ode/FirstOrderConverter.java
new file mode 100644
index 0000000..b4712e5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/FirstOrderConverter.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.math.ode;
+
+import org.apache.commons.math.ode.DerivativeException;
+
+/** 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>
+ *
+ * <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>
+ *
+ * <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.</p>
+ *
+ * @see FirstOrderIntegrator
+ * @see FirstOrderDifferentialEquations
+ * @see SecondOrderDifferentialEquations
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @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.</p>
+   * @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
+   * @throws DerivativeException this exception is propagated to the caller if the
+   * underlying user function triggers one
+   */
+  public void computeDerivatives(final double t, final double[] y, final double[] yDot)
+      throws DerivativeException {
+
+    // 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/math/ode/FirstOrderDifferentialEquations.java b/src/main/java/org/apache/commons/math/ode/FirstOrderDifferentialEquations.java
new file mode 100644
index 0000000..7c6aacc
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/FirstOrderDifferentialEquations.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.math.ode;
+
+
+
+/** 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>
+ *
+ * <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>
+ *
+ * <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.</p>
+ *
+ * @see FirstOrderIntegrator
+ * @see FirstOrderConverter
+ * @see SecondOrderDifferentialEquations
+ *
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @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
+     * @throws DerivativeException this user-defined exception should be used if an error is
+     * is triggered by user code
+     */
+    void computeDerivatives(double t, double[] y, double[] yDot)
+        throws DerivativeException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/ode/FirstOrderIntegrator.java b/src/main/java/org/apache/commons/math/ode/FirstOrderIntegrator.java
new file mode 100644
index 0000000..b833084
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/FirstOrderIntegrator.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.math.ode;
+
+
+/** 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.</p>
+ *
+ * @see FirstOrderDifferentialEquations
+ * @see org.apache.commons.math.ode.sampling.StepHandler
+ * @see org.apache.commons.math.ode.events.EventHandler
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @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>
+   * <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.</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</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.math.ode.events.EventHandler} stops it at some point.
+   * @throws DerivativeException this exception is propagated to the caller if
+   * the underlying user function triggers one
+   * @throws IntegratorException if the integrator cannot perform integration
+   */
+  double integrate (FirstOrderDifferentialEquations equations,
+                    double t0, double[] y0,
+                    double t, double[] y) throws DerivativeException, IntegratorException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/ode/IntegratorException.java b/src/main/java/org/apache/commons/math/ode/IntegratorException.java
new file mode 100644
index 0000000..b116225
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/IntegratorException.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.math.ode;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.exception.util.Localizable;
+
+/**
+ * This exception is made available to users to report
+ * the error conditions that are triggered during integration
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ * @since 1.2
+ */
+public class IntegratorException
+  extends MathException {
+
+  /** Serializable version identifier */
+    private static final long serialVersionUID = -1607588949778036796L;
+
+    /** 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)
+     * @deprecated as of 2.2 replaced by {@link #IntegratorException(Localizable, Object...)}
+     */
+    @Deprecated
+    public IntegratorException(final String specifier, final Object ... parts) {
+      super(specifier, parts);
+    }
+
+    /** 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 IntegratorException(final Localizable specifier, final Object ... parts) {
+      super(specifier, parts);
+    }
+
+  /**
+   * Create an exception with a given root cause.
+   * @param cause  the exception or error that caused this exception to be thrown
+   */
+  public IntegratorException(final Throwable cause) {
+    super(cause);
+  }
+
+}
diff --git a/src/main/java/org/apache/commons/math/ode/MultistepIntegrator.java b/src/main/java/org/apache/commons/math/ode/MultistepIntegrator.java
new file mode 100644
index 0000000..1794878
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/MultistepIntegrator.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.math.ode;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.linear.Array2DRowRealMatrix;
+import org.apache.commons.math.linear.RealMatrix;
+import org.apache.commons.math.ode.nonstiff.AdaptiveStepsizeIntegrator;
+import org.apache.commons.math.ode.nonstiff.DormandPrince853Integrator;
+import org.apache.commons.math.ode.sampling.StepHandler;
+import org.apache.commons.math.ode.sampling.StepInterpolator;
+import org.apache.commons.math.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(k)<sub>n</sub> for k<sup>th</sup> derivative
+ * </pre></p>
+ * <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>
+ * <p>
+ * Multistep integrators with Nordsieck representation are highly sensitive to
+ * large step changes because when the step is multiplied by a 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>.
+ * </p>
+ *
+ * @see org.apache.commons.math.ode.nonstiff.AdamsBashforthIntegrator
+ * @see org.apache.commons.math.ode.nonstiff.AdamsMoultonIntegrator
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @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(k))</p>
+     */
+    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>
+     * <p>
+     * The default max growth factor is set to a quite low value: 2<sup>1/order</sup>.
+     * </p>
+     * @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
+     */
+    protected MultistepIntegrator(final String name, final int nSteps,
+                                  final int order,
+                                  final double minStep, final double maxStep,
+                                  final double scalAbsoluteTolerance,
+                                  final double scalRelativeTolerance) {
+
+        super(name, minStep, maxStep, scalAbsoluteTolerance, scalRelativeTolerance);
+
+        if (nSteps <= 0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.INTEGRATION_METHOD_NEEDS_AT_LEAST_ONE_PREVIOUS_POINT,
+                  name);
+        }
+
+        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>
+     * <p>
+     * The default max growth factor is set to a quite low value: 2<sup>1/order</sup>.
+     * </p>
+     * @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.</p>
+     * @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.</p>
+     * @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)
+     * @throws IntegratorException if the integrator cannot perform integration
+     * @throws DerivativeException this exception is propagated to the caller if
+     * the underlying user function triggers one
+     */
+    protected void start(final double t0, final double[] y0, final double t)
+        throws DerivativeException, IntegratorException {
+
+        // 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(y0.length));
+
+        // start integration, expecting a InitializationCompletedMarkerException
+        try {
+            starter.integrate(new CountingDifferentialEquations(y0.length),
+                              t0, y0, t, new double[y0.length]);
+        } catch (DerivativeException mue) {
+            if (!(mue instanceof InitializationCompletedMarkerException)) {
+                // this is not the expected nominal interruption of the start integrator
+                throw mue;
+            }
+        }
+
+        // remove the specific step handler
+        starter.clearStepHandlers();
+
+    }
+
+    /** Initialize the high order scaled derivatives at step start.
+     * @param first first scaled derivative at step start
+     * @param multistep scaled derivatives after step start (hy'1, ..., hy'k-1)
+     * will be modified
+     * @return high order scaled derivatives at step start
+     */
+    protected abstract Array2DRowRealMatrix initializeHighOrderDerivatives(final double[] first,
+                                                                           final double[][] multistep);
+
+    /** 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;
+    }
+
+    /** 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. */
+    public static interface NordsieckTransformer {
+        /** Initialize the high order scaled derivatives at step start.
+         * @param first first scaled derivative at step start
+         * @param multistep scaled derivatives after step start (hy'1, ..., hy'k-1)
+         * will be modified
+         * @return high order derivatives at step start
+         */
+        RealMatrix initializeHighOrderDerivatives(double[] first, double[][] multistep);
+    }
+
+    /** Specialized step handler storing the first step. */
+    private class NordsieckInitializer implements StepHandler {
+
+        /** Problem dimension. */
+        private final int n;
+
+        /** Simple constructor.
+         * @param n problem dimension
+         */
+        public NordsieckInitializer(final int n) {
+            this.n = n;
+        }
+
+        /** {@inheritDoc} */
+        public void handleStep(StepInterpolator interpolator, boolean isLast)
+            throws DerivativeException {
+
+            final double prev = interpolator.getPreviousTime();
+            final double curr = interpolator.getCurrentTime();
+            stepStart = prev;
+            stepSize  = (curr - prev) / (nSteps + 1);
+
+            // compute the first scaled derivative
+            interpolator.setInterpolatedTime(prev);
+            scaled = interpolator.getInterpolatedDerivatives().clone();
+            for (int j = 0; j < n; ++j) {
+                scaled[j] *= stepSize;
+            }
+
+            // compute the high order scaled derivatives
+            final double[][] multistep = new double[nSteps][];
+            for (int i = 1; i <= nSteps; ++i) {
+                interpolator.setInterpolatedTime(prev + stepSize * i);
+                final double[] msI = interpolator.getInterpolatedDerivatives().clone();
+                for (int j = 0; j < n; ++j) {
+                    msI[j] *= stepSize;
+                }
+                multistep[i - 1] = msI;
+            }
+            nordsieck = initializeHighOrderDerivatives(scaled, multistep);
+
+            // stop the integrator after the first step has been handled
+            throw new InitializationCompletedMarkerException();
+
+        }
+
+        /** {@inheritDoc} */
+        public boolean requiresDenseOutput() {
+            return true;
+        }
+
+        /** {@inheritDoc} */
+        public void reset() {
+            // nothing to do
+        }
+
+    }
+
+    /** Marker exception used ONLY to stop the starter integrator after first step. */
+    private static class InitializationCompletedMarkerException
+        extends DerivativeException {
+
+        /** Serializable version identifier. */
+        private static final long serialVersionUID = -4105805787353488365L;
+
+        /** Simple constructor. */
+        public InitializationCompletedMarkerException() {
+            super((Throwable) null);
+        }
+
+    }
+
+    /** Wrapper for differential equations, ensuring start evaluations are counted. */
+    private class CountingDifferentialEquations implements ExtendedFirstOrderDifferentialEquations {
+
+        /** Dimension of the problem. */
+        private final int dimension;
+
+        /** Simple constructor.
+         * @param dimension dimension of the problem
+         */
+        public CountingDifferentialEquations(final int dimension) {
+            this.dimension = dimension;
+        }
+
+        /** {@inheritDoc} */
+        public void computeDerivatives(double t, double[] y, double[] dot)
+                throws DerivativeException {
+            MultistepIntegrator.this.computeDerivatives(t, y, dot);
+        }
+
+        /** {@inheritDoc} */
+        public int getDimension() {
+            return dimension;
+        }
+
+        /** {@inheritDoc} */
+        public int getMainSetDimension() {
+            return mainSetDimension;
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/ode/ODEIntegrator.java b/src/main/java/org/apache/commons/math/ode/ODEIntegrator.java
new file mode 100644
index 0000000..6343478
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/ODEIntegrator.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.math.ode;
+
+import java.util.Collection;
+
+import org.apache.commons.math.ode.events.EventHandler;
+import org.apache.commons.math.ode.sampling.StepHandler;
+
+/**
+ * This interface defines the common parts shared by integrators
+ * for first and second order differential equations.
+ * @see FirstOrderIntegrator
+ * @see SecondOrderIntegrator
+ * @version $Revision: 1061507 $ $Date: 2011-01-20 21:55:00 +0100 (jeu. 20 janv. 2011) $
+ * @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.</p>
+     * @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.
+     * @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);
+
+    /** 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>
+     * <p>The result is undefined if the method is called outside of
+     * calls to <code>integrate</code>.</p>
+     * @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>
+     * <p>The result is undefined if the method is called outside of
+     * calls to <code>integrate</code>.</p>
+     * @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.</p>
+     * @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.
+     * </p>
+     * @return number of evaluations of the differential equations function
+     */
+    int getEvaluations();
+
+}
diff --git a/src/main/java/org/apache/commons/math/ode/SecondOrderDifferentialEquations.java b/src/main/java/org/apache/commons/math/ode/SecondOrderDifferentialEquations.java
new file mode 100644
index 0000000..d5e7324
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/SecondOrderDifferentialEquations.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.math.ode;
+
+import org.apache.commons.math.ode.DerivativeException;
+
+/** 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>
+ *
+ * <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>
+ *
+ * <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.</p>
+ *
+ * @see SecondOrderIntegrator
+ * @see FirstOrderConverter
+ * @see FirstOrderDifferentialEquations
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @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
+     * @throws DerivativeException this user-defined exception should be used if an error is
+     * is triggered by user code
+     */
+    void computeSecondDerivatives(double t, double[] y, double[] yDot, double[] yDDot)
+        throws DerivativeException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/ode/SecondOrderIntegrator.java b/src/main/java/org/apache/commons/math/ode/SecondOrderIntegrator.java
new file mode 100644
index 0000000..f744e85
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/SecondOrderIntegrator.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.math.ode;
+
+import org.apache.commons.math.ode.DerivativeException;
+
+
+/** 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.</p>
+ *
+ * @see SecondOrderDifferentialEquations
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @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 IntegratorException if the integrator cannot perform integration
+   * @throws DerivativeException this exception is propagated to the caller if the
+   * underlying user function triggers one
+   */
+  void integrate(SecondOrderDifferentialEquations equations,
+                 double t0, double[] y0, double[] yDot0,
+                 double t, double[] y, double[] yDot)
+      throws DerivativeException, IntegratorException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/ode/events/CombinedEventsManager.java b/src/main/java/org/apache/commons/math/ode/events/CombinedEventsManager.java
new file mode 100644
index 0000000..1886702
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/events/CombinedEventsManager.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.math.ode.events;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.commons.math.ConvergenceException;
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.ode.IntegratorException;
+import org.apache.commons.math.ode.sampling.StepInterpolator;
+
+/** This class manages several {@link EventHandler event handlers} during integration.
+ *
+ * @see EventHandler
+ * @see EventState
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @since 1.2
+ * @deprecated as of 2.2, this class is not used anymore
+ */
+@Deprecated
+public class CombinedEventsManager {
+
+    /** Events states. */
+    private final List<EventState> states;
+
+    /** First active event. */
+    private EventState first;
+
+    /** Initialization indicator. */
+    private boolean initialized;
+
+    /** Simple constructor.
+     * Create an empty manager
+     */
+    public CombinedEventsManager() {
+        states      = new ArrayList<EventState>();
+        first       = null;
+        initialized = false;
+    }
+
+    /** Add an events handler.
+     * @param handler event handler
+     * @param maxCheckInterval maximal time interval between events
+     * 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 #getEventsHandlers()
+     * @see #clearEventsHandlers()
+     */
+    public void addEventHandler(final EventHandler handler, final double maxCheckInterval,
+                                final double convergence, final int maxIterationCount) {
+        states.add(new EventState(handler, maxCheckInterval,
+                                  convergence, maxIterationCount));
+    }
+
+    /** Get all the events handlers that have been added to the manager.
+     * @return an unmodifiable collection of the added event handlers
+     * @see #addEventHandler(EventHandler, double, double, int)
+     * @see #clearEventsHandlers()
+     * @see #getEventsStates()
+     */
+    public Collection<EventHandler> getEventsHandlers() {
+        final List<EventHandler> list = new ArrayList<EventHandler>();
+        for (EventState state : states) {
+            list.add(state.getEventHandler());
+        }
+        return Collections.unmodifiableCollection(list);
+    }
+
+    /** Remove all the events handlers that have been added to the manager.
+     * @see #addEventHandler(EventHandler, double, double, int)
+     * @see #getEventsHandlers()
+     */
+    public void clearEventsHandlers() {
+        states.clear();
+    }
+
+    /** Get all the events state wrapping the handlers that have been added to the manager.
+     * @return a collection of the events states
+     * @see #getEventsHandlers()
+     */
+    public Collection<EventState> getEventsStates() {
+        return states;
+    }
+
+    /** Check if the manager does not manage any event handlers.
+     * @return true if manager is empty
+     */
+    public boolean isEmpty() {
+        return states.isEmpty();
+    }
+
+    /** Evaluate the impact of the proposed step on all managed
+     * event handlers.
+     * @param interpolator step interpolator for the proposed step
+     * @return true if at least one event handler triggers an event
+     * before the end of the proposed step (this implies the step should
+     * be rejected)
+     * @exception DerivativeException if the interpolator fails to
+     * compute the function somewhere within the step
+     * @exception IntegratorException if an event cannot be located
+     */
+    public boolean evaluateStep(final StepInterpolator interpolator)
+    throws DerivativeException, IntegratorException {
+
+        try {
+
+            first = null;
+            if (states.isEmpty()) {
+                // there is nothing to do, return now to avoid setting the
+                // interpolator time (and hence avoid unneeded calls to the
+                // user function due to interpolator finalization)
+                return false;
+            }
+
+            if (! initialized) {
+
+                // initialize the events states
+                for (EventState state : states) {
+                    state.reinitializeBegin(interpolator);
+                }
+
+                initialized = true;
+
+            }
+
+            // check events occurrence
+            for (EventState state : states) {
+
+                if (state.evaluateStep(interpolator)) {
+                    if (first == null) {
+                        first = state;
+                    } else {
+                        if (interpolator.isForward()) {
+                            if (state.getEventTime() < first.getEventTime()) {
+                                first = state;
+                            }
+                        } else {
+                            if (state.getEventTime() > first.getEventTime()) {
+                                first = state;
+                            }
+                        }
+                    }
+                }
+
+            }
+
+            return first != null;
+
+        } catch (EventException se) {
+            final Throwable cause = se.getCause();
+            if ((cause != null) && (cause instanceof DerivativeException)) {
+                throw (DerivativeException) cause;
+            }
+            throw new IntegratorException(se);
+        } catch (ConvergenceException ce) {
+            throw new IntegratorException(ce);
+        }
+
+    }
+
+    /** Get the occurrence time of the first event triggered in the
+     * last evaluated step.
+     * @return occurrence time of the first event triggered in the last
+     * evaluated step, or </code>Double.NaN</code> if no event is
+     * triggered
+     */
+    public double getEventTime() {
+        return (first == null) ? Double.NaN : first.getEventTime();
+    }
+
+    /** Inform the event handlers that 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
+     * @exception IntegratorException if the value of one of the
+     * events states cannot be evaluated
+     */
+    public void stepAccepted(final double t, final double[] y)
+    throws IntegratorException {
+        try {
+            for (EventState state : states) {
+                state.stepAccepted(t, y);
+            }
+        } catch (EventException se) {
+            throw new IntegratorException(se);
+        }
+    }
+
+    /** 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() {
+        for (EventState state : states) {
+            if (state.stop()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /** Let the event handlers reset the state if they want.
+     * @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
+     * @exception IntegratorException if one of the events states
+     * that should reset the state fails to do it
+     */
+    public boolean reset(final double t, final double[] y)
+        throws IntegratorException {
+        try {
+            boolean resetDerivatives = false;
+            for (EventState state : states) {
+                if (state.reset(t, y)) {
+                    resetDerivatives = true;
+                }
+            }
+            return resetDerivatives;
+        } catch (EventException se) {
+            throw new IntegratorException(se);
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/ode/events/EventException.java b/src/main/java/org/apache/commons/math/ode/events/EventException.java
new file mode 100644
index 0000000..92e8e0c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/events/EventException.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.math.ode.events;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.exception.util.Localizable;
+
+/**
+ * This exception is made available to users to report
+ * the error conditions that are triggered by {@link EventHandler}
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ * @since 2.0
+ */
+public class EventException extends MathException {
+
+    /** Serialization UID. */
+    private static final long serialVersionUID = -898215297400035290L;
+
+    /** 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)
+     * @deprecated as of 2.2 replaced by {@link #EventException(Localizable, Object...)}
+     */
+     @Deprecated
+    public EventException(final String specifier, final Object ... parts) {
+        super(specifier, parts);
+    }
+
+    /** 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 EventException(final Localizable specifier, final Object ... parts) {
+        super(specifier, parts);
+    }
+
+    /**
+     * Create an exception with a given root cause.
+     * @param cause  the exception or error that caused this exception to be thrown
+     */
+    public EventException(final Throwable cause) {
+        super(cause);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/ode/events/EventHandler.java b/src/main/java/org/apache/commons/math/ode/events/EventHandler.java
new file mode 100644
index 0000000..4e50db6
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/events/EventHandler.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.math.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>
+ *
+ * @version $Revision: 1067500 $ $Date: 2011-02-05 21:11:30 +0100 (sam. 05 févr. 2011) $
+ * @since 1.2
+ */
+
+public interface EventHandler  {
+
+  /** 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>
+   */
+  int STOP = 0;
+
+  /** 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>
+   */
+  int RESET_STATE = 1;
+
+  /** 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.math.ode.FirstOrderDifferentialEquations#computeDerivatives}
+   * method).</p>
+   */
+  int RESET_DERIVATIVES = 2;
+
+  /** 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>
+   */
+  int CONTINUE = 3;
+
+  /** 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>
+   *
+   * @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
+   * @exception EventException if the switching function cannot be evaluated
+   */
+  double g(double t, double[] y) throws EventException;
+
+  /** 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.math.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 #STOP} is returned, the step handler will be called
+   *   with the <code>isLast</code> flag of the {@link
+   *   org.apache.commons.math.ode.sampling.StepHandler#handleStep handleStep}
+   *   method set to true and the integration will be stopped,</li>
+   *   <li>if {@link #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 #RESET_DERIVATIVES} is returned, the integrator
+   *   will recompute the derivatives,
+   *   <li>if {@link #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.math.ode.sampling.StepHandler StepHandler} method {@link
+   * org.apache.commons.math.ode.sampling.StepHandler#handleStep(
+   * org.apache.commons.math.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 #STOP}. As the interpolator may be used to navigate back
+   * throughout the last step (as {@link
+   * org.apache.commons.math.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.math.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.math.ode.sampling.StepHandler variable step handlers} and
+   * to the size of the fixed step for {@link
+   * org.apache.commons.math.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 #STOP}, {@link #RESET_STATE},
+   * {@link #RESET_DERIVATIVES} or {@link #CONTINUE}
+   * @exception EventException if the event occurrence triggers an error
+   */
+  int eventOccurred(double t, double[] y, boolean increasing) throws EventException;
+
+  /** 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 #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
+   * #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
+   * @exception EventException if the state cannot be reseted
+   */
+  void resetState(double t, double[] y) throws EventException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/ode/events/EventState.java b/src/main/java/org/apache/commons/math/ode/events/EventState.java
new file mode 100644
index 0000000..30cb47e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/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.math.ode.events;
+
+import org.apache.commons.math.ConvergenceException;
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.analysis.solvers.BrentSolver;
+import org.apache.commons.math.exception.MathInternalError;
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.ode.sampling.StepInterpolator;
+import org.apache.commons.math.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 (and hence the step should be reduced to ensure the
+ * event occurs at a bound rather than inside the step).</p>
+ *
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @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;
+
+    /** 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 int nextAction;
+
+    /** 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
+     */
+    public EventState(final EventHandler handler, final double maxCheckInterval,
+                      final double convergence, final int maxIterationCount) {
+        this.handler           = handler;
+        this.maxCheckInterval  = maxCheckInterval;
+        this.convergence       = FastMath.abs(convergence);
+        this.maxIterationCount = maxIterationCount;
+
+        // some dummy values ...
+        t0                = Double.NaN;
+        g0                = Double.NaN;
+        g0Positive        = true;
+        pendingEvent      = false;
+        pendingEventTime  = Double.NaN;
+        previousEventTime = Double.NaN;
+        increasing        = true;
+        nextAction        = EventHandler.CONTINUE;
+
+    }
+
+    /** Get the underlying event handler.
+     * @return underlying event handler
+     */
+    public EventHandler 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 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 EventException if the event handler
+     * value cannot be evaluated at the beginning of the step
+     */
+    public void reinitializeBegin(final StepInterpolator interpolator)
+        throws EventException {
+        try {
+            // 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 want 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
+            // less than epsilon after the solver start in the first step should be ignored,
+            // where epsilon is the convergence threshold of the event. The sign of the g
+            // function should be evaluated after this initial ignore zone, not exactly at
+            // beginning (if there are no event at the very beginning g(t0) and g(t0+epsilon)
+            // have the same sign, so this does not hurt ; if there is an event at the very
+            // beginning, g(t0) and g(t0+epsilon) have opposite signs and we want to start
+            // with the second one. Of course, the sign of epsilon depend on the integration
+            // direction (forward or backward). This explains what is done below.
+
+            final double ignoreZone = interpolator.isForward() ? getConvergence() : -getConvergence();
+            t0 = interpolator.getPreviousTime() + ignoreZone;
+            interpolator.setInterpolatedTime(t0);
+            g0 = handler.g(t0, interpolator.getInterpolatedState());
+            if (g0 == 0) {
+                // extremely rare case: there is a zero EXACTLY at end of ignore zone
+                // we will use the opposite of sign at step beginning to force ignoring this zero
+                final double tStart = interpolator.getPreviousTime();
+                interpolator.setInterpolatedTime(tStart);
+                g0Positive = handler.g(tStart, interpolator.getInterpolatedState()) <= 0;
+            } else {
+                g0Positive = g0 >= 0;
+            }
+
+        } catch (DerivativeException mue) {
+            throw new EventException(mue);
+        }
+    }
+
+    /** 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 DerivativeException if the interpolator fails to
+     * compute the switching function somewhere within the step
+     * @exception EventException if the switching function
+     * cannot be evaluated
+     * @exception ConvergenceException if an event cannot be located
+     */
+    public boolean evaluateStep(final StepInterpolator interpolator)
+        throws DerivativeException, EventException, ConvergenceException {
+
+        try {
+
+            forward = interpolator.isForward();
+            final double t1 = interpolator.getCurrentTime();
+            if (FastMath.abs(t1 - t0) < convergence) {
+                // we cannot do anything on such a small step, don't trigger any events
+                return false;
+            }
+            final double start = forward ? (t0 + convergence) : t0 - convergence;
+            final double dt    = t1 - start;
+            final int    n     = FastMath.max(1, (int) FastMath.ceil(FastMath.abs(dt) / maxCheckInterval));
+            final double h     = dt / n;
+
+            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 = start + (i + 1) * h;
+                interpolator.setInterpolatedTime(tb);
+                final double gb = handler.g(tb, interpolator.getInterpolatedState());
+
+                // 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;
+
+                    final UnivariateRealFunction f = new UnivariateRealFunction() {
+                        public double value(final double t) {
+                            try {
+                                interpolator.setInterpolatedTime(t);
+                                return handler.g(t, interpolator.getInterpolatedState());
+                            } catch (DerivativeException e) {
+                                throw new EmbeddedDerivativeException(e);
+                            } catch (EventException e) {
+                                throw new EmbeddedEventException(e);
+                            }
+                        }
+                    };
+                    final BrentSolver solver = new BrentSolver(convergence);
+
+                    if (ga * gb >= 0) {
+                        // this is a corner case:
+                        // - there was an event near ta,
+                        // - there is another event between ta and tb
+                        // - when ta was computed, convergence was reached on the "wrong side" of the interval
+                        // this implies that the real sign of ga is the same as gb, so we need to slightly
+                        // shift ta to make sure ga and gb get opposite signs and the solver won't complain
+                        // about bracketing
+                        final double epsilon = (forward ? 0.25 : -0.25) * convergence;
+                        for (int k = 0; (k < 4) && (ga * gb > 0); ++k) {
+                            ta += epsilon;
+                            try {
+                                ga = f.value(ta);
+                            } catch (FunctionEvaluationException ex) {
+                                throw new DerivativeException(ex);
+                            }
+                        }
+                        if (ga * gb > 0) {
+                            // this should never happen
+                            throw new MathInternalError();
+                        }
+                    }
+
+                    final double root;
+                    try {
+                        root = (ta <= tb) ?
+                                solver.solve(maxIterationCount, f, ta, tb) :
+                                    solver.solve(maxIterationCount, f, tb, ta);
+                    } catch (FunctionEvaluationException ex) {
+                        throw new DerivativeException(ex);
+                    }
+
+                    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, we simply ignore it
+                        ta = tb;
+                        ga = gb;
+                    } 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 (EmbeddedDerivativeException ede) {
+            throw ede.getDerivativeException();
+        } catch (EmbeddedEventException eee) {
+            throw eee.getEventException();
+        }
+
+    }
+
+    /** Get the occurrence time of the event triggered in the current step.
+     * @return occurrence time of the event triggered in the current
+     * step or positive infinity if no events are triggered
+     */
+    public double getEventTime() {
+        return pendingEvent ? pendingEventTime : Double.POSITIVE_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
+     * @exception EventException if the value of the event
+     * handler cannot be evaluated
+     */
+    public void stepAccepted(final double t, final double[] y)
+        throws EventException {
+
+        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.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.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
+     * @exception EventException if the state cannot be reseted by the event
+     * handler
+     */
+    public boolean reset(final double t, final double[] y)
+        throws EventException {
+
+        if (!(pendingEvent && (FastMath.abs(pendingEventTime - t) <= convergence))) {
+            return false;
+        }
+
+        if (nextAction == EventHandler.RESET_STATE) {
+            handler.resetState(t, y);
+        }
+        pendingEvent      = false;
+        pendingEventTime  = Double.NaN;
+
+        return (nextAction == EventHandler.RESET_STATE) ||
+               (nextAction == EventHandler.RESET_DERIVATIVES);
+
+    }
+
+    /** Local exception for embedding DerivativeException. */
+    private static class EmbeddedDerivativeException extends RuntimeException {
+
+        /** Serializable UID. */
+        private static final long serialVersionUID = 3574188382434584610L;
+
+        /** Embedded exception. */
+        private final DerivativeException derivativeException;
+
+        /** Simple constructor.
+         * @param derivativeException embedded exception
+         */
+        public EmbeddedDerivativeException(final DerivativeException derivativeException) {
+            this.derivativeException = derivativeException;
+        }
+
+        /** Get the embedded exception.
+         * @return embedded exception
+         */
+        public DerivativeException getDerivativeException() {
+            return derivativeException;
+        }
+
+    }
+
+    /** Local exception for embedding EventException. */
+    private static class EmbeddedEventException extends RuntimeException {
+
+        /** Serializable UID. */
+        private static final long serialVersionUID = -1337749250090455474L;
+
+        /** Embedded exception. */
+        private final EventException eventException;
+
+        /** Simple constructor.
+         * @param eventException embedded exception
+         */
+        public EmbeddedEventException(final EventException eventException) {
+            this.eventException = eventException;
+        }
+
+        /** Get the embedded exception.
+         * @return embedded exception
+         */
+        public EventException getEventException() {
+            return eventException;
+        }
+
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/ode/events/package.html b/src/main/java/org/apache/commons/math/ode/events/package.html
new file mode 100644
index 0000000..68dcd97
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/events/package.html
@@ -0,0 +1,96 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 613620 $ -->
+<body>
+<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.math.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) * (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>
+
+</body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/ode/jacobians/EventHandlerWithJacobians.java b/src/main/java/org/apache/commons/math/ode/jacobians/EventHandlerWithJacobians.java
new file mode 100644
index 0000000..80d6c6e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/jacobians/EventHandlerWithJacobians.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.math.ode.jacobians;
+
+import org.apache.commons.math.ode.events.EventException;
+
+/** 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>
+ *
+ * <p>Note that is is possible to register a {@link
+ * org.apache.commons.math.ode.events.EventHandler classical event handler}
+ * in the low level integrator used to build a {@link FirstOrderIntegratorWithJacobians}
+ * rather than implementing this class. The event handlers registered at low level
+ * will see the big compound state whether the event handlers defined by this interface
+ * see the original state, and its jacobians in separate arrays.</p>
+ *
+ * <p>The compound state is guaranteed to contain the original state in the first
+ * elements, followed by the jacobian with respect to initial state (in row order),
+ * followed by the jacobian with respect to parameters (in row order). If for example
+ * the original state dimension is 6 and there are 3 parameters, the compound state will
+ * be a 60 elements array. The first 6 elements will be the original state, the next 36
+ * elements will be the jacobian with respect to initial state, and the remaining 18 elements
+ * will be the jacobian with respect to parameters.</p>
+ *
+ * <p>Dealing with low level event handlers is cumbersome if one really needs the jacobians
+ * in these methods, but it also prevents many data being copied back and forth between
+ * state and jacobians on one side and compound state on the other side. So for performance
+ * reasons, it is recommended to use this interface <em>only</em> if jacobians are really
+ * needed and to use lower level handlers if only state is needed.</p>
+ *
+ * @version $Revision: 1037341 $ $Date: 2010-11-20 22:58:35 +0100 (sam. 20 nov. 2010) $
+ * @since 2.1
+ * @deprecated as of 2.2 the complete package is deprecated, it will be replaced
+ * in 3.0 by a completely rewritten implementation
+ */
+@Deprecated
+public interface EventHandlerWithJacobians  {
+
+    /** 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>
+     */
+    int STOP = 0;
+
+    /** 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>
+     */
+    int RESET_STATE = 1;
+
+    /** 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.math.ode.FirstOrderDifferentialEquations#computeDerivatives}
+     * method).</p>
+     */
+    int RESET_DERIVATIVES = 2;
+
+    /** 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>
+     */
+    int CONTINUE = 3;
+
+    /** 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>
+
+     * @param t current value of the independent <i>time</i> variable
+     * @param y array containing the current value of the state vector
+     * @param dydy0 array containing the current value of the jacobian of
+     * the state vector with respect to initial state
+     * @param dydp array containing the current value of the jacobian of
+     * the state vector with respect to parameters
+     * @return value of the g switching function
+     * @exception EventException if the switching function cannot be evaluated
+     */
+    double g(double t, double[] y, double[][] dydy0, double[][] dydp)
+        throws EventException;
+
+    /** 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.math.ode.jacobians.ODEWithJacobians
+     * 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 #STOP} is returned, the step handler will be called
+     *   with the <code>isLast</code> flag of the {@link
+     *   org.apache.commons.math.ode.jacobians.StepHandlerWithJacobians#handleStep(
+     *   StepInterpolatorWithJacobians, boolean) handleStep} method set to true and
+     *   the integration will be stopped,</li>
+     *   <li>if {@link #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 #RESET_DERIVATIVES} is returned, the integrator
+     *   will recompute the derivatives,
+     *   <li>if {@link #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.math.ode.jacobians.StepHandlerWithJacobians
+     * StepHandlerWithJacobians} method {@link
+     * org.apache.commons.math.ode.jacobians.StepHandlerWithJacobians#handleStep(
+     * StepInterpolatorWithJacobians, 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 #STOP}. As the
+     * interpolator may be used to navigate back throughout the last step (as {@link
+     * org.apache.commons.math.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 EventHandlerWithJacobians EventHandler} interface and the
+     * {@link org.apache.commons.math.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.math.ode.sampling.StepHandler variable step handlers} and
+     * to the size of the fixed step for {@link
+     * org.apache.commons.math.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 dydy0 array containing the current value of the jacobian of
+     * the state vector with respect to initial state
+     * @param dydp array containing the current value of the jacobian of
+     * the state vector with respect to parameters
+     * @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 #STOP}, {@link #RESET_STATE},
+     * {@link #RESET_DERIVATIVES} or {@link #CONTINUE}
+     * @exception EventException if the event occurrence triggers an error
+     */
+    int eventOccurred(double t, double[] y, double[][] dydy0, double[][] dydp,
+                      boolean increasing) throws EventException;
+
+    /** 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 #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
+     * #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
+     * @param dydy0 array containing the current value of the jacobian of
+     * the state vector with respect to initial state, the new jacobian
+     * should be put in the same array
+     * @param dydp array containing the current value of the jacobian of
+     * the state vector with respect to parameters, the new jacobian
+     * should be put in the same array
+     * @exception EventException if the state cannot be reseted
+     */
+    void resetState(double t, double[] y, double[][] dydy0, double[][] dydp)
+    throws EventException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/ode/jacobians/FirstOrderIntegratorWithJacobians.java b/src/main/java/org/apache/commons/math/ode/jacobians/FirstOrderIntegratorWithJacobians.java
new file mode 100644
index 0000000..dc5ca18
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/jacobians/FirstOrderIntegratorWithJacobians.java
@@ -0,0 +1,899 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.ode.jacobians;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.lang.reflect.Array;
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.MaxEvaluationsExceededException;
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.ode.ExtendedFirstOrderDifferentialEquations;
+import org.apache.commons.math.ode.FirstOrderIntegrator;
+import org.apache.commons.math.ode.IntegratorException;
+import org.apache.commons.math.ode.events.EventException;
+import org.apache.commons.math.ode.events.EventHandler;
+import org.apache.commons.math.ode.sampling.StepHandler;
+import org.apache.commons.math.ode.sampling.StepInterpolator;
+
+/** This class enhances a first order integrator for differential equations to
+ * compute also partial derivatives of the solution with respect to initial state
+ * and parameters.
+ * <p>In order to compute both the state and its derivatives, the ODE problem
+ * is extended with jacobians of the raw ODE and the variational equations are
+ * added to form a new compound problem of higher dimension. If the original ODE
+ * problem has dimension n and there are p parameters, the compound problem will
+ * have dimension n &times; (1 + n + p).</p>
+ * @see ParameterizedODE
+ * @see ODEWithJacobians
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @since 2.1
+ * @deprecated as of 2.2 the complete package is deprecated, it will be replaced
+ * in 3.0 by a completely rewritten implementation
+ */
+@Deprecated
+public class FirstOrderIntegratorWithJacobians {
+
+    /** Underlying integrator for compound problem. */
+    private final FirstOrderIntegrator integrator;
+
+    /** Raw equations to integrate. */
+    private final ODEWithJacobians ode;
+
+    /** Maximal number of evaluations allowed. */
+    private int maxEvaluations;
+
+    /** Number of evaluations already performed. */
+    private int evaluations;
+
+    /** Build an enhanced integrator using internal differentiation to compute jacobians.
+     * @param integrator underlying integrator to solve the compound problem
+     * @param ode original problem (f in the equation y' = f(t, y))
+     * @param p parameters array (may be null if {@link
+     * ParameterizedODE#getParametersDimension()
+     * getParametersDimension()} from original problem is zero)
+     * @param hY step sizes to use for computing the jacobian df/dy, must have the
+     * same dimension as the original problem
+     * @param hP step sizes to use for computing the jacobian df/dp, must have the
+     * same dimension as the original problem parameters dimension
+     * @see #FirstOrderIntegratorWithJacobians(FirstOrderIntegrator,
+     * ODEWithJacobians)
+     */
+    public FirstOrderIntegratorWithJacobians(final FirstOrderIntegrator integrator,
+                                             final ParameterizedODE ode,
+                                             final double[] p, final double[] hY, final double[] hP) {
+        checkDimension(ode.getDimension(), hY);
+        checkDimension(ode.getParametersDimension(), p);
+        checkDimension(ode.getParametersDimension(), hP);
+        this.integrator = integrator;
+        this.ode = new FiniteDifferencesWrapper(ode, p, hY, hP);
+        setMaxEvaluations(-1);
+    }
+
+    /** Build an enhanced integrator using ODE builtin jacobian computation features.
+     * @param integrator underlying integrator to solve the compound problem
+     * @param ode original problem, which can compute the jacobians by itself
+     * @see #FirstOrderIntegratorWithJacobians(FirstOrderIntegrator,
+     * ParameterizedODE, double[], double[], double[])
+     */
+    public FirstOrderIntegratorWithJacobians(final FirstOrderIntegrator integrator,
+                                             final ODEWithJacobians ode) {
+        this.integrator = integrator;
+        this.ode = ode;
+        setMaxEvaluations(-1);
+    }
+
+    /** Add a step handler to this integrator.
+     * <p>The handler will be called by the integrator for each accepted
+     * step.</p>
+     * @param handler handler for the accepted steps
+     * @see #getStepHandlers()
+     * @see #clearStepHandlers()
+     */
+    public void addStepHandler(StepHandlerWithJacobians handler) {
+        final int n = ode.getDimension();
+        final int k = ode.getParametersDimension();
+        integrator.addStepHandler(new StepHandlerWrapper(handler, n, k));
+    }
+
+    /** Get all the step handlers that have been added to the integrator.
+     * @return an unmodifiable collection of the added events handlers
+     * @see #addStepHandler(StepHandlerWithJacobians)
+     * @see #clearStepHandlers()
+     */
+    public Collection<StepHandlerWithJacobians> getStepHandlers() {
+        final Collection<StepHandlerWithJacobians> handlers =
+            new ArrayList<StepHandlerWithJacobians>();
+        for (final StepHandler handler : integrator.getStepHandlers()) {
+            if (handler instanceof StepHandlerWrapper) {
+                handlers.add(((StepHandlerWrapper) handler).getHandler());
+            }
+        }
+        return handlers;
+    }
+
+    /** Remove all the step handlers that have been added to the integrator.
+     * @see #addStepHandler(StepHandlerWithJacobians)
+     * @see #getStepHandlers()
+     */
+    public void clearStepHandlers() {
+        integrator.clearStepHandlers();
+    }
+
+    /** 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
+     * @see #getEventHandlers()
+     * @see #clearEventHandlers()
+     */
+    public void addEventHandler(EventHandlerWithJacobians handler,
+                                double maxCheckInterval,
+                                double convergence,
+                                int maxIterationCount) {
+        final int n = ode.getDimension();
+        final int k = ode.getParametersDimension();
+        integrator.addEventHandler(new EventHandlerWrapper(handler, n, k),
+                                   maxCheckInterval, convergence, maxIterationCount);
+    }
+
+    /** Get all the event handlers that have been added to the integrator.
+     * @return an unmodifiable collection of the added events handlers
+     * @see #addEventHandler(EventHandlerWithJacobians, double, double, int)
+     * @see #clearEventHandlers()
+     */
+    public Collection<EventHandlerWithJacobians> getEventHandlers() {
+        final Collection<EventHandlerWithJacobians> handlers =
+            new ArrayList<EventHandlerWithJacobians>();
+        for (final EventHandler handler : integrator.getEventHandlers()) {
+            if (handler instanceof EventHandlerWrapper) {
+                handlers.add(((EventHandlerWrapper) handler).getHandler());
+            }
+        }
+        return handlers;
+    }
+
+    /** Remove all the event handlers that have been added to the integrator.
+     * @see #addEventHandler(EventHandlerWithJacobians, double, double, int)
+     * @see #getEventHandlers()
+     */
+    public void clearEventHandlers() {
+        integrator.clearEventHandlers();
+    }
+
+    /** Integrate the differential equations and the variational equations up to the given time.
+     * <p>This method solves an Initial Value Problem (IVP) and also computes the derivatives
+     * of the solution with respect to initial state and parameters. This can be used as
+     * a basis to solve Boundary Value Problems (BVP).</p>
+     * <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.</p>
+     * @param t0 initial time
+     * @param y0 initial value of the state vector at t0
+     * @param dY0dP initial value of the state vector derivative with respect to the
+     * parameters 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
+     * @param dYdY0 placeholder where to put the state vector derivative with respect
+     * to the initial state (dy[i]/dy0[j] is in element array dYdY0[i][j]) at each successful
+     *  step (and hence at the end of integration)
+     * @param dYdP placeholder where to put the state vector derivative with respect
+     * to the parameters (dy[i]/dp[j] is in element array dYdP[i][j]) at each successful
+     *  step (and hence at the end of integration)
+     * @return stop time, will be the same as target time if integration reached its
+     * target, but may be different if some event handler stops it at some point.
+     * @throws IntegratorException if the integrator cannot perform integration
+     * @throws DerivativeException this exception is propagated to the caller if
+     * the underlying user function triggers one
+     */
+    public double integrate(final double t0, final double[] y0, final double[][] dY0dP,
+                            final double t, final double[] y,
+                            final double[][] dYdY0, final double[][] dYdP)
+        throws DerivativeException, IntegratorException {
+
+        final int n = ode.getDimension();
+        final int k = ode.getParametersDimension();
+        checkDimension(n, y0);
+        checkDimension(n, y);
+        checkDimension(n, dYdY0);
+        checkDimension(n, dYdY0[0]);
+        if (k != 0) {
+            checkDimension(n, dY0dP);
+            checkDimension(k, dY0dP[0]);
+            checkDimension(n, dYdP);
+            checkDimension(k, dYdP[0]);
+        }
+
+        // set up initial state, including partial derivatives
+        // the compound state z contains the raw state y and its derivatives
+        // with respect to initial state y0 and to parameters p
+        //    y[i]         is stored in z[i]
+        //    dy[i]/dy0[j] is stored in z[n + i * n + j]
+        //    dy[i]/dp[j]  is stored in z[n * (n + 1) + i * k + j]
+        final double[] z = new double[n * (1 + n + k)];
+        System.arraycopy(y0, 0, z, 0, n);
+        for (int i = 0; i < n; ++i) {
+
+            // set diagonal element of dy/dy0 to 1.0 at t = t0
+            z[i * (1 + n) + n] = 1.0;
+
+            // set initial derivatives with respect to parameters
+            System.arraycopy(dY0dP[i], 0, z, n * (n + 1) + i * k, k);
+
+        }
+
+        // integrate the compound state variational equations
+        evaluations = 0;
+        final double stopTime = integrator.integrate(new MappingWrapper(), t0, z, t, z);
+
+        // dispatch the final compound state into the state and partial derivatives arrays
+        dispatchCompoundState(z, y, dYdY0, dYdP);
+
+        return stopTime;
+
+    }
+
+    /** Dispatch a compound state array into state and jacobians arrays.
+     * @param z compound state
+     * @param y raw state array to fill
+     * @param dydy0 jacobian array to fill
+     * @param dydp jacobian array to fill
+     */
+    private static void dispatchCompoundState(final double[] z, final double[] y,
+                                              final double[][] dydy0, final double[][] dydp) {
+
+        final int n = y.length;
+        final int k = dydp[0].length;
+
+        // state
+        System.arraycopy(z, 0, y, 0, n);
+
+        // jacobian with respect to initial state
+        for (int i = 0; i < n; ++i) {
+            System.arraycopy(z, n * (i + 1), dydy0[i], 0, n);
+        }
+
+        // jacobian with respect to parameters
+        for (int i = 0; i < n; ++i) {
+            System.arraycopy(z, n * (n + 1) + i * k, dydp[i], 0, k);
+        }
+
+    }
+
+    /** 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 org.apache.commons.math.ode.FirstOrderDifferentialEquations
+     * differential equations} problem) if the value of the current step that
+     * is attempted is needed.</p>
+     * <p>The result is undefined if the method is called outside of
+     * calls to <code>integrate</code>.</p>
+     * @return current value of the step start time t<sub>i</sub>
+     */
+    public double getCurrentStepStart() {
+        return integrator.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 org.apache.commons.math.ode.FirstOrderDifferentialEquations
+     * differential equations} problem) if the signed value of the current stepsize
+     * that is tried is needed.</p>
+     * <p>The result is undefined if the method is called outside of
+     * calls to <code>integrate</code>.</p>
+     * @return current signed value of the stepsize
+     */
+    public double getCurrentSignedStepsize() {
+        return integrator.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.</p>
+     * @param maxEvaluations maximal number of function evaluations (negative
+     * values are silently converted to maximal integer value, thus representing
+     * almost unlimited evaluations)
+     */
+    public void setMaxEvaluations(int maxEvaluations) {
+        this.maxEvaluations = (maxEvaluations < 0) ? Integer.MAX_VALUE : maxEvaluations;
+    }
+
+    /** Get the maximal number of functions evaluations.
+     * @return maximal number of functions evaluations
+     */
+    public int getMaxEvaluations() {
+        return maxEvaluations;
+    }
+
+    /** 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.
+     * </p>
+     * @return number of evaluations of the differential equations function
+     */
+    public int getEvaluations() {
+        return evaluations;
+    }
+
+    /** Check array dimensions.
+     * @param expected expected dimension
+     * @param array (may be null if expected is 0)
+     * @throws IllegalArgumentException if the array dimension does not match the expected one
+     */
+    private void checkDimension(final int expected, final Object array)
+        throws IllegalArgumentException {
+        int arrayDimension = (array == null) ? 0 : Array.getLength(array);
+        if (arrayDimension != expected) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, arrayDimension, expected);
+        }
+    }
+
+    /** Wrapper class used to map state and jacobians into compound state. */
+    private class MappingWrapper implements  ExtendedFirstOrderDifferentialEquations {
+
+        /** Current state. */
+        private final double[]   y;
+
+        /** Time derivative of the current state. */
+        private final double[]   yDot;
+
+        /** Derivatives of yDot with respect to state. */
+        private final double[][] dFdY;
+
+        /** Derivatives of yDot with respect to parameters. */
+        private final double[][] dFdP;
+
+        /** Simple constructor.
+         */
+        public MappingWrapper() {
+
+            final int n = ode.getDimension();
+            final int k = ode.getParametersDimension();
+            y    = new double[n];
+            yDot = new double[n];
+            dFdY = new double[n][n];
+            dFdP = new double[n][k];
+
+        }
+
+        /** {@inheritDoc} */
+        public int getDimension() {
+            final int n = y.length;
+            final int k = dFdP[0].length;
+            return n * (1 + n + k);
+        }
+
+        /** {@inheritDoc} */
+        public int getMainSetDimension() {
+            return ode.getDimension();
+        }
+
+        /** {@inheritDoc} */
+        public void computeDerivatives(final double t, final double[] z, final double[] zDot)
+            throws DerivativeException {
+
+            final int n = y.length;
+            final int k = dFdP[0].length;
+
+            // compute raw ODE and its jacobians: dy/dt, d[dy/dt]/dy0 and d[dy/dt]/dp
+            System.arraycopy(z,    0, y,    0, n);
+            if (++evaluations > maxEvaluations) {
+                throw new DerivativeException(new MaxEvaluationsExceededException(maxEvaluations));
+            }
+            ode.computeDerivatives(t, y, yDot);
+            ode.computeJacobians(t, y, yDot, dFdY, dFdP);
+
+            // state part of the compound equations
+            System.arraycopy(yDot, 0, zDot, 0, n);
+
+            // variational equations: from d[dy/dt]/dy0 to d[dy/dy0]/dt
+            for (int i = 0; i < n; ++i) {
+                final double[] dFdYi = dFdY[i];
+                for (int j = 0; j < n; ++j) {
+                    double s = 0;
+                    final int startIndex = n + j;
+                    int zIndex = startIndex;
+                    for (int l = 0; l < n; ++l) {
+                        s += dFdYi[l] * z[zIndex];
+                        zIndex += n;
+                    }
+                    zDot[startIndex + i * n] = s;
+                }
+            }
+
+            // variational equations: from d[dy/dt]/dy0 and d[dy/dt]/dp to d[dy/dp]/dt
+            for (int i = 0; i < n; ++i) {
+                final double[] dFdYi = dFdY[i];
+                final double[] dFdPi = dFdP[i];
+                for (int j = 0; j < k; ++j) {
+                    double s = dFdPi[j];
+                    final int startIndex = n * (n + 1) + j;
+                    int zIndex = startIndex;
+                    for (int l = 0; l < n; ++l) {
+                        s += dFdYi[l] * z[zIndex];
+                        zIndex += k;
+                    }
+                    zDot[startIndex + i * k] = s;
+                }
+            }
+
+        }
+
+    }
+
+    /** Wrapper class to compute jacobians by finite differences for ODE which do not compute them themselves. */
+    private class FiniteDifferencesWrapper implements ODEWithJacobians {
+
+        /** Raw ODE without jacobians computation. */
+        private final ParameterizedODE ode;
+
+        /** Parameters array (may be null if parameters dimension from original problem is zero) */
+        private final double[] p;
+
+        /** Step sizes to use for computing the jacobian df/dy. */
+        private final double[] hY;
+
+        /** Step sizes to use for computing the jacobian df/dp. */
+        private final double[] hP;
+
+        /** Temporary array for state derivatives used to compute jacobians. */
+        private final double[] tmpDot;
+
+        /** Simple constructor.
+         * @param ode original ODE problem, without jacobians computations
+         * @param p parameters array (may be null if parameters dimension from original problem is zero)
+         * @param hY step sizes to use for computing the jacobian df/dy
+         * @param hP step sizes to use for computing the jacobian df/dp
+         */
+        public FiniteDifferencesWrapper(final ParameterizedODE ode,
+                                        final double[] p, final double[] hY, final double[] hP) {
+            this.ode = ode;
+            this.p  = p.clone();
+            this.hY = hY.clone();
+            this.hP = hP.clone();
+            tmpDot = new double[ode.getDimension()];
+        }
+
+        /** {@inheritDoc} */
+        public int getDimension() {
+            return ode.getDimension();
+        }
+
+        /** {@inheritDoc} */
+        public void computeDerivatives(double t, double[] y, double[] yDot) throws DerivativeException {
+            // this call to computeDerivatives has already been counted,
+            // we must not increment the counter again
+            ode.computeDerivatives(t, y, yDot);
+        }
+
+        /** {@inheritDoc} */
+        public int getParametersDimension() {
+            return ode.getParametersDimension();
+        }
+
+        /** {@inheritDoc} */
+        public void computeJacobians(double t, double[] y, double[] yDot,
+                                     double[][] dFdY, double[][] dFdP)
+            throws DerivativeException {
+
+            final int n = hY.length;
+            final int k = hP.length;
+
+            evaluations += n + k;
+            if (evaluations > maxEvaluations) {
+                throw new DerivativeException(new MaxEvaluationsExceededException(maxEvaluations));
+            }
+
+            // compute df/dy where f is the ODE and y is the state array
+            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;
+            }
+
+            // compute df/dp where f is the ODE and p is the parameters array
+            for (int j = 0; j < k; ++j) {
+                ode.setParameter(j, p[j] +  hP[j]);
+                ode.computeDerivatives(t, y, tmpDot);
+                for (int i = 0; i < n; ++i) {
+                    dFdP[i][j] = (tmpDot[i] - yDot[i]) / hP[j];
+                }
+                ode.setParameter(j, p[j]);
+            }
+
+        }
+
+    }
+
+    /** Wrapper for step handlers. */
+    private static class StepHandlerWrapper implements StepHandler {
+
+        /** Underlying step handler with jacobians. */
+        private final StepHandlerWithJacobians handler;
+
+        /** Dimension of the original ODE. */
+        private final int n;
+
+        /** Number of parameters. */
+        private final int k;
+
+        /** Simple constructor.
+         * @param handler underlying step handler with jacobians
+         * @param n dimension of the original ODE
+         * @param k number of parameters
+         */
+        public StepHandlerWrapper(final StepHandlerWithJacobians handler,
+                                  final int n, final int k) {
+            this.handler = handler;
+            this.n       = n;
+            this.k       = k;
+        }
+
+        /** Get the underlying step handler with jacobians.
+         * @return underlying step handler with jacobians
+         */
+        public StepHandlerWithJacobians getHandler() {
+            return handler;
+        }
+
+        /** {@inheritDoc} */
+        public void handleStep(StepInterpolator interpolator, boolean isLast)
+            throws DerivativeException {
+            handler.handleStep(new StepInterpolatorWrapper(interpolator, n, k), isLast);
+        }
+
+        /** {@inheritDoc} */
+        public boolean requiresDenseOutput() {
+            return handler.requiresDenseOutput();
+        }
+
+        /** {@inheritDoc} */
+        public void reset() {
+            handler.reset();
+        }
+
+    }
+
+    /** Wrapper for step interpolators. */
+    private static class StepInterpolatorWrapper
+        implements StepInterpolatorWithJacobians {
+
+        /** Wrapped interpolator. */
+        private StepInterpolator interpolator;
+
+        /** State array. */
+        private double[] y;
+
+        /** Jacobian with respect to initial state dy/dy0. */
+        private double[][] dydy0;
+
+        /** Jacobian with respect to parameters dy/dp. */
+        private double[][] dydp;
+
+        /** Time derivative of the state array. */
+        private double[] yDot;
+
+        /** Time derivative of the sacobian with respect to initial state dy/dy0. */
+        private double[][] dydy0Dot;
+
+        /** Time derivative of the jacobian with respect to parameters dy/dp. */
+        private double[][] dydpDot;
+
+        /** Simple constructor.
+         * <p>This constructor is used only for externalization. It does nothing.</p>
+         */
+        @SuppressWarnings("unused")
+        public StepInterpolatorWrapper() {
+        }
+
+        /** Simple constructor.
+         * @param interpolator wrapped interpolator
+         * @param n dimension of the original ODE
+         * @param k number of parameters
+         */
+        public StepInterpolatorWrapper(final StepInterpolator interpolator,
+                                       final int n, final int k) {
+            this.interpolator = interpolator;
+            y        = new double[n];
+            dydy0    = new double[n][n];
+            dydp     = new double[n][k];
+            yDot     = new double[n];
+            dydy0Dot = new double[n][n];
+            dydpDot  = new double[n][k];
+        }
+
+        /** {@inheritDoc} */
+        public void setInterpolatedTime(double time) {
+            interpolator.setInterpolatedTime(time);
+        }
+
+        /** {@inheritDoc} */
+        public boolean isForward() {
+            return interpolator.isForward();
+        }
+
+        /** {@inheritDoc} */
+        public double getPreviousTime() {
+            return interpolator.getPreviousTime();
+        }
+
+        /** {@inheritDoc} */
+        public double getInterpolatedTime() {
+            return interpolator.getInterpolatedTime();
+        }
+
+        /** {@inheritDoc} */
+        public double[] getInterpolatedY() throws DerivativeException {
+            double[] extendedState = interpolator.getInterpolatedState();
+            System.arraycopy(extendedState, 0, y, 0, y.length);
+            return y;
+        }
+
+        /** {@inheritDoc} */
+        public double[][] getInterpolatedDyDy0() throws DerivativeException {
+            double[] extendedState = interpolator.getInterpolatedState();
+            final int n = y.length;
+            int start = n;
+            for (int i = 0; i < n; ++i) {
+                System.arraycopy(extendedState, start, dydy0[i], 0, n);
+                start += n;
+            }
+            return dydy0;
+        }
+
+        /** {@inheritDoc} */
+        public double[][] getInterpolatedDyDp() throws DerivativeException {
+            double[] extendedState = interpolator.getInterpolatedState();
+            final int n = y.length;
+            final int k = dydp[0].length;
+            int start = n * (n + 1);
+            for (int i = 0; i < n; ++i) {
+                System.arraycopy(extendedState, start, dydp[i], 0, k);
+                start += k;
+            }
+            return dydp;
+        }
+
+        /** {@inheritDoc} */
+        public double[] getInterpolatedYDot() throws DerivativeException {
+            double[] extendedDerivatives = interpolator.getInterpolatedDerivatives();
+            System.arraycopy(extendedDerivatives, 0, yDot, 0, yDot.length);
+            return yDot;
+        }
+
+        /** {@inheritDoc} */
+        public double[][] getInterpolatedDyDy0Dot() throws DerivativeException {
+            double[] extendedDerivatives = interpolator.getInterpolatedDerivatives();
+            final int n = y.length;
+            int start = n;
+            for (int i = 0; i < n; ++i) {
+                System.arraycopy(extendedDerivatives, start, dydy0Dot[i], 0, n);
+                start += n;
+            }
+            return dydy0Dot;
+        }
+
+        /** {@inheritDoc} */
+        public double[][] getInterpolatedDyDpDot() throws DerivativeException {
+            double[] extendedDerivatives = interpolator.getInterpolatedDerivatives();
+            final int n = y.length;
+            final int k = dydpDot[0].length;
+            int start = n * (n + 1);
+            for (int i = 0; i < n; ++i) {
+                System.arraycopy(extendedDerivatives, start, dydpDot[i], 0, k);
+                start += k;
+            }
+            return dydpDot;
+        }
+
+        /** {@inheritDoc} */
+        public double getCurrentTime() {
+            return interpolator.getCurrentTime();
+        }
+
+        /** {@inheritDoc} */
+        public StepInterpolatorWithJacobians copy() throws DerivativeException {
+            final int n = y.length;
+            final int k = dydp[0].length;
+            StepInterpolatorWrapper copied =
+                new StepInterpolatorWrapper(interpolator.copy(), n, k);
+            copyArray(y,        copied.y);
+            copyArray(dydy0,    copied.dydy0);
+            copyArray(dydp,     copied.dydp);
+            copyArray(yDot,     copied.yDot);
+            copyArray(dydy0Dot, copied.dydy0Dot);
+            copyArray(dydpDot,  copied.dydpDot);
+            return copied;
+        }
+
+        /** {@inheritDoc} */
+        public void writeExternal(ObjectOutput out) throws IOException {
+            out.writeObject(interpolator);
+            out.writeInt(y.length);
+            out.writeInt(dydp[0].length);
+            writeArray(out, y);
+            writeArray(out, dydy0);
+            writeArray(out, dydp);
+            writeArray(out, yDot);
+            writeArray(out, dydy0Dot);
+            writeArray(out, dydpDot);
+        }
+
+        /** {@inheritDoc} */
+        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+            interpolator = (StepInterpolator) in.readObject();
+            final int n = in.readInt();
+            final int k = in.readInt();
+            y        = new double[n];
+            dydy0    = new double[n][n];
+            dydp     = new double[n][k];
+            yDot     = new double[n];
+            dydy0Dot = new double[n][n];
+            dydpDot  = new double[n][k];
+            readArray(in, y);
+            readArray(in, dydy0);
+            readArray(in, dydp);
+            readArray(in, yDot);
+            readArray(in, dydy0Dot);
+            readArray(in, dydpDot);
+        }
+
+        /** Copy an array.
+         * @param src source array
+         * @param dest destination array
+         */
+        private static void copyArray(final double[] src, final double[] dest) {
+            System.arraycopy(src, 0, dest, 0, src.length);
+        }
+
+        /** Copy an array.
+         * @param src source array
+         * @param dest destination array
+         */
+        private static void copyArray(final double[][] src, final double[][] dest) {
+            for (int i = 0; i < src.length; ++i) {
+                copyArray(src[i], dest[i]);
+            }
+        }
+
+        /** Write an array.
+         * @param out output stream
+         * @param array array to write
+         * @exception IOException if array cannot be read
+         */
+        private static void writeArray(final ObjectOutput out, final double[] array)
+            throws IOException {
+            for (int i = 0; i < array.length; ++i) {
+                out.writeDouble(array[i]);
+            }
+        }
+
+        /** Write an array.
+         * @param out output stream
+         * @param array array to write
+         * @exception IOException if array cannot be read
+         */
+        private static void writeArray(final ObjectOutput out, final double[][] array)
+            throws IOException {
+            for (int i = 0; i < array.length; ++i) {
+                writeArray(out, array[i]);
+            }
+        }
+
+        /** Read an array.
+         * @param in input stream
+         * @param array array to read
+         * @exception IOException if array cannot be read
+         */
+        private static void readArray(final ObjectInput in, final double[] array)
+            throws IOException {
+            for (int i = 0; i < array.length; ++i) {
+                array[i] = in.readDouble();
+            }
+        }
+
+        /** Read an array.
+         * @param in input stream
+         * @param array array to read
+         * @exception IOException if array cannot be read
+         */
+        private static void readArray(final ObjectInput in, final double[][] array)
+            throws IOException {
+            for (int i = 0; i < array.length; ++i) {
+                readArray(in, array[i]);
+            }
+        }
+
+    }
+
+    /** Wrapper for event handlers. */
+    private static class EventHandlerWrapper implements EventHandler {
+
+        /** Underlying event handler with jacobians. */
+        private final EventHandlerWithJacobians handler;
+
+        /** State array. */
+        private double[] y;
+
+        /** Jacobian with respect to initial state dy/dy0. */
+        private double[][] dydy0;
+
+        /** Jacobian with respect to parameters dy/dp. */
+        private double[][] dydp;
+
+        /** Simple constructor.
+         * @param handler underlying event handler with jacobians
+         * @param n dimension of the original ODE
+         * @param k number of parameters
+         */
+        public EventHandlerWrapper(final EventHandlerWithJacobians handler,
+                                   final int n, final int k) {
+            this.handler = handler;
+            y        = new double[n];
+            dydy0    = new double[n][n];
+            dydp     = new double[n][k];
+        }
+
+        /** Get the underlying event handler with jacobians.
+         * @return underlying event handler with jacobians
+         */
+        public EventHandlerWithJacobians getHandler() {
+            return handler;
+        }
+
+        /** {@inheritDoc} */
+        public int eventOccurred(double t, double[] z, boolean increasing)
+            throws EventException {
+            dispatchCompoundState(z, y, dydy0, dydp);
+            return handler.eventOccurred(t, y, dydy0, dydp, increasing);
+        }
+
+        /** {@inheritDoc} */
+        public double g(double t, double[] z)
+            throws EventException {
+            dispatchCompoundState(z, y, dydy0, dydp);
+            return handler.g(t, y, dydy0, dydp);
+        }
+
+        /** {@inheritDoc} */
+        public void resetState(double t, double[] z)
+            throws EventException {
+            dispatchCompoundState(z, y, dydy0, dydp);
+            handler.resetState(t, y, dydy0, dydp);
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/ode/jacobians/ODEWithJacobians.java b/src/main/java/org/apache/commons/math/ode/jacobians/ODEWithJacobians.java
new file mode 100644
index 0000000..40a7e77
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/jacobians/ODEWithJacobians.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.math.ode.jacobians;
+
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.ode.FirstOrderDifferentialEquations;
+
+
+/** This interface represents {@link ParameterizedODE
+ * first order differential equations} with parameters and partial derivatives.
+ *
+ * @see FirstOrderIntegratorWithJacobians
+ *
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @since 2.1
+ * @deprecated as of 2.2 the complete package is deprecated, it will be replaced
+ * in 3.0 by a completely rewritten implementation
+ */
+@Deprecated
+public interface ODEWithJacobians extends FirstOrderDifferentialEquations {
+
+    /** Get the number of parameters.
+     * @return number of parameters
+     */
+    int getParametersDimension();
+
+    /** Compute the partial derivatives of ODE with respect to state.
+     * @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 time derivative of the state vector
+     * @param dFdY placeholder array where to put the jacobian of the ODE with respect to the state vector
+     * @param dFdP placeholder array where to put the jacobian of the ODE with respect to the parameters
+     * @throws DerivativeException this exception is propagated to the caller if the
+     * underlying user function triggers one
+     */
+    void computeJacobians(double t, double[] y, double[] yDot, double[][] dFdY, double[][] dFdP)
+        throws DerivativeException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/ode/jacobians/ParameterizedODE.java b/src/main/java/org/apache/commons/math/ode/jacobians/ParameterizedODE.java
new file mode 100644
index 0000000..89caad7
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/jacobians/ParameterizedODE.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.math.ode.jacobians;
+
+import org.apache.commons.math.ode.FirstOrderDifferentialEquations;
+
+
+/** This interface represents {@link FirstOrderDifferentialEquations
+ * first order differential equations} with parameters.
+ *
+ * @see FirstOrderIntegratorWithJacobians
+ *
+ * @version $Revision: 1037341 $ $Date: 2010-11-20 22:58:35 +0100 (sam. 20 nov. 2010) $
+ * @since 2.1
+ * @deprecated as of 2.2 the complete package is deprecated, it will be replaced
+ * in 3.0 by a completely rewritten implementation
+ */
+@Deprecated
+public interface ParameterizedODE extends FirstOrderDifferentialEquations {
+
+    /** Get the number of parameters.
+     * @return number of parameters
+     */
+    int getParametersDimension();
+
+    /** Set a parameter.
+     * @param i index of the parameters (must be between 0
+     * and {@link #getParametersDimension() getParametersDimension() - 1})
+     * @param value value for the parameter
+     */
+    void setParameter(int i, double value);
+
+}
diff --git a/src/main/java/org/apache/commons/math/ode/jacobians/StepHandlerWithJacobians.java b/src/main/java/org/apache/commons/math/ode/jacobians/StepHandlerWithJacobians.java
new file mode 100644
index 0000000..8cbb747
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/jacobians/StepHandlerWithJacobians.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.math.ode.jacobians;
+
+import org.apache.commons.math.ode.DerivativeException;
+
+/**
+ * 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>
+ *
+ * <p>Note that is is possible to register a {@link
+ * org.apache.commons.math.ode.sampling.StepHandler classical step handler}
+ * in the low level integrator used to build a {@link FirstOrderIntegratorWithJacobians}
+ * rather than implementing this class. The step handlers registered at low level
+ * will see the big compound state whether the step handlers defined by this interface
+ * see the original state, and its jacobians in separate arrays.</p>
+ *
+ * <p>The compound state is guaranteed to contain the original state in the first
+ * elements, followed by the jacobian with respect to initial state (in row order),
+ * followed by the jacobian with respect to parameters (in row order). If for example
+ * the original state dimension is 6 and there are 3 parameters, the compound state will
+ * be a 60 elements array. The first 6 elements will be the original state, the next 36
+ * elements will be the jacobian with respect to initial state, and the remaining 18 elements
+ * will be the jacobian with respect to parameters.</p>
+ *
+ * <p>Dealing with low level step handlers is cumbersome if one really needs the jacobians
+ * in these methods, but it also prevents many data being copied back and forth between
+ * state and jacobians on one side and compound state on the other side. So for performance
+ * reasons, it is recommended to use this interface <em>only</em> if jacobians are really
+ * needed and to use lower level handlers if only state is needed.</p>
+ *
+ * @see FirstOrderIntegratorWithJacobians
+ * @see StepInterpolatorWithJacobians
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @since 2.1
+ * @deprecated as of 2.2 the complete package is deprecated, it will be replaced
+ * in 3.0 by a completely rewritten implementation
+ */
+@Deprecated
+public interface StepHandlerWithJacobians {
+
+  /** Determines whether this handler needs dense output.
+   * <p>This method allows the integrator to avoid performing extra
+   * computation if the handler does not need dense output.</p>
+   * @return true if the handler needs dense output
+   */
+  boolean requiresDenseOutput();
+
+  /** Reset the step handler.
+   * Initialize the internal data as required before the first step is
+   * handled.
+   */
+  void reset();
+
+  /**
+   * 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.math.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
+   * @throws DerivativeException this exception is propagated to the
+   * caller if the underlying user function triggers one
+   */
+  void handleStep(StepInterpolatorWithJacobians interpolator, boolean isLast) throws DerivativeException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/ode/jacobians/StepInterpolatorWithJacobians.java b/src/main/java/org/apache/commons/math/ode/jacobians/StepInterpolatorWithJacobians.java
new file mode 100644
index 0000000..bff9d17
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/jacobians/StepInterpolatorWithJacobians.java
@@ -0,0 +1,188 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.ode.jacobians;
+
+import java.io.Externalizable;
+
+import org.apache.commons.math.ode.DerivativeException;
+
+/** 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 FirstOrderIntegratorWithJacobians
+ * @see StepHandlerWithJacobians
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @since 2.1
+ * @deprecated as of 2.2 the complete package is deprecated, it will be replaced
+ * in 3.0 by a completely rewritten implementation
+ */
+@Deprecated
+public interface StepInterpolatorWithJacobians 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. If a
+   * specific state must be preserved, a copy of the instance must be
+   * created using {@link #copy()}.</p>
+   * @param time time of the interpolated point
+   */
+  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.</p>
+   * @return state vector at time {@link #getInterpolatedTime}
+   * @see #getInterpolatedYDot()
+   * @throws DerivativeException if this call induces an automatic
+   * step finalization that throws one
+   */
+  double[] getInterpolatedY() throws DerivativeException;
+
+  /**
+   * Get the partial derivatives of the state vector with respect to
+   * the initial state 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.</p>
+   * @return partial derivatives of the state vector with respect to
+   * the initial state at time {@link #getInterpolatedTime}
+   * @see #getInterpolatedY()
+   * @throws DerivativeException if this call induces an automatic
+   * step finalization that throws one
+   */
+  double[][] getInterpolatedDyDy0() throws DerivativeException;
+
+  /**
+   * Get the partial derivatives of the state vector with respect to
+   * the ODE parameters 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.</p>
+   * @return partial derivatives of the state vector with respect to
+   * the ODE parameters at time {@link #getInterpolatedTime}
+   * @see #getInterpolatedY()
+   * @throws DerivativeException if this call induces an automatic
+   * step finalization that throws one
+   */
+  double[][] getInterpolatedDyDp() throws DerivativeException;
+
+  /**
+   * Get the time 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.</p>
+   * @return derivatives of the state vector at time {@link #getInterpolatedTime}
+   * @see #getInterpolatedY()
+   * @throws DerivativeException if this call induces an automatic
+   * step finalization that throws one
+   */
+  double[] getInterpolatedYDot() throws DerivativeException;
+
+  /**
+   * Get the time derivatives of the jacobian of the state vector
+   * with respect to the initial state 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.</p>
+   * @return time derivatives of the jacobian of the state vector
+   * with respect to the initial state at time {@link #getInterpolatedTime}
+   * @see #getInterpolatedY()
+   * @throws DerivativeException if this call induces an automatic
+   * step finalization that throws one
+   */
+  double[][] getInterpolatedDyDy0Dot() throws DerivativeException;
+
+  /**
+   * Get the time derivatives of the jacobian of the state vector
+   * with respect to the ODE parameters 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.</p>
+   * @return time derivatives of the jacobian of the state vector
+   * with respect to the ODE parameters at time {@link #getInterpolatedTime}
+   * @see #getInterpolatedY()
+   * @throws DerivativeException if this call induces an automatic
+   * step finalization that throws one
+   */
+  double[][] getInterpolatedDyDpDot() throws DerivativeException;
+
+  /** 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.
+   * @throws DerivativeException if this call induces an automatic
+   * step finalization that throws one
+   * @see #setInterpolatedTime(double)
+   */
+   StepInterpolatorWithJacobians copy() throws DerivativeException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/ode/jacobians/package.html b/src/main/java/org/apache/commons/math/ode/jacobians/package.html
new file mode 100644
index 0000000..29d6f8f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/jacobians/package.html
@@ -0,0 +1,28 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 1037341 $ -->
+<body>
+<p>
+This package was intended to solve Ordinary Differential Equations problems
+and also compute derivatives of the solution. It was introduced in 2.1 but is
+difficult to use and clumsy. It is completely deprecated in 2.2 and will be removed
+in 3.0, to be replaced by a completely new implementation, much more tightly
+bound to the top level ode package.
+</p>
+</body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/ode/nonstiff/AdamsBashforthIntegrator.java b/src/main/java/org/apache/commons/math/ode/nonstiff/AdamsBashforthIntegrator.java
new file mode 100644
index 0000000..6ba7733
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/nonstiff/AdamsBashforthIntegrator.java
@@ -0,0 +1,317 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.ode.nonstiff;
+
+import org.apache.commons.math.linear.Array2DRowRealMatrix;
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.ode.FirstOrderDifferentialEquations;
+import org.apache.commons.math.ode.IntegratorException;
+import org.apache.commons.math.ode.sampling.NordsieckStepInterpolator;
+import org.apache.commons.math.ode.sampling.StepHandler;
+import org.apache.commons.math.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(k)<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</sub> j (-i)<sup>j-1</sup> s<sub>j</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 (-i)<sup>j-1</sup> terms:
+ * <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>
+ *
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @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 (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 IllegalArgumentException if order is 1 or less
+     */
+    public AdamsBashforthIntegrator(final int nSteps,
+                                    final double minStep, final double maxStep,
+                                    final double scalAbsoluteTolerance,
+                                    final double scalRelativeTolerance)
+        throws IllegalArgumentException {
+        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 (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
+     * @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);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double integrate(final FirstOrderDifferentialEquations equations,
+                            final double t0, final double[] y0,
+                            final double t, final double[] y)
+        throws DerivativeException, IntegratorException {
+
+        final int n = y0.length;
+        sanityChecks(equations, t0, y0, t, y);
+        setEquations(equations);
+        resetEvaluations();
+        final boolean forward = t > t0;
+
+        // initialize working arrays
+        if (y != y0) {
+            System.arraycopy(y0, 0, y, 0, n);
+        }
+        final double[] yDot = new double[n];
+
+        // set up an interpolator sharing the integrator arrays
+        final NordsieckStepInterpolator interpolator = new NordsieckStepInterpolator();
+        interpolator.reinitialize(y, forward);
+
+        // set up integration control objects
+        for (StepHandler handler : stepHandlers) {
+            handler.reset();
+        }
+        setStateInitialized(false);
+
+        // compute the initial Nordsieck vector using the configured starter integrator
+        start(t0, y, t);
+        interpolator.reinitialize(stepStart, stepSize, scaled, nordsieck);
+        interpolator.storeTime(stepStart);
+        final int lastRow = nordsieck.getRowDimension() - 1;
+
+        // reuse the step that was chosen by the starter integrator
+        double hNew = stepSize;
+        interpolator.rescale(hNew);
+
+        // main integration loop
+        isLastStep = false;
+        do {
+
+            double error = 10;
+            while (error >= 1.0) {
+
+                stepSize = hNew;
+
+                // evaluate error using the last term of the Taylor expansion
+                error = 0;
+                for (int i = 0; i < mainSetDimension; ++i) {
+                    final double yScale = FastMath.abs(y[i]);
+                    final double tol = (vecAbsoluteTolerance == null) ?
+                                       (scalAbsoluteTolerance + scalRelativeTolerance * yScale) :
+                                       (vecAbsoluteTolerance[i] + vecRelativeTolerance[i] * yScale);
+                    final double ratio  = nordsieck.getEntry(lastRow, i) / tol;
+                    error += ratio * ratio;
+                }
+                error = FastMath.sqrt(error / mainSetDimension);
+
+                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);
+
+                }
+            }
+
+            // predict a first estimate of the state at step end
+            final double stepEnd = stepStart + stepSize;
+            interpolator.shift();
+            interpolator.setInterpolatedTime(stepEnd);
+            System.arraycopy(interpolator.getInterpolatedState(), 0, y, 0, y0.length);
+
+            // evaluate the derivative
+            computeDerivatives(stepEnd, y, yDot);
+
+            // update Nordsieck vector
+            final double[] predictedScaled = new double[y0.length];
+            for (int j = 0; j < y0.length; ++j) {
+                predictedScaled[j] = stepSize * yDot[j];
+            }
+            final Array2DRowRealMatrix nordsieckTmp = updateHighOrderDerivativesPhase1(nordsieck);
+            updateHighOrderDerivativesPhase2(scaled, predictedScaled, nordsieckTmp);
+            interpolator.reinitialize(stepEnd, stepSize, predictedScaled, nordsieckTmp);
+
+            // discrete events handling
+            interpolator.storeTime(stepEnd);
+            stepStart = acceptStep(interpolator, y, yDot, t);
+            scaled    = predictedScaled;
+            nordsieck = nordsieckTmp;
+            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);
+
+        final double stopTime = stepStart;
+        resetInternalState();
+        return stopTime;
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/ode/nonstiff/AdamsIntegrator.java b/src/main/java/org/apache/commons/math/ode/nonstiff/AdamsIntegrator.java
new file mode 100644
index 0000000..0b114f0
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/nonstiff/AdamsIntegrator.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.math.ode.nonstiff;
+
+import org.apache.commons.math.linear.Array2DRowRealMatrix;
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.ode.FirstOrderDifferentialEquations;
+import org.apache.commons.math.ode.IntegratorException;
+import org.apache.commons.math.ode.MultistepIntegrator;
+
+
+/** Base class for {@link AdamsBashforthIntegrator Adams-Bashforth} and
+ * {@link AdamsMoultonIntegrator Adams-Moulton} integrators.
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @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 prameters.
+     * @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 (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 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 scalAbsoluteTolerance,
+                           final double scalRelativeTolerance)
+        throws IllegalArgumentException {
+        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 (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
+     * @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 double integrate(final FirstOrderDifferentialEquations equations,
+                                     final double t0, final double[] y0,
+                                     final double t, final double[] y)
+        throws DerivativeException, IntegratorException;
+
+    /** {@inheritDoc} */
+    @Override
+    protected Array2DRowRealMatrix initializeHighOrderDerivatives(final double[] first,
+                                                        final double[][] multistep) {
+        return transformer.initializeHighOrderDerivatives(first, multistep);
+    }
+
+    /** 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/math/ode/nonstiff/AdamsMoultonIntegrator.java b/src/main/java/org/apache/commons/math/ode/nonstiff/AdamsMoultonIntegrator.java
new file mode 100644
index 0000000..77a4418
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/nonstiff/AdamsMoultonIntegrator.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.math.ode.nonstiff;
+
+import java.util.Arrays;
+
+import org.apache.commons.math.linear.Array2DRowRealMatrix;
+import org.apache.commons.math.linear.RealMatrixPreservingVisitor;
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.ode.FirstOrderDifferentialEquations;
+import org.apache.commons.math.ode.IntegratorException;
+import org.apache.commons.math.ode.sampling.NordsieckStepInterpolator;
+import org.apache.commons.math.ode.sampling.StepHandler;
+import org.apache.commons.math.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(k)<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</sub> j (-i)<sup>j-1</sup> s<sub>j</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 (-i)<sup>j-1</sup> terms:
+ * <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>
+ *
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @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 (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 IllegalArgumentException if order is 1 or less
+     */
+    public AdamsMoultonIntegrator(final int nSteps,
+                                  final double minStep, final double maxStep,
+                                  final double scalAbsoluteTolerance,
+                                  final double scalRelativeTolerance)
+        throws IllegalArgumentException {
+        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 (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
+     * @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 double integrate(final FirstOrderDifferentialEquations equations,
+                            final double t0, final double[] y0,
+                            final double t, final double[] y)
+        throws DerivativeException, IntegratorException {
+
+        final int n = y0.length;
+        sanityChecks(equations, t0, y0, t, y);
+        setEquations(equations);
+        resetEvaluations();
+        final boolean forward = t > t0;
+
+        // initialize working arrays
+        if (y != y0) {
+            System.arraycopy(y0, 0, y, 0, n);
+        }
+        final double[] yDot = new double[y0.length];
+        final double[] yTmp = new double[y0.length];
+        final double[] predictedScaled = new double[y0.length];
+        Array2DRowRealMatrix nordsieckTmp = null;
+
+        // set up two interpolators sharing the integrator arrays
+        final NordsieckStepInterpolator interpolator = new NordsieckStepInterpolator();
+        interpolator.reinitialize(y, forward);
+
+        // set up integration control objects
+        for (StepHandler handler : stepHandlers) {
+            handler.reset();
+        }
+        setStateInitialized(false);
+
+        // compute the initial Nordsieck vector using the configured starter integrator
+        start(t0, 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);
+                System.arraycopy(interpolator.getInterpolatedState(), 0, yTmp, 0, y0.length);
+
+                // 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, n);
+            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);
+
+        final double stopTime  = stepStart;
+        stepStart = Double.NaN;
+        stepSize  = Double.NaN;
+        return stopTime;
+
+    }
+
+    /** 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)
+         */
+        public 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;
+                    error += ratio * ratio;
+                }
+            }
+
+            return FastMath.sqrt(error / mainSetDimension);
+
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/ode/nonstiff/AdamsNordsieckTransformer.java b/src/main/java/org/apache/commons/math/ode/nonstiff/AdamsNordsieckTransformer.java
new file mode 100644
index 0000000..1f16a76
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/nonstiff/AdamsNordsieckTransformer.java
@@ -0,0 +1,312 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.ode.nonstiff;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.math.fraction.BigFraction;
+import org.apache.commons.math.linear.Array2DRowFieldMatrix;
+import org.apache.commons.math.linear.Array2DRowRealMatrix;
+import org.apache.commons.math.linear.DefaultFieldMatrixChangingVisitor;
+import org.apache.commons.math.linear.FieldDecompositionSolver;
+import org.apache.commons.math.linear.FieldLUDecompositionImpl;
+import org.apache.commons.math.linear.FieldMatrix;
+import org.apache.commons.math.linear.MatrixUtils;
+
+/** Transformer to Nordsieck vectors for Adams integrators.
+ * <p>This class i 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(k)<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</sub> j (-i)<sup>j-1</sup> s<sub>j</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 (-i)<sup>j-1</sup> terms:
+ * <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>
+ *
+ * @version $Revision: 810196 $ $Date: 2009-09-01 21:47:46 +0200 (mar. 01 sept. 2009) $
+ * @since 2.0
+ */
+public class AdamsNordsieckTransformer {
+
+    /** Cache for already computed coefficients. */
+    private static final Map<Integer, AdamsNordsieckTransformer> CACHE =
+        new HashMap<Integer, AdamsNordsieckTransformer>();
+
+    /** Initialization matrix for the higher order derivatives wrt y'', y''' ... */
+    private final Array2DRowRealMatrix initialization;
+
+    /** Update matrix for the higher order derivatives h<sup>2</sup>/2y'', 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 nSteps number of steps of the multistep method
+     * (excluding the one being computed)
+     */
+    private AdamsNordsieckTransformer(final int nSteps) {
+
+        // compute exact coefficients
+        FieldMatrix<BigFraction> bigP = buildP(nSteps);
+        FieldDecompositionSolver<BigFraction> pSolver =
+            new FieldLUDecompositionImpl<BigFraction>(bigP).getSolver();
+
+        BigFraction[] u = new BigFraction[nSteps];
+        Arrays.fill(u, BigFraction.ONE);
+        BigFraction[] bigC1 = pSolver.solve(u);
+
+        // 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[nSteps];
+        Arrays.fill(shiftedP[0], BigFraction.ZERO);
+        FieldMatrix<BigFraction> bigMSupdate =
+            pSolver.solve(new Array2DRowFieldMatrix<BigFraction>(shiftedP, false));
+
+        // initialization coefficients, computed from a R matrix = abs(P)
+        bigP.walkInOptimizedOrder(new DefaultFieldMatrixChangingVisitor<BigFraction>(BigFraction.ZERO) {
+            /** {@inheritDoc} */
+            @Override
+            public BigFraction visit(int row, int column, BigFraction value) {
+                return ((column & 0x1) == 0x1) ? value : value.negate();
+            }
+        });
+        FieldMatrix<BigFraction> bigRInverse =
+            new FieldLUDecompositionImpl<BigFraction>(bigP).getSolver().getInverse();
+
+        // convert coefficients to double
+        initialization = MatrixUtils.bigFractionMatrixToRealMatrix(bigRInverse);
+        update         = MatrixUtils.bigFractionMatrixToRealMatrix(bigMSupdate);
+        c1             = new double[nSteps];
+        for (int i = 0; i < nSteps; ++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)
+     */
+    public int getNSteps() {
+        return c1.length;
+    }
+
+    /** Build the P matrix.
+     * <p>The P matrix general terms are shifted j (-i)<sup>j-1</sup> terms:
+     * <pre>
+     *        [  -2   3   -4    5  ... ]
+     *        [  -4  12  -32   80  ... ]
+     *   P =  [  -6  27 -108  405  ... ]
+     *        [  -8  48 -256 1280  ... ]
+     *        [          ...           ]
+     * </pre></p>
+     * @param nSteps number of steps of the multistep method
+     * (excluding the one being computed)
+     * @return P matrix
+     */
+    private FieldMatrix<BigFraction> buildP(final int nSteps) {
+
+        final BigFraction[][] pData = new BigFraction[nSteps][nSteps];
+
+        for (int i = 0; i < pData.length; ++i) {
+            // build the P matrix elements from Taylor series formulas
+            final BigFraction[] pI = pData[i];
+            final int factor = -(i + 1);
+            int aj = factor;
+            for (int j = 0; j < pI.length; ++j) {
+                pI[j] = new BigFraction(aj * (j + 2));
+                aj *= factor;
+            }
+        }
+
+        return new Array2DRowFieldMatrix<BigFraction>(pData, false);
+
+    }
+
+    /** Initialize the high order scaled derivatives at step start.
+     * @param first first scaled derivative at step start
+     * @param multistep scaled derivatives after step start (hy'1, ..., hy'k-1)
+     * will be modified
+     * @return high order derivatives at step start
+     */
+    public Array2DRowRealMatrix initializeHighOrderDerivatives(final double[] first,
+                                                     final double[][] multistep) {
+        for (int i = 0; i < multistep.length; ++i) {
+            final double[] msI = multistep[i];
+            for (int j = 0; j < first.length; ++j) {
+                msI[j] -= first[j];
+            }
+        }
+        return initialization.multiply(new Array2DRowRealMatrix(multistep, false));
+    }
+
+    /** 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/math/ode/nonstiff/AdaptiveStepsizeIntegrator.java b/src/main/java/org/apache/commons/math/ode/nonstiff/AdaptiveStepsizeIntegrator.java
new file mode 100644
index 0000000..bfae8f4
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/nonstiff/AdaptiveStepsizeIntegrator.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.math.ode.nonstiff;
+
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.ode.AbstractIntegrator;
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.ode.ExtendedFirstOrderDifferentialEquations;
+import org.apache.commons.math.ode.FirstOrderDifferentialEquations;
+import org.apache.commons.math.ode.IntegratorException;
+import org.apache.commons.math.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 ExtendedFirstOrderDifferentialEquations
+ * extended ODE} rather than a {@link FirstOrderDifferentialEquations basic ODE},
+ * then <em>only</em> the {@link ExtendedFirstOrderDifferentialEquations#getMainSetDimension()
+ * main set} 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>
+ *
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @since 1.2
+ *
+ */
+
+public abstract class AdaptiveStepsizeIntegrator
+  extends AbstractIntegrator {
+
+    /** Allowed absolute scalar error. */
+    protected final double scalAbsoluteTolerance;
+
+    /** Allowed relative scalar error. */
+    protected final double scalRelativeTolerance;
+
+    /** Allowed absolute vectorial error. */
+    protected final double[] vecAbsoluteTolerance;
+
+    /** Allowed relative vectorial error. */
+    protected final double[] vecRelativeTolerance;
+
+    /** Main set dimension. */
+    protected int mainSetDimension;
+
+    /** User supplied initial step. */
+    private double initialStep;
+
+    /** Minimal step. */
+    private final double minStep;
+
+    /** Maximal step. */
+    private final 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 (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
+   */
+  public AdaptiveStepsizeIntegrator(final String name,
+                                    final double minStep, final double maxStep,
+                                    final double scalAbsoluteTolerance,
+                                    final double scalRelativeTolerance) {
+
+    super(name);
+
+    this.minStep     = FastMath.abs(minStep);
+    this.maxStep     = FastMath.abs(maxStep);
+    this.initialStep = -1.0;
+
+    this.scalAbsoluteTolerance = scalAbsoluteTolerance;
+    this.scalRelativeTolerance = scalRelativeTolerance;
+    this.vecAbsoluteTolerance  = null;
+    this.vecRelativeTolerance  = null;
+
+    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 (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 AdaptiveStepsizeIntegrator(final String name,
+                                    final double minStep, final double maxStep,
+                                    final double[] vecAbsoluteTolerance,
+                                    final double[] vecRelativeTolerance) {
+
+    super(name);
+
+    this.minStep     = minStep;
+    this.maxStep     = maxStep;
+    this.initialStep = -1.0;
+
+    this.scalAbsoluteTolerance = 0;
+    this.scalRelativeTolerance = 0;
+    this.vecAbsoluteTolerance  = vecAbsoluteTolerance.clone();
+    this.vecRelativeTolerance  = vecRelativeTolerance.clone();
+
+    resetInternalState();
+
+  }
+
+  /** 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;
+    }
+  }
+
+  /** Perform some sanity checks on the integration parameters.
+   * @param equations differential equations set
+   * @param t0 start time
+   * @param y0 state vector at t0
+   * @param t target time for the integration
+   * @param y placeholder where to put the state vector
+   * @exception IntegratorException if some inconsistency is detected
+   */
+  @Override
+  protected void sanityChecks(final FirstOrderDifferentialEquations equations,
+                              final double t0, final double[] y0,
+                              final double t, final double[] y)
+      throws IntegratorException {
+
+      super.sanityChecks(equations, t0, y0, t, y);
+
+      if (equations instanceof ExtendedFirstOrderDifferentialEquations) {
+          mainSetDimension = ((ExtendedFirstOrderDifferentialEquations) equations).getMainSetDimension();
+      } else {
+          mainSetDimension = equations.getDimension();
+      }
+
+      if ((vecAbsoluteTolerance != null) && (vecAbsoluteTolerance.length != mainSetDimension)) {
+          throw new IntegratorException(
+                  LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, mainSetDimension, vecAbsoluteTolerance.length);
+      }
+
+      if ((vecRelativeTolerance != null) && (vecRelativeTolerance.length != mainSetDimension)) {
+          throw new IntegratorException(
+                  LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, mainSetDimension, vecRelativeTolerance.length);
+      }
+
+  }
+
+  /** Initialize the integration step.
+   * @param equations differential equations set
+   * @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 DerivativeException this exception is propagated to
+   * the caller if the underlying user function triggers one
+   */
+  public double initializeStep(final FirstOrderDifferentialEquations equations,
+                               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 DerivativeException {
+
+    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 IntegratorException if the step is too small and acceptSmall is false
+   */
+  protected double filterStep(final double h, final boolean forward, final boolean acceptSmall)
+    throws IntegratorException {
+
+      double filteredH = h;
+      if (FastMath.abs(h) < minStep) {
+          if (acceptSmall) {
+              filteredH = forward ? minStep : -minStep;
+          } else {
+              throw new IntegratorException(
+                      LocalizedFormats.MINIMAL_STEPSIZE_REACHED_DURING_INTEGRATION,
+                      minStep, FastMath.abs(h));
+          }
+      }
+
+      if (filteredH > maxStep) {
+          filteredH = maxStep;
+      } else if (filteredH < -maxStep) {
+          filteredH = -maxStep;
+      }
+
+      return filteredH;
+
+  }
+
+  /** {@inheritDoc} */
+  public abstract double integrate (FirstOrderDifferentialEquations equations,
+                                    double t0, double[] y0,
+                                    double t, double[] y)
+    throws DerivativeException, IntegratorException;
+
+  /** {@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/math/ode/nonstiff/ClassicalRungeKuttaIntegrator.java b/src/main/java/org/apache/commons/math/ode/nonstiff/ClassicalRungeKuttaIntegrator.java
new file mode 100644
index 0000000..a993d40
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/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.math.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
+ * @version $Revision: 810196 $ $Date: 2009-09-01 21:47:46 +0200 (mar. 01 sept. 2009) $
+ * @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/math/ode/nonstiff/ClassicalRungeKuttaStepInterpolator.java b/src/main/java/org/apache/commons/math/ode/nonstiff/ClassicalRungeKuttaStepInterpolator.java
new file mode 100644
index 0000000..90596b1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/nonstiff/ClassicalRungeKuttaStepInterpolator.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.math.ode.nonstiff;
+
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.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 :
+
+ * <pre>
+ *   y(t_n + theta h) = y (t_n + h)
+ *                    + (1 - theta) (h/6) [ (-4 theta^2 + 5 theta - 1) y'_1
+ *                                          +(4 theta^2 - 2 theta - 2) (y'_2 + y'_3)
+ *                                          -(4 theta^2 +   theta + 1) y'_4
+ *                                        ]
+ * </pre>
+ *
+ * where theta belongs to [0 ; 1] and where y'_1 to y'_4 are the four
+ * evaluations of the derivatives already computed during the
+ * step.</p>
+ *
+ * @see ClassicalRungeKuttaIntegrator
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @since 1.2
+ */
+
+class ClassicalRungeKuttaStepInterpolator
+    extends RungeKuttaStepInterpolator {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -6576285612589783992L;
+
+    /** 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.
+     */
+    public ClassicalRungeKuttaStepInterpolator() {
+    }
+
+    /** 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 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)
+        throws DerivativeException {
+
+        final double fourTheta      = 4 * theta;
+        final double oneMinusTheta  = 1 - theta;
+        final double oneMinus2Theta = 1 - 2 * 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);
+        final double coeffDot1     = oneMinusTheta * oneMinus2Theta;
+        final double coeffDot23    = 2 * theta * oneMinusTheta;
+        final double coeffDot4     = -theta * oneMinus2Theta;
+        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/math/ode/nonstiff/DormandPrince54Integrator.java b/src/main/java/org/apache/commons/math/ode/nonstiff/DormandPrince54Integrator.java
new file mode 100644
index 0000000..e31991f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/nonstiff/DormandPrince54Integrator.java
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.ode.nonstiff;
+
+import org.apache.commons.math.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>
+ *
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ * @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 (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
+   */
+  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 (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 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/math/ode/nonstiff/DormandPrince54StepInterpolator.java b/src/main/java/org/apache/commons/math/ode/nonstiff/DormandPrince54StepInterpolator.java
new file mode 100644
index 0000000..34cd924
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/nonstiff/DormandPrince54StepInterpolator.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.math.ode.nonstiff;
+
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.ode.AbstractIntegrator;
+import org.apache.commons.math.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
+ *
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @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 = 4104157279605906956L;
+
+    /** 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.
+   */
+  public DormandPrince54StepInterpolator() {
+    super();
+    v1 = null;
+    v2 = null;
+    v3 = null;
+    v4 = null;
+    vectorsInitialized = false;
+  }
+
+  /** 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 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) {
+    super.reinitialize(integrator, y, yDotK, forward);
+    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)
+    throws DerivativeException {
+
+    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));
+    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/math/ode/nonstiff/DormandPrince853Integrator.java b/src/main/java/org/apache/commons/math/ode/nonstiff/DormandPrince853Integrator.java
new file mode 100644
index 0000000..68c1141
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/nonstiff/DormandPrince853Integrator.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.math.ode.nonstiff;
+
+import org.apache.commons.math.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>
+ *
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ * @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 (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
+   */
+  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 (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 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/math/ode/nonstiff/DormandPrince853StepInterpolator.java b/src/main/java/org/apache/commons/math/ode/nonstiff/DormandPrince853StepInterpolator.java
new file mode 100644
index 0000000..946f8a2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/nonstiff/DormandPrince853StepInterpolator.java
@@ -0,0 +1,480 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.ode.nonstiff;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
+import org.apache.commons.math.ode.AbstractIntegrator;
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.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
+ *
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @since 1.2
+ */
+
+class DormandPrince853StepInterpolator
+  extends RungeKuttaStepInterpolator {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 7152276390558450974L;
+
+    /** 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.
+   */
+  public DormandPrince853StepInterpolator() {
+    super();
+    yDotKLast = null;
+    v         = null;
+    vectorsInitialized = false;
+  }
+
+  /** 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 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) {
+
+    super.reinitialize(integrator, y, yDotK, forward);
+
+    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 DerivativeException {
+
+    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)));
+
+    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 DerivativeException {
+
+    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 (DerivativeException e) {
+        IOException ioe = new IOException(e.getLocalizedMessage());
+        ioe.initCause(e);
+        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 {
+
+    // 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/math/ode/nonstiff/EmbeddedRungeKuttaIntegrator.java b/src/main/java/org/apache/commons/math/ode/nonstiff/EmbeddedRungeKuttaIntegrator.java
new file mode 100644
index 0000000..97e772e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/nonstiff/EmbeddedRungeKuttaIntegrator.java
@@ -0,0 +1,379 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.ode.nonstiff;
+
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.ode.FirstOrderDifferentialEquations;
+import org.apache.commons.math.ode.IntegratorException;
+import org.apache.commons.math.ode.sampling.AbstractStepInterpolator;
+import org.apache.commons.math.ode.sampling.DummyStepInterpolator;
+import org.apache.commons.math.ode.sampling.StepHandler;
+import org.apache.commons.math.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>
+ *
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @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 (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
+   */
+  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 double integrate(final FirstOrderDifferentialEquations equations,
+                          final double t0, final double[] y0,
+                          final double t, final double[] y)
+  throws DerivativeException, IntegratorException {
+
+    sanityChecks(equations, t0, y0, t, y);
+    setEquations(equations);
+    resetEvaluations();
+    final boolean forward = t > t0;
+
+    // create some internal working arrays
+    final int stages = c.length + 1;
+    if (y != y0) {
+      System.arraycopy(y0, 0, y, 0, y0.length);
+    }
+    final double[][] yDotK = new double[stages][y0.length];
+    final double[] yTmp    = new double[y0.length];
+    final double[] yDotTmp = new double[y0.length];
+
+    // set up an interpolator sharing the integrator arrays
+    AbstractStepInterpolator interpolator;
+    if (requiresDenseOutput()) {
+      final RungeKuttaStepInterpolator rki = (RungeKuttaStepInterpolator) prototype.copy();
+      rki.reinitialize(this, yTmp, yDotK, forward);
+      interpolator = rki;
+    } else {
+      interpolator = new DummyStepInterpolator(yTmp, yDotK[stages - 1], forward);
+    }
+    interpolator.storeTime(t0);
+
+    // set up integration control objects
+    stepStart         = t0;
+    double  hNew      = 0;
+    boolean firstTime = true;
+    for (StepHandler handler : stepHandlers) {
+        handler.reset();
+    }
+    setStateInitialized(false);
+
+    // 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(equations, forward, getOrder(), scale,
+                                stepStart, y, yDotK[0], yTmp, yDotK[1]);
+          firstTime = false;
+        }
+
+        stepSize = hNew;
+
+        // 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);
+
+      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);
+
+    final double stopTime = stepStart;
+    resetInternalState();
+    return stopTime;
+
+  }
+
+  /** 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/math/ode/nonstiff/EulerIntegrator.java b/src/main/java/org/apache/commons/math/ode/nonstiff/EulerIntegrator.java
new file mode 100644
index 0000000..21b0f74
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/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.math.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
+ * @version $Revision: 810196 $ $Date: 2009-09-01 21:47:46 +0200 (mar. 01 sept. 2009) $
+ * @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/math/ode/nonstiff/EulerStepInterpolator.java b/src/main/java/org/apache/commons/math/ode/nonstiff/EulerStepInterpolator.java
new file mode 100644
index 0000000..96f0c13
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/nonstiff/EulerStepInterpolator.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.math.ode.nonstiff;
+
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.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 :
+ *
+ * <pre>
+ *   y(t_n + theta h) = y (t_n + h) - (1-theta) h y'
+ * </pre>
+ *
+ * where theta belongs to [0 ; 1] and where y' is the evaluation of
+ * the derivatives already computed during the step.</p>
+ *
+ * @see EulerIntegrator
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @since 1.2
+ */
+
+class EulerStepInterpolator
+  extends RungeKuttaStepInterpolator {
+
+  /** Serializable version identifier */
+  private static final long serialVersionUID = -7179861704951334960L;
+
+  /** Simple constructor.
+   * This constructor builds an instance that is not usable yet, the
+   * {@link
+   * org.apache.commons.math.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.
+   */
+  public EulerStepInterpolator() {
+  }
+
+  /** 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 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)
+    throws DerivativeException {
+
+    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/math/ode/nonstiff/GillIntegrator.java b/src/main/java/org/apache/commons/math/ode/nonstiff/GillIntegrator.java
new file mode 100644
index 0000000..3b31f9f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/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.math.ode.nonstiff;
+
+import org.apache.commons.math.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
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ * @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/math/ode/nonstiff/GillStepInterpolator.java b/src/main/java/org/apache/commons/math/ode/nonstiff/GillStepInterpolator.java
new file mode 100644
index 0000000..3d17d27
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/nonstiff/GillStepInterpolator.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.math.ode.nonstiff;
+
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.ode.sampling.StepInterpolator;
+import org.apache.commons.math.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 :
+ *
+ * <pre>
+ *   y(t_n + theta h) = y (t_n + h)
+ *                    - (1 - theta) (h/6) [ (1 - theta) (1 - 4 theta) y'_1
+ *                                        + (1 - theta) (1 + 2 theta) ((2-q) y'_2 + (2+q) y'_3)
+ *                                        + (1 + theta + 4 theta^2) y'_4
+ *                                        ]
+ * </pre>
+ * where theta belongs to [0 ; 1], q = sqrt(2) and where y'_1 to y'_4
+ * are the four evaluations of the derivatives already computed during
+ * the step.</p>
+ *
+ * @see GillIntegrator
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @since 1.2
+ */
+
+class GillStepInterpolator
+  extends RungeKuttaStepInterpolator {
+
+    /** First Gill coefficient. */
+    private static final double TWO_MINUS_SQRT_2 = 2 - FastMath.sqrt(2.0);
+
+    /** Second Gill coefficient. */
+    private static final double TWO_PLUS_SQRT_2 = 2 + FastMath.sqrt(2.0);
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -107804074496313322L;
+
+  /** Simple constructor.
+   * This constructor builds an instance that is not usable yet, the
+   * {@link
+   * org.apache.commons.math.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.
+   */
+  public GillStepInterpolator() {
+  }
+
+  /** 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 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)
+    throws DerivativeException {
+
+    final double twoTheta  = 2 * theta;
+    final double fourTheta = 4 * theta;
+    final double s         = oneMinusThetaH / 6.0;
+    final double oMt       = 1 - theta;
+    final double soMt      = s * oMt;
+    final double c23       = soMt * (1 + twoTheta);
+    final double coeff1    = soMt * (1 - fourTheta);
+    final double coeff2    = c23  * TWO_MINUS_SQRT_2;
+    final double coeff3    = c23  * TWO_PLUS_SQRT_2;
+    final double coeff4    = s * (1 + theta * (1 + fourTheta));
+    final double coeffDot1 = theta * (twoTheta - 3) + 1;
+    final double cDot23    = theta * oMt;
+    final double coeffDot2 = cDot23  * TWO_MINUS_SQRT_2;
+    final double coeffDot3 = cDot23  * TWO_PLUS_SQRT_2;
+    final double coeffDot4 = theta * (twoTheta - 1);
+
+    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/math/ode/nonstiff/GraggBulirschStoerIntegrator.java b/src/main/java/org/apache/commons/math/ode/nonstiff/GraggBulirschStoerIntegrator.java
new file mode 100644
index 0000000..09267d0
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/nonstiff/GraggBulirschStoerIntegrator.java
@@ -0,0 +1,963 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.ode.nonstiff;
+
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.ode.FirstOrderDifferentialEquations;
+import org.apache.commons.math.ode.IntegratorException;
+import org.apache.commons.math.ode.events.EventHandler;
+import org.apache.commons.math.ode.sampling.AbstractStepInterpolator;
+import org.apache.commons.math.ode.sampling.DummyStepInterpolator;
+import org.apache.commons.math.ode.sampling.StepHandler;
+import org.apache.commons.math.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>
+ *
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @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 (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
+   */
+  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);
+    setStepsizeControl(-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);
+    setStepsizeControl(-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 setStepsizeControl(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) {
+    super.addEventHandler(function, maxCheckInterval, convergence, maxIterationCount);
+
+    // 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];
+    }
+
+    if (requiresDenseOutput()) {
+      // step size sequence: 2, 6, 10, 14, ...
+      for (int k = 0; k < size; ++k) {
+        sequence[k] = 4 * k + 2;
+      }
+    } else {
+      // step size sequence: 2, 4, 6, 8, ...
+      for (int k = 0; k < size; ++k) {
+        sequence[k] = 2 * (k + 1);
+      }
+    }
+
+    // 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
+   * @throws DerivativeException this exception is propagated to the caller if the
+   * underlying user function triggers one
+   */
+  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 DerivativeException {
+
+    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 double integrate(final FirstOrderDifferentialEquations equations,
+                          final double t0, final double[] y0, final double t, final double[] y)
+      throws DerivativeException, IntegratorException {
+
+    sanityChecks(equations, t0, y0, t, y);
+    setEquations(equations);
+    resetEvaluations();
+    final boolean forward = t > t0;
+
+    // create some internal working arrays
+    final double[] yDot0   = new double[y0.length];
+    final double[] y1      = new double[y0.length];
+    final double[] yTmp    = new double[y0.length];
+    final double[] yTmpDot = new double[y0.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[y0.length];
+      y1Diag[k] = new double[y0.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);
+    }
+
+    double[] yDot1      = new double[y0.length];
+    double[][] yMidDots = null;
+    final boolean denseOutput = requiresDenseOutput();
+    if (denseOutput) {
+      yMidDots = new double[1 + 2 * sequence.length][];
+      for (int j = 0; j < yMidDots.length; ++j) {
+        yMidDots[j] = new double[y0.length];
+      }
+    } else {
+      yMidDots    = new double[1][];
+      yMidDots[0] = new double[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
+    AbstractStepInterpolator interpolator = null;
+    if (denseOutput) {
+      interpolator = new GraggBulirschStoerStepInterpolator(y, yDot0,
+                                                            y1, yDot1,
+                                                            yMidDots, forward);
+    } else {
+      interpolator = new DummyStepInterpolator(y, yDot1, forward);
+    }
+    interpolator.storeTime(t0);
+
+    stepStart = t0;
+    double  hNew             = 0;
+    double  maxError         = Double.MAX_VALUE;
+    boolean previousRejected = false;
+    boolean firstTime        = true;
+    boolean newStep          = true;
+    boolean firstStepAlreadyComputed = false;
+    for (StepHandler handler : stepHandlers) {
+        handler.reset();
+    }
+    setStateInitialized(false);
+    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(equations, 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 (denseOutput && ! 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);
+
+    final double stopTime = stepStart;
+    resetInternalState();
+    return stopTime;
+
+  }
+
+}
diff --git a/src/main/java/org/apache/commons/math/ode/nonstiff/GraggBulirschStoerStepInterpolator.java b/src/main/java/org/apache/commons/math/ode/nonstiff/GraggBulirschStoerStepInterpolator.java
new file mode 100644
index 0000000..48fdf2c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/nonstiff/GraggBulirschStoerStepInterpolator.java
@@ -0,0 +1,401 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.ode.nonstiff;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
+import org.apache.commons.math.ode.sampling.AbstractStepInterpolator;
+import org.apache.commons.math.ode.sampling.StepInterpolator;
+import org.apache.commons.math.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
+ * @version $Revision: 1061507 $ $Date: 2011-01-20 21:55:00 +0100 (jeu. 20 janv. 2011) $
+ * @since 1.2
+ */
+
+class GraggBulirschStoerStepInterpolator
+  extends AbstractStepInterpolator {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 7320613236731409847L;
+
+    /** 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 polynoms. */
+    private double[][] polynoms;
+
+    /** Error coefficients for the interpolation. */
+    private double[] errfac;
+
+    /** Degree of the interpolation polynoms. */
+    private int currentDegree;
+
+  /** Simple constructor.
+   * This constructor should not be used directly, it is only intended
+   * for the serialization process.
+   */
+  public GraggBulirschStoerStepInterpolator() {
+    y0Dot    = null;
+    y1       = null;
+    y1Dot    = null;
+    yMidDots = null;
+    resetTables(-1);
+  }
+
+  /** 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
+   */
+  public GraggBulirschStoerStepInterpolator(final double[] y, final double[] y0Dot,
+                                            final double[] y1, final double[] y1Dot,
+                                            final double[][] yMidDots,
+                                            final boolean forward) {
+
+    super(y, forward);
+    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
+   */
+  public 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 polynoms (up to the current degree only)
+    if (interpolator.polynoms == null) {
+      polynoms = null;
+      currentDegree = -1;
+    } else {
+      resetTables(interpolator.currentDegree);
+      for (int i = 0; i < polynoms.length; ++i) {
+        polynoms[i] = new double[dimension];
+        System.arraycopy(interpolator.polynoms[i], 0,
+                         polynoms[i], 0, dimension);
+      }
+      currentDegree = interpolator.currentDegree;
+    }
+
+  }
+
+  /** Reallocate the internal tables.
+   * Reallocate the internal tables in order to be able to handle
+   * interpolation polynoms up to the given degree
+   * @param maxDegree maximal degree to handle
+   */
+  private void resetTables(final int maxDegree) {
+
+    if (maxDegree < 0) {
+      polynoms      = null;
+      errfac        = null;
+      currentDegree = -1;
+    } else {
+
+      final double[][] newPols = new double[maxDegree + 1][];
+      if (polynoms != null) {
+        System.arraycopy(polynoms, 0, newPols, 0, polynoms.length);
+        for (int i = polynoms.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];
+        }
+      }
+      polynoms = 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 ((polynoms == null) || (polynoms.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;
+
+      polynoms[0][i] = currentState[i];
+      polynoms[1][i] = ydiff;
+      polynoms[2][i] = aspl;
+      polynoms[3][i] = bspl;
+
+      if (mu < 0) {
+        return;
+      }
+
+      // compute the remaining coefficients
+      final double ph0 = 0.5 * (currentState[i] + y1[i]) + 0.125 * (aspl + bspl);
+      polynoms[4][i] = 16 * (yMidDots[0][i] - ph0);
+
+      if (mu > 0) {
+        final double ph1 = ydiff + 0.25 * (aspl - bspl);
+        polynoms[5][i] = 16 * (yMidDots[1][i] - ph1);
+
+        if (mu > 1) {
+          final double ph2 = yp1 - yp0;
+          polynoms[6][i] = 16 * (yMidDots[2][i] - ph2 + polynoms[4][i]);
+
+          if (mu > 2) {
+            final double ph3 = 6 * (bspl - aspl);
+            polynoms[7][i] = 16 * (yMidDots[3][i] - ph3 + 3 * polynoms[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);
+              polynoms[j+4][i] =
+                  16 * (yMidDots[j][i] + fac1 * polynoms[j+2][i] - fac2 * polynoms[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 = polynoms[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 = polynoms[0][i];
+        final double p1 = polynoms[1][i];
+        final double p2 = polynoms[2][i];
+        final double p3 = polynoms[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 = polynoms[currentDegree][i];
+            for (int j = currentDegree - 1; j > 3; --j) {
+                final double d = 1.0 / (j - 3);
+                cDot = d * (theta05 * cDot + c);
+                c = polynoms[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(polynoms[k][l]);
+      }
+    }
+
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public void readExternal(final ObjectInput in)
+    throws IOException {
+
+    // 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) {
+        polynoms[k][l] = in.readDouble();
+      }
+    }
+
+    // we can now set the interpolated time and state
+    setInterpolatedTime(t);
+
+  }
+
+}
diff --git a/src/main/java/org/apache/commons/math/ode/nonstiff/HighamHall54Integrator.java b/src/main/java/org/apache/commons/math/ode/nonstiff/HighamHall54Integrator.java
new file mode 100644
index 0000000..6ea8564
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/nonstiff/HighamHall54Integrator.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.math.ode.nonstiff;
+
+import org.apache.commons.math.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>
+ *
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ * @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 (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
+   */
+  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 (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 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/math/ode/nonstiff/HighamHall54StepInterpolator.java b/src/main/java/org/apache/commons/math/ode/nonstiff/HighamHall54StepInterpolator.java
new file mode 100644
index 0000000..f3eb9fb
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/nonstiff/HighamHall54StepInterpolator.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.math.ode.nonstiff;
+
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.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
+ *
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @since 1.2
+ */
+
+class HighamHall54StepInterpolator
+  extends RungeKuttaStepInterpolator {
+
+  /** Serializable version identifier */
+  private static final long serialVersionUID = -3583240427587318654L;
+
+  /** Simple constructor.
+   * This constructor builds an instance that is not usable yet, the
+   * {@link
+   * org.apache.commons.math.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.
+   */
+  public HighamHall54StepInterpolator() {
+    super();
+  }
+
+  /** 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 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)
+    throws DerivativeException {
+
+    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));
+    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);
+
+    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/math/ode/nonstiff/MidpointIntegrator.java b/src/main/java/org/apache/commons/math/ode/nonstiff/MidpointIntegrator.java
new file mode 100644
index 0000000..89d1d87
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/nonstiff/MidpointIntegrator.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.math.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
+ *
+ * @version $Revision: 810196 $ $Date: 2009-09-01 21:47:46 +0200 (mar. 01 sept. 2009) $
+ * @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/math/ode/nonstiff/MidpointStepInterpolator.java b/src/main/java/org/apache/commons/math/ode/nonstiff/MidpointStepInterpolator.java
new file mode 100644
index 0000000..094ba7a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/nonstiff/MidpointStepInterpolator.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.math.ode.nonstiff;
+
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.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 :
+ *
+ * <pre>
+ *   y(t_n + theta h) = y (t_n + h) + (1-theta) h [theta y'_1 - (1+theta) y'_2]
+ * </pre>
+ *
+ * where theta belongs to [0 ; 1] and where y'_1 and y'_2 are the two
+ * evaluations of the derivatives already computed during the
+ * step.</p>
+ *
+ * @see MidpointIntegrator
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @since 1.2
+ */
+
+class MidpointStepInterpolator
+  extends RungeKuttaStepInterpolator {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -865524111506042509L;
+
+  /** Simple constructor.
+   * This constructor builds an instance that is not usable yet, the
+   * {@link
+   * org.apache.commons.math.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.
+   */
+  public MidpointStepInterpolator() {
+  }
+
+  /** 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 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)
+    throws DerivativeException {
+
+    final double coeff1    = oneMinusThetaH * theta;
+    final double coeff2    = oneMinusThetaH * (1.0 + theta);
+    final double coeffDot2 = 2 * theta;
+    final double coeffDot1 = 1 - coeffDot2;
+
+    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/math/ode/nonstiff/RungeKuttaIntegrator.java b/src/main/java/org/apache/commons/math/ode/nonstiff/RungeKuttaIntegrator.java
new file mode 100644
index 0000000..c550f90
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/nonstiff/RungeKuttaIntegrator.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.math.ode.nonstiff;
+
+
+import org.apache.commons.math.ode.AbstractIntegrator;
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.ode.FirstOrderDifferentialEquations;
+import org.apache.commons.math.ode.IntegratorException;
+import org.apache.commons.math.ode.sampling.AbstractStepInterpolator;
+import org.apache.commons.math.ode.sampling.DummyStepInterpolator;
+import org.apache.commons.math.ode.sampling.StepHandler;
+import org.apache.commons.math.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
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @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} */
+  public double integrate(final FirstOrderDifferentialEquations equations,
+                          final double t0, final double[] y0,
+                          final double t, final double[] y)
+  throws DerivativeException, IntegratorException {
+
+    sanityChecks(equations, t0, y0, t, y);
+    setEquations(equations);
+    resetEvaluations();
+    final boolean forward = t > t0;
+
+    // create some internal working arrays
+    final int stages = c.length + 1;
+    if (y != y0) {
+      System.arraycopy(y0, 0, y, 0, y0.length);
+    }
+    final double[][] yDotK = new double[stages][];
+    for (int i = 0; i < stages; ++i) {
+      yDotK [i] = new double[y0.length];
+    }
+    final double[] yTmp    = new double[y0.length];
+    final double[] yDotTmp = new double[y0.length];
+
+    // set up an interpolator sharing the integrator arrays
+    AbstractStepInterpolator interpolator;
+    if (requiresDenseOutput()) {
+      final RungeKuttaStepInterpolator rki = (RungeKuttaStepInterpolator) prototype.copy();
+      rki.reinitialize(this, yTmp, yDotK, forward);
+      interpolator = rki;
+    } else {
+      interpolator = new DummyStepInterpolator(yTmp, yDotK[stages - 1], forward);
+    }
+    interpolator.storeTime(t0);
+
+    // set up integration control objects
+    stepStart = t0;
+    stepSize  = forward ? step : -step;
+    for (StepHandler handler : stepHandlers) {
+        handler.reset();
+    }
+    setStateInitialized(false);
+
+    // 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);
+
+    final double stopTime = stepStart;
+    stepStart = Double.NaN;
+    stepSize  = Double.NaN;
+    return stopTime;
+
+  }
+
+}
diff --git a/src/main/java/org/apache/commons/math/ode/nonstiff/RungeKuttaStepInterpolator.java b/src/main/java/org/apache/commons/math/ode/nonstiff/RungeKuttaStepInterpolator.java
new file mode 100644
index 0000000..97d9fe7
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/nonstiff/RungeKuttaStepInterpolator.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.math.ode.nonstiff;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
+import org.apache.commons.math.ode.AbstractIntegrator;
+import org.apache.commons.math.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
+ *
+ * @version $Revision: 811827 $ $Date: 2009-09-06 17:32:50 +0200 (dim. 06 sept. 2009) $
+ * @since 1.2
+ */
+
+abstract class RungeKuttaStepInterpolator
+  extends AbstractStepInterpolator {
+
+    /** 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() {
+    super();
+    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.
+
+  */
+  public RungeKuttaStepInterpolator(final RungeKuttaStepInterpolator interpolator) {
+
+    super(interpolator);
+
+    if (interpolator.currentState != null) {
+      final int dimension = currentState.length;
+
+      yDotK = new double[interpolator.yDotK.length][];
+      for (int k = 0; k < interpolator.yDotK.length; ++k) {
+        yDotK[k] = new double[dimension];
+        System.arraycopy(interpolator.yDotK[k], 0,
+                         yDotK[k], 0, dimension);
+      }
+
+    } else {
+      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
+   */
+  public void reinitialize(final AbstractIntegrator rkIntegrator,
+                           final double[] y, final double[][] yDotArray, final boolean forward) {
+    reinitialize(y, forward);
+    this.yDotK = yDotArray;
+    this.integrator = rkIntegrator;
+  }
+
+  /** {@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;
+    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 {
+
+    // read the base class
+    final double t = readBaseExternal(in);
+
+    // read the local attributes
+    final int n = (currentState == null) ? -1 : currentState.length;
+    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/math/ode/nonstiff/ThreeEighthesIntegrator.java b/src/main/java/org/apache/commons/math/ode/nonstiff/ThreeEighthesIntegrator.java
new file mode 100644
index 0000000..60c39d0
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/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.math.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
+ * @version $Revision: 810196 $ $Date: 2009-09-01 21:47:46 +0200 (mar. 01 sept. 2009) $
+ * @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/math/ode/nonstiff/ThreeEighthesStepInterpolator.java b/src/main/java/org/apache/commons/math/ode/nonstiff/ThreeEighthesStepInterpolator.java
new file mode 100644
index 0000000..19d5693
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/nonstiff/ThreeEighthesStepInterpolator.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.math.ode.nonstiff;
+
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.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 :
+ *
+ * <pre>
+ *   y(t_n + theta h) = y (t_n + h)
+ *                    - (1 - theta) (h/8) [ (1 - 7 theta + 8 theta^2) y'_1
+ *                                      + 3 (1 +   theta - 4 theta^2) y'_2
+ *                                      + 3 (1 +   theta)             y'_3
+ *                                      +   (1 +   theta + 4 theta^2) y'_4
+ *                                        ]
+ * </pre>
+ *
+ * where theta belongs to [0 ; 1] and where y'_1 to y'_4 are the four
+ * evaluations of the derivatives already computed during the
+ * step.</p>
+ *
+ * @see ThreeEighthesIntegrator
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @since 1.2
+ */
+
+class ThreeEighthesStepInterpolator
+  extends RungeKuttaStepInterpolator {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -3345024435978721931L;
+
+  /** Simple constructor.
+   * This constructor builds an instance that is not usable yet, the
+   * {@link
+   * org.apache.commons.math.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.
+   */
+  public ThreeEighthesStepInterpolator() {
+  }
+
+  /** 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 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)
+      throws DerivativeException {
+
+      final double fourTheta2 = 4 * theta * theta;
+      final double s          = oneMinusThetaH / 8.0;
+      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);
+      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);
+
+      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/math/ode/nonstiff/package.html b/src/main/java/org/apache/commons/math/ode/nonstiff/package.html
new file mode 100644
index 0000000..3e68e82
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/nonstiff/package.html
@@ -0,0 +1,25 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 613620 $ -->
+<body>
+<p>
+This package provides classes to solve non-stiff Ordinary Differential Equations problems.
+</p>
+
+</body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/ode/package.html b/src/main/java/org/apache/commons/math/ode/package.html
new file mode 100644
index 0000000..d390204
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/package.html
@@ -0,0 +1,167 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 920131 $ -->
+<body>
+<p>
+This package provides classes to solve Ordinary Differential Equations problems.
+</p>
+
+<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>.
+If in addition to <code>y(t)</code> users need to get the
+derivatives 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>, then the
+classes from the <a href="./jacobians/package-summary.html">
+org.apache.commons.math.ode.jacobians</a> package must be used
+instead of the classes in this package.
+</p>
+
+<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.math.ode.sampling.StepInterpolator StepInterpolator}
+abstract class, which are made available to the user at the end of
+each step.
+</p>
+
+<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>
+
+<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.math.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.math.ode.FirstOrderIntegrator
+FirstOrderIntegrator} interface.
+</p>
+
+<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.math.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.math.ode.sampling.StepHandler StepHandler} interface or a
+{@link org.apache.commons.math.ode.sampling.StepNormalizer StepNormalizer}
+object wrapping a user-specified object implementing the {@link
+org.apache.commons.math.ode.sampling.FixedStepHandler FixedStepHandler}
+interface into the integrator before calling the {@link
+org.apache.commons.math.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>
+
+<p>
+{@link org.apache.commons.math.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>
+
+<p>
+Other default implementations of the {@link
+org.apache.commons.math.ode.sampling.StepHandler StepHandler} interface are
+available for general needs ({@link
+org.apache.commons.math.ode.sampling.DummyStepHandler DummyStepHandler}, {@link
+org.apache.commons.math.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>
+
+<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.math.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.math.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.math.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>
+
+<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.math.ode.nonstiff.EulerIntegrator Euler}</td><td>1</td></tr>
+<tr><td>{@link org.apache.commons.math.ode.nonstiff.MidpointIntegrator Midpoint}</td><td>2</td></tr>
+<tr><td>{@link org.apache.commons.math.ode.nonstiff.ClassicalRungeKuttaIntegrator Classical Runge-Kutta}</td><td>4</td></tr>
+<tr><td>{@link org.apache.commons.math.ode.nonstiff.GillIntegrator Gill}</td><td>4</td></tr>
+<tr><td>{@link org.apache.commons.math.ode.nonstiff.ThreeEighthesIntegrator 3/8}</td><td>4</td></tr>
+</table>
+</p>
+
+<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.math.ode.nonstiff.HighamHall54Integrator Higham and Hall}</td><td>5</td><td>4</td></tr>
+<tr><td>{@link org.apache.commons.math.ode.nonstiff.DormandPrince54Integrator Dormand-Prince 5(4)}</td><td>5</td><td>4</td></tr>
+<tr><td>{@link org.apache.commons.math.ode.nonstiff.DormandPrince853Integrator Dormand-Prince 8(5,3)}</td><td>8</td><td>5 and 3</td></tr>
+<tr><td>{@link org.apache.commons.math.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.math.ode.nonstiff.AdamsBashforthIntegrator Adams-Bashforth}</td><td>variable</td><td>variable</td></tr>
+<tr><td>{@link org.apache.commons.math.ode.nonstiff.AdamsMoultonIntegrator Adams-Moulton}</td><td>variable</td><td>variable</td></tr>
+</table>
+</p>
+
+<p>
+In the table above, the {@link org.apache.commons.math.ode.nonstiff.AdamsBashforthIntegrator
+Adams-Bashforth} and {@link org.apache.commons.math.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.
+</p>
+
+</body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/ode/sampling/AbstractStepInterpolator.java b/src/main/java/org/apache/commons/math/ode/sampling/AbstractStepInterpolator.java
new file mode 100644
index 0000000..5cb2979
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/sampling/AbstractStepInterpolator.java
@@ -0,0 +1,519 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.ode.sampling;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
+import org.apache.commons.math.ode.DerivativeException;
+
+/** 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.math.ode.FirstOrderIntegrator
+ * @see org.apache.commons.math.ode.SecondOrderIntegrator
+ * @see StepHandler
+ *
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @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;
+
+  /** 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;
+
+
+  /** 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.math.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;
+    interpolatedState       = null;
+    interpolatedDerivatives = null;
+    finalized               = false;
+    this.forward            = true;
+    this.dirtyState         = true;
+  }
+
+  /** Simple constructor.
+   * @param y reference to the integrator array holding the state at
+   * the end of the step
+   * @param forward integration direction indicator
+   */
+  protected AbstractStepInterpolator(final double[] y, final boolean forward) {
+
+    globalPreviousTime = Double.NaN;
+    globalCurrentTime  = Double.NaN;
+    softPreviousTime   = Double.NaN;
+    softCurrentTime    = Double.NaN;
+    h                  = Double.NaN;
+    interpolatedTime   = Double.NaN;
+
+    currentState            = y;
+    interpolatedState       = new double[y.length];
+    interpolatedDerivatives = new double[y.length];
+
+    finalized         = false;
+    this.forward      = forward;
+    this.dirtyState   = true;
+
+  }
+
+  /** 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            = interpolator.currentState.clone();
+      interpolatedState       = interpolator.interpolatedState.clone();
+      interpolatedDerivatives = interpolator.interpolatedDerivatives.clone();
+    } else {
+      currentState            = null;
+      interpolatedState       = null;
+      interpolatedDerivatives = null;
+    }
+
+    finalized  = interpolator.finalized;
+    forward    = interpolator.forward;
+    dirtyState = interpolator.dirtyState;
+
+  }
+
+  /** Reinitialize the instance
+   * @param y reference to the integrator array holding the state at
+   * the end of the step
+   * @param isForward integration direction indicator
+   */
+  protected void reinitialize(final double[] y, final boolean isForward) {
+
+    globalPreviousTime = Double.NaN;
+    globalCurrentTime  = Double.NaN;
+    softPreviousTime   = Double.NaN;
+    softCurrentTime    = Double.NaN;
+    h                  = Double.NaN;
+    interpolatedTime   = Double.NaN;
+
+    currentState            = y;
+    interpolatedState       = new double[y.length];
+    interpolatedDerivatives = new double[y.length];
+
+    finalized         = false;
+    this.forward      = isForward;
+    this.dirtyState   = true;
+
+  }
+
+  /** {@inheritDoc} */
+   public StepInterpolator copy() throws DerivativeException {
+
+     // 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
+   * @since 2.2
+   */
+  public double getGlobalPreviousTime() {
+    return globalPreviousTime;
+  }
+
+  /**
+   * Get the current global grid point time.
+   * @return current global grid point time
+   * @since 2.2
+   */
+  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
+   * @throws DerivativeException this exception is propagated to the caller if the
+   * underlying user function triggers one
+   */
+  protected abstract void computeInterpolatedStateAndDerivatives(double theta,
+                                                                 double oneMinusThetaH)
+    throws DerivativeException;
+
+  /** {@inheritDoc} */
+  public double[] getInterpolatedState() throws DerivativeException {
+
+      // 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;
+      }
+
+      return interpolatedState;
+
+  }
+
+  /** {@inheritDoc} */
+  public double[] getInterpolatedDerivatives() throws DerivativeException {
+
+      // 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;
+      }
+
+      return interpolatedDerivatives;
+
+  }
+
+  /**
+   * 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>
+   *
+   * @throws DerivativeException this exception is propagated to the
+   * caller if the underlying user function triggers one
+   */
+  public final void finalizeStep()
+    throws DerivativeException {
+    if (! finalized) {
+      doFinalize();
+      finalized = true;
+    }
+  }
+
+  /**
+   * Really finalize the step.
+   * The default implementation of this method does nothing.
+   * @throws DerivativeException this exception is propagated to the
+   * caller if the underlying user function triggers one
+   */
+  protected void doFinalize()
+    throws DerivativeException {
+  }
+
+  /** {@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);
+
+    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
+
+    // finalize the step (and don't bother saving the now true flag)
+    try {
+      finalizeStep();
+    } catch (DerivativeException e) {
+        IOException ioe = new IOException(e.getLocalizedMessage());
+        ioe.initCause(e);
+        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 be set later by the caller
+   * @exception IOException in case of read error
+   */
+  protected double readBaseExternal(final ObjectInput in)
+    throws IOException {
+
+    final int dimension = in.readInt();
+    globalPreviousTime  = in.readDouble();
+    globalCurrentTime   = in.readDouble();
+    softPreviousTime    = in.readDouble();
+    softCurrentTime     = in.readDouble();
+    h                   = in.readDouble();
+    forward             = in.readBoolean();
+    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;
+    interpolatedState       = (dimension < 0) ? null : new double[dimension];
+    interpolatedDerivatives = (dimension < 0) ? null : new double[dimension];
+
+    finalized = true;
+
+    return in.readDouble();
+
+  }
+
+}
diff --git a/src/main/java/org/apache/commons/math/ode/sampling/DummyStepHandler.java b/src/main/java/org/apache/commons/math/ode/sampling/DummyStepHandler.java
new file mode 100644
index 0000000..cc759b9
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/sampling/DummyStepHandler.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.math.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
+ * @version $Revision: 811827 $ $Date: 2009-09-06 17:32:50 +0200 (dim. 06 sept. 2009) $
+ * @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;
+    }
+
+    /** Determines whether this handler needs dense output.
+     * Since this handler does nothing, it does not require dense output.
+     * @return always false
+     */
+    public boolean requiresDenseOutput() {
+        return false;
+    }
+
+    /** Reset the step handler.
+     * Initialize the internal data as required before the first step is
+     * handled.
+     */
+    public void reset() {
+    }
+
+    /**
+     * 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/math/ode/sampling/DummyStepInterpolator.java b/src/main/java/org/apache/commons/math/ode/sampling/DummyStepInterpolator.java
new file mode 100644
index 0000000..af28c13
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/sampling/DummyStepInterpolator.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.math.ode.sampling;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+
+/** This class is a step interpolator that does nothing.
+ *
+ * <p>This class is used when the {@link StepHandler "step handler"}
+ * set up by the user does not need step interpolation. It does not
+ * recompute the state when {@link AbstractStepInterpolator#setInterpolatedTime
+ * setInterpolatedTime} is called. This implies the interpolated state
+ * is always the state at the end of the current step.</p>
+ *
+ * @see StepHandler
+ *
+ * @version $Revision: 1037327 $ $Date: 2010-11-20 21:57:37 +0100 (sam. 20 nov. 2010) $
+ * @since 1.2
+ */
+
+public class DummyStepInterpolator
+  extends AbstractStepInterpolator {
+
+  /** Serializable version identifier. */
+  private static final long serialVersionUID = 1708010296707839488L;
+
+  /** Current derivative. */
+  private double[] currentDerivative;
+
+  /** Simple constructor.
+   * This constructor builds an instance that is not usable yet, the
+   * <code>AbstractStepInterpolator.reinitialize</code> protected 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.math.ode.nonstiff.EmbeddedRungeKuttaIntegrator} uses
+   * the prototyping design pattern to create the step interpolators by
+   * cloning an uninitialized model and latter initializing the copy.
+   */
+  public DummyStepInterpolator() {
+    super();
+    currentDerivative = null;
+  }
+
+  /** Simple constructor.
+   * @param y reference to the integrator array holding the state at
+   * the end of the step
+   * @param yDot reference to the integrator array holding the state
+   * derivative at some arbitrary point within the step
+   * @param forward integration direction indicator
+   */
+  public DummyStepInterpolator(final double[] y, final double[] yDot, final boolean forward) {
+    super(y, forward);
+    currentDerivative = yDot;
+  }
+
+  /** 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 DummyStepInterpolator(final DummyStepInterpolator interpolator) {
+    super(interpolator);
+    currentDerivative = interpolator.currentDerivative.clone();
+  }
+
+  /** Really copy the finalized instance.
+   * @return a copy of the finalized instance
+   */
+  @Override
+  protected StepInterpolator doCopy() {
+    return new DummyStepInterpolator(this);
+  }
+
+  /** Compute the state at the interpolated time.
+   * In this class, this method does nothing: the interpolated state
+   * is always the state at the end of the current step.
+   * @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
+   */
+  @Override
+  protected void computeInterpolatedStateAndDerivatives(final double theta, final double oneMinusThetaH) {
+      System.arraycopy(currentState,      0, interpolatedState,       0, currentState.length);
+      System.arraycopy(currentDerivative, 0, interpolatedDerivatives, 0, currentDerivative.length);
+  }
+
+  /** Write the instance to an output channel.
+   * @param out output channel
+   * @exception IOException if the instance cannot be written
+   */
+  @Override
+  public void writeExternal(final ObjectOutput out)
+    throws IOException {
+
+      // save the state of the base class
+    writeBaseExternal(out);
+
+    if (currentDerivative != null) {
+        for (int i = 0; i < currentDerivative.length; ++i) {
+            out.writeDouble(currentDerivative[i]);
+        }
+    }
+
+  }
+
+  /** Read the instance from an input channel.
+   * @param in input channel
+   * @exception IOException if the instance cannot be read
+   */
+  @Override
+  public void readExternal(final ObjectInput in)
+    throws IOException {
+
+    // read the base class
+    final double t = readBaseExternal(in);
+
+    if (currentState == null) {
+        currentDerivative = null;
+    } else {
+        currentDerivative  = new double[currentState.length];
+        for (int i = 0; i < currentDerivative.length; ++i) {
+            currentDerivative[i] = in.readDouble();
+        }
+    }
+
+    // we can now set the interpolated time and state
+    setInterpolatedTime(t);
+
+  }
+
+}
diff --git a/src/main/java/org/apache/commons/math/ode/sampling/FixedStepHandler.java b/src/main/java/org/apache/commons/math/ode/sampling/FixedStepHandler.java
new file mode 100644
index 0000000..190c999
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/sampling/FixedStepHandler.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.math.ode.sampling;
+
+import org.apache.commons.math.ode.DerivativeException;
+
+/**
+ * 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
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @since 1.2
+ */
+
+public interface FixedStepHandler  {
+
+  /**
+   * 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
+   * @throws DerivativeException if some error condition is encountered
+   */
+  void handleStep(double t, double[] y, double[] yDot, boolean isLast) throws DerivativeException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/ode/sampling/NordsieckStepInterpolator.java b/src/main/java/org/apache/commons/math/ode/sampling/NordsieckStepInterpolator.java
new file mode 100644
index 0000000..2090276
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/sampling/NordsieckStepInterpolator.java
@@ -0,0 +1,291 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.ode.sampling;
+
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.Arrays;
+
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.linear.Array2DRowRealMatrix;
+import org.apache.commons.math.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.math.ode.nonstiff.AdamsBashforthIntegrator
+ * @see org.apache.commons.math.ode.nonstiff.AdamsMoultonIntegrator
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @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
+     */
+    @Override
+    public void reinitialize(final double[] y, final boolean forward) {
+        super.reinitialize(y, forward);
+        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 Nordiseck 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()
+     * @throws DerivativeException if this call induces an automatic
+     * step finalization that throws one
+     */
+    public double[] getInterpolatedStateVariation()
+        throws DerivativeException {
+        // 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/math/ode/sampling/StepHandler.java b/src/main/java/org/apache/commons/math/ode/sampling/StepHandler.java
new file mode 100644
index 0000000..53c2fbe
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/sampling/StepHandler.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.math.ode.sampling;
+
+import org.apache.commons.math.ode.DerivativeException;
+
+/**
+ * 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.math.ode.FirstOrderIntegrator
+ * @see org.apache.commons.math.ode.SecondOrderIntegrator
+ * @see StepInterpolator
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @since 1.2
+ */
+
+public interface StepHandler {
+
+  /** Determines whether this handler needs dense output.
+   * <p>This method allows the integrator to avoid performing extra
+   * computation if the handler does not need dense output. If this
+   * method returns false, the integrator will call the {@link
+   * #handleStep} method with a {@link DummyStepInterpolator} rather
+   * than a custom interpolator.</p>
+   * @return true if the handler needs dense output
+   */
+  boolean requiresDenseOutput();
+
+  /** Reset the step handler.
+   * Initialize the internal data as required before the first step is
+   * handled.
+   */
+  void reset();
+
+  /**
+   * 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.math.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 DerivativeException if user code called from step interpolator
+   * finalization triggers one
+   */
+  void handleStep(StepInterpolator interpolator, boolean isLast) throws DerivativeException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/ode/sampling/StepInterpolator.java b/src/main/java/org/apache/commons/math/ode/sampling/StepInterpolator.java
new file mode 100644
index 0000000..ceb5a86
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/sampling/StepInterpolator.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.math.ode.sampling;
+
+import java.io.Externalizable;
+
+import org.apache.commons.math.ode.DerivativeException;
+
+/** 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.math.ode.FirstOrderIntegrator
+ * @see org.apache.commons.math.ode.SecondOrderIntegrator
+ * @see StepHandler
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @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. If a
+   * specific state must be preserved, a copy of the instance must be
+   * created using {@link #copy()}.</p>
+   * @param time time of the interpolated point
+   */
+  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.</p>
+   * @return state vector at time {@link #getInterpolatedTime}
+   * @see #getInterpolatedDerivatives()
+   * @exception DerivativeException if user code called from step interpolator
+   * finalization triggers one
+   */
+  double[] getInterpolatedState() throws DerivativeException;
+
+  /**
+   * 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.</p>
+   * @return derivatives of the state vector at time {@link #getInterpolatedTime}
+   * @see #getInterpolatedState()
+   * @exception DerivativeException if user code called from step interpolator
+   * finalization triggers one
+   * @since 2.0
+   */
+  double[] getInterpolatedDerivatives() throws DerivativeException;
+
+  /** 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.
+   * @exception DerivativeException if user code called from step interpolator
+   * finalization triggers one
+   * @see #setInterpolatedTime(double)
+   */
+   StepInterpolator copy() throws DerivativeException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/ode/sampling/StepNormalizer.java b/src/main/java/org/apache/commons/math/ode/sampling/StepNormalizer.java
new file mode 100644
index 0000000..73bcc23
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/sampling/StepNormalizer.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.math.ode.sampling;
+
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * 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 the beginning time of
+ * the integration t0 and also at times t0+h, t0+2h, ... If the
+ * integration range is an integer multiple of the stepsize, then the
+ * last point handled will be the endpoint of the integration tend, if
+ * not, the last point will belong to the interval [tend - h ;
+ * tend].</p>
+ *
+ * <p>There is no constraint on the integrator, it can use any
+ * timestep it needs (time steps longer or shorter than the fixed time
+ * step and non-integer ratios are all allowed).</p>
+ *
+ * @see StepHandler
+ * @see FixedStepHandler
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @since 1.2
+ */
+
+public class StepNormalizer implements StepHandler {
+
+    /** Fixed time step. */
+    private double h;
+
+    /** Underlying step handler. */
+    private final FixedStepHandler handler;
+
+    /** Last step time. */
+    private double lastTime;
+
+    /** Last State vector. */
+    private double[] lastState;
+
+    /** Last Derivatives vector. */
+    private double[] lastDerivatives;
+
+    /** Integration direction indicator. */
+    private boolean forward;
+
+    /** Simple constructor.
+     * @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       = FastMath.abs(h);
+        this.handler = handler;
+        reset();
+    }
+
+    /** Determines whether this handler needs dense output.
+     * This handler needs dense output in order to provide data at
+     * regularly spaced steps regardless of the steps the integrator
+     * uses, so this method always returns true.
+     * @return always true
+     */
+    public boolean requiresDenseOutput() {
+        return true;
+    }
+
+    /** Reset the step handler.
+     * Initialize the internal data as required before the first step is
+     * handled.
+     */
+    public void reset() {
+        lastTime        = Double.NaN;
+        lastState       = null;
+        lastDerivatives = null;
+        forward         = true;
+    }
+
+    /**
+     * 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
+     * @throws DerivativeException this exception is propagated to the
+     * caller if the underlying user function triggers one
+     */
+    public void handleStep(final StepInterpolator interpolator, final boolean isLast)
+        throws DerivativeException {
+
+        if (lastState == null) {
+
+            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;
+            }
+
+        }
+
+        double nextTime = lastTime + h;
+        boolean nextInStep = forward ^ (nextTime > interpolator.getCurrentTime());
+        while (nextInStep) {
+
+            // output the stored previous step
+            handler.handleStep(lastTime, lastState, lastDerivatives, false);
+
+            // store the next step
+            lastTime = nextTime;
+            interpolator.setInterpolatedTime(lastTime);
+            System.arraycopy(interpolator.getInterpolatedState(), 0,
+                             lastState, 0, lastState.length);
+            System.arraycopy(interpolator.getInterpolatedDerivatives(), 0,
+                             lastDerivatives, 0, lastDerivatives.length);
+
+            nextTime  += h;
+            nextInStep = forward ^ (nextTime > interpolator.getCurrentTime());
+
+        }
+
+        if (isLast) {
+            // there will be no more steps,
+            // the stored one should be flagged as being the last
+            handler.handleStep(lastTime, lastState, lastDerivatives, true);
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/ode/sampling/package.html b/src/main/java/org/apache/commons/math/ode/sampling/package.html
new file mode 100644
index 0000000..46de4ef
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/ode/sampling/package.html
@@ -0,0 +1,60 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 613620 $ -->
+<body>
+<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.math.ode.sampling.StepHandler StepHandler} instance using the
+{@link org.apache.commons.math.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.math.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.math.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.math.ode.sampling.FixedStepHandler FixedStepHandler}
+interface can be used. Objects implementing this interface should be wrapped within a
+{@link org.apache.commons.math.ode.sampling.StepNormalizer StepNormalizer} instance
+in order to be registered to the integrator.
+</p>
+
+</body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/optimization/DifferentiableMultivariateRealOptimizer.java b/src/main/java/org/apache/commons/math/optimization/DifferentiableMultivariateRealOptimizer.java
new file mode 100644
index 0000000..b7b7dbb
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/DifferentiableMultivariateRealOptimizer.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.math.optimization;
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.analysis.DifferentiableMultivariateRealFunction;
+
+/**
+ * This interface represents an optimization algorithm for
+ * {@link DifferentiableMultivariateRealFunction scalar differentiable objective
+ * functions}.
+ * Optimization algorithms find the input point set that either {@link GoalType
+ * maximize or minimize} an objective function.
+ *
+ * @see MultivariateRealOptimizer
+ * @see DifferentiableMultivariateVectorialOptimizer
+ * @version $Revision: 1065484 $ $Date: 2011-01-31 06:45:14 +0100 (lun. 31 janv. 2011) $
+ * @since 2.0
+ */
+public interface DifferentiableMultivariateRealOptimizer {
+
+    /** 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
+     * {@code optimize} method. It is 0 if the method has not been called yet.
+     * </p>
+     * @return number of iterations
+     */
+    int getIterations();
+
+    /** Set the maximal number of functions evaluations.
+     * @param maxEvaluations maximal number of function 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 objective function.
+     * <p>
+     * The number of evaluations corresponds to the last call to the
+     * {@link #optimize(DifferentiableMultivariateRealFunction, GoalType, double[]) optimize}
+     * method. It is 0 if the method has not been called yet.
+     * </p>
+     * @return number of evaluations of the objective function
+     */
+    int getEvaluations();
+
+    /** Get the number of evaluations of the objective function gradient.
+     * <p>
+     * The number of evaluations corresponds to the last call to the
+     * {@link #optimize(DifferentiableMultivariateRealFunction, GoalType, double[]) optimize}
+     * method. It is 0 if the method has not been called yet.
+     * </p>
+     * @return number of evaluations of the objective function gradient
+     */
+    int getGradientEvaluations();
+
+    /** Set the convergence checker.
+     * @param checker object to use to check for convergence
+     */
+    void setConvergenceChecker(RealConvergenceChecker checker);
+
+    /** Get the convergence checker.
+     * @return object used to check for convergence
+     */
+    RealConvergenceChecker getConvergenceChecker();
+
+    /** Optimizes an objective function.
+     * @param f objective function
+     * @param goalType type of optimization goal: either {@link GoalType#MAXIMIZE}
+     * or {@link GoalType#MINIMIZE}
+     * @param startPoint the start point for optimization
+     * @return the point/value pair giving the optimal value for objective function
+     * @exception FunctionEvaluationException if the objective function throws one during
+     * the search
+     * @exception OptimizationException if the algorithm failed to converge
+     * @exception IllegalArgumentException if the start point dimension is wrong
+     */
+    RealPointValuePair optimize(DifferentiableMultivariateRealFunction f,
+                                  GoalType goalType,
+                                  double[] startPoint)
+        throws FunctionEvaluationException, OptimizationException, IllegalArgumentException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/DifferentiableMultivariateVectorialOptimizer.java b/src/main/java/org/apache/commons/math/optimization/DifferentiableMultivariateVectorialOptimizer.java
new file mode 100644
index 0000000..51f0763
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/DifferentiableMultivariateVectorialOptimizer.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.math.optimization;
+
+import org.apache.commons.math.analysis.DifferentiableMultivariateVectorialFunction;
+import org.apache.commons.math.FunctionEvaluationException;
+
+/**
+ * This interface represents an optimization algorithm for {@link DifferentiableMultivariateVectorialFunction
+ * vectorial differentiable objective functions}.
+ * <p>Optimization algorithms find the input point set that either {@link GoalType
+ * maximize or minimize} an objective function.</p>
+ * @see MultivariateRealOptimizer
+ * @see DifferentiableMultivariateRealOptimizer
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @since 2.0
+ */
+public interface DifferentiableMultivariateVectorialOptimizer {
+
+    /** 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.
+     * @return number of iterations
+    */
+   int getIterations();
+
+   /** Set the maximal number of functions evaluations.
+    * @param maxEvaluations maximal number of function 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 objective function.
+     * <p>
+     * The number of evaluation correspond to the last call to the
+     * {@link #optimize(DifferentiableMultivariateVectorialFunction,
+     * double[], double[], double[]) optimize} method. It is 0 if
+     * the method has not been called yet.
+     * </p>
+     * @return number of evaluations of the objective function
+     */
+    int getEvaluations();
+
+    /** Get the number of evaluations of the objective function jacobian .
+     * <p>
+     * The number of evaluation correspond to the last call to the
+     * {@link #optimize(DifferentiableMultivariateVectorialFunction,
+     * double[], double[], double[]) optimize} method. It is 0 if
+     * the method has not been called yet.
+     * </p>
+     * @return number of evaluations of the objective function jacobian
+     */
+    int getJacobianEvaluations();
+
+    /** Set the convergence checker.
+     * @param checker object to use to check for convergence
+     */
+    void setConvergenceChecker(VectorialConvergenceChecker checker);
+
+    /** Get the convergence checker.
+     * @return object used to check for convergence
+     */
+    VectorialConvergenceChecker getConvergenceChecker();
+
+    /** Optimizes an objective function.
+     * <p>
+     * Optimization is considered to be a weighted least-squares minimization.
+     * The cost function to be minimized is
+     * &sum;weight<sub>i</sub>(objective<sub>i</sub>-target<sub>i</sub>)<sup>2</sup>
+     * </p>
+     * @param f objective function
+     * @param target target value for the objective functions at optimum
+     * @param weights weight for the least squares cost computation
+     * @param startPoint the start point for optimization
+     * @return the point/value pair giving the optimal value for objective function
+     * @exception FunctionEvaluationException if the objective function throws one during
+     * the search
+     * @exception OptimizationException if the algorithm failed to converge
+     * @exception IllegalArgumentException if the start point dimension is wrong
+     */
+    VectorialPointValuePair optimize(DifferentiableMultivariateVectorialFunction f,
+                                     double[] target, double[] weights,
+                                     double[] startPoint)
+        throws FunctionEvaluationException, OptimizationException, IllegalArgumentException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/GoalType.java b/src/main/java/org/apache/commons/math/optimization/GoalType.java
new file mode 100644
index 0000000..1392d27
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/GoalType.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.math.optimization;
+
+import java.io.Serializable;
+
+/**
+ * Goal type for an optimization problem.
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ * @since 2.0
+ */
+public enum GoalType implements Serializable {
+
+    /** Maximization goal. */
+    MAXIMIZE,
+
+    /** Minimization goal. */
+    MINIMIZE
+
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/LeastSquaresConverter.java b/src/main/java/org/apache/commons/math/optimization/LeastSquaresConverter.java
new file mode 100644
index 0000000..0ed2efd
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/LeastSquaresConverter.java
@@ -0,0 +1,193 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.optimization;
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.analysis.MultivariateRealFunction;
+import org.apache.commons.math.analysis.MultivariateVectorialFunction;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.linear.RealMatrix;
+
+/** This class converts {@link MultivariateVectorialFunction vectorial
+ * objective functions} to {@link MultivariateRealFunction 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>
+ * <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 MultivariateRealFunction} 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>
+ * <p>
+ * This class support combination of residuals with or without weights and correlations.
+ * </p>
+  *
+ * @see MultivariateRealFunction
+ * @see MultivariateVectorialFunction
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ * @since 2.0
+ */
+
+public class LeastSquaresConverter implements MultivariateRealFunction {
+
+    /** Underlying vectorial function. */
+    private final MultivariateVectorialFunction 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 MultivariateVectorialFunction 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>
+     * <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 FunctionEvaluationException} 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
+     * @exception IllegalArgumentException if the observations vector and the weights
+     * vector dimensions don't match (objective function dimension is checked only when
+     * the {@link #value(double[])} method is called)
+     */
+    public LeastSquaresConverter(final MultivariateVectorialFunction function,
+                                 final double[] observations, final double[] weights)
+        throws IllegalArgumentException {
+        if (observations.length != weights.length) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE,
+                    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>
+     * <p>
+     * The array computed by the objective function, the observations array and the
+     * the scaling matrix must have consistent sizes or a {@link FunctionEvaluationException}
+     * 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
+     * @exception IllegalArgumentException if the observations vector and the scale
+     * matrix dimensions don't match (objective function dimension is checked only when
+     * the {@link #value(double[])} method is called)
+     */
+    public LeastSquaresConverter(final MultivariateVectorialFunction function,
+                                 final double[] observations, final RealMatrix scale)
+        throws IllegalArgumentException {
+        if (observations.length != scale.getColumnDimension()) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE,
+                    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) throws FunctionEvaluationException {
+
+        // compute residuals
+        final double[] residuals = function.value(point);
+        if (residuals.length != observations.length) {
+            throw new FunctionEvaluationException(point,LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE,
+                                        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/math/optimization/MultiStartDifferentiableMultivariateRealOptimizer.java b/src/main/java/org/apache/commons/math/optimization/MultiStartDifferentiableMultivariateRealOptimizer.java
new file mode 100644
index 0000000..9cde37b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/MultiStartDifferentiableMultivariateRealOptimizer.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.math.optimization;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.analysis.DifferentiableMultivariateRealFunction;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.random.RandomVectorGenerator;
+
+/**
+ * Special implementation of the {@link DifferentiableMultivariateRealOptimizer} 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.
+ * </p>
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @since 2.0
+ */
+public class MultiStartDifferentiableMultivariateRealOptimizer
+    implements DifferentiableMultivariateRealOptimizer {
+
+    /** Underlying classical optimizer. */
+    private final DifferentiableMultivariateRealOptimizer optimizer;
+
+    /** Maximal number of iterations allowed. */
+    private int maxIterations;
+
+    /** Number of iterations already performed for all starts. */
+    private int totalIterations;
+
+    /** Maximal number of evaluations allowed. */
+    private int maxEvaluations;
+
+    /** Number of evaluations already performed for all starts. */
+    private int totalEvaluations;
+
+    /** Number of gradient evaluations already performed for all starts. */
+    private int totalGradientEvaluations;
+
+    /** Number of starts to go. */
+    private int starts;
+
+    /** Random generator for multi-start. */
+    private RandomVectorGenerator generator;
+
+    /** Found optima. */
+    private RealPointValuePair[] 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 (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 MultiStartDifferentiableMultivariateRealOptimizer(final DifferentiableMultivariateRealOptimizer optimizer,
+                                                             final int starts,
+                                                             final RandomVectorGenerator generator) {
+        this.optimizer                = optimizer;
+        this.totalIterations          = 0;
+        this.totalEvaluations         = 0;
+        this.totalGradientEvaluations = 0;
+        this.starts                   = starts;
+        this.generator                = generator;
+        this.optima                   = null;
+        setMaxIterations(Integer.MAX_VALUE);
+        setMaxEvaluations(Integer.MAX_VALUE);
+    }
+
+    /** Get all the optima found during the last call to {@link
+     * #optimize(DifferentiableMultivariateRealFunction, GoalType, double[])
+     * optimize}.
+     * <p>The optimizer stores all the optima found during a set of
+     * restarts. The {@link #optimize(DifferentiableMultivariateRealFunction,
+     * 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(DifferentiableMultivariateRealFunction, GoalType, double[])
+     * optimize} method.
+     * </p>
+     * <p>
+     * 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(DifferentiableMultivariateRealFunction,
+     * GoalType, double[]) optimize} method did throw a {@link
+     * org.apache.commons.math.ConvergenceException ConvergenceException}).
+     * This also means that if the first element is non null, it is the best
+     * point found across all starts.</p>
+     * @return array containing the optima
+     * @exception IllegalStateException if {@link #optimize(DifferentiableMultivariateRealFunction,
+     * GoalType, double[]) optimize} has not been called
+     */
+    public RealPointValuePair[] getOptima() throws IllegalStateException {
+        if (optima == null) {
+            throw MathRuntimeException.createIllegalStateException(LocalizedFormats.NO_OPTIMUM_COMPUTED_YET);
+        }
+        return optima.clone();
+    }
+
+    /** {@inheritDoc} */
+    public void setMaxIterations(int maxIterations) {
+        this.maxIterations = maxIterations;
+    }
+
+    /** {@inheritDoc} */
+    public int getMaxIterations() {
+        return maxIterations;
+    }
+
+    /** {@inheritDoc} */
+    public int getIterations() {
+        return totalIterations;
+    }
+
+    /** {@inheritDoc} */
+    public void setMaxEvaluations(int maxEvaluations) {
+        this.maxEvaluations = maxEvaluations;
+    }
+
+    /** {@inheritDoc} */
+    public int getMaxEvaluations() {
+        return maxEvaluations;
+    }
+
+    /** {@inheritDoc} */
+    public int getEvaluations() {
+        return totalEvaluations;
+    }
+
+    /** {@inheritDoc} */
+    public int getGradientEvaluations() {
+        return totalGradientEvaluations;
+    }
+
+    /** {@inheritDoc} */
+    public void setConvergenceChecker(RealConvergenceChecker checker) {
+        optimizer.setConvergenceChecker(checker);
+    }
+
+    /** {@inheritDoc} */
+    public RealConvergenceChecker getConvergenceChecker() {
+        return optimizer.getConvergenceChecker();
+    }
+
+    /** {@inheritDoc} */
+    public RealPointValuePair optimize(final DifferentiableMultivariateRealFunction f,
+                                         final GoalType goalType,
+                                         double[] startPoint)
+        throws FunctionEvaluationException, OptimizationException, FunctionEvaluationException {
+
+        optima                   = new RealPointValuePair[starts];
+        totalIterations          = 0;
+        totalEvaluations         = 0;
+        totalGradientEvaluations = 0;
+
+        // multi-start loop
+        for (int i = 0; i < starts; ++i) {
+
+            try {
+                optimizer.setMaxIterations(maxIterations - totalIterations);
+                optimizer.setMaxEvaluations(maxEvaluations - totalEvaluations);
+                optima[i] = optimizer.optimize(f, goalType,
+                                               (i == 0) ? startPoint : generator.nextVector());
+            } catch (FunctionEvaluationException fee) {
+                optima[i] = null;
+            } catch (OptimizationException oe) {
+                optima[i] = null;
+            }
+
+            totalIterations          += optimizer.getIterations();
+            totalEvaluations         += optimizer.getEvaluations();
+            totalGradientEvaluations += optimizer.getGradientEvaluations();
+
+        }
+
+        // sort the optima from best to worst, followed by null elements
+        Arrays.sort(optima, new Comparator<RealPointValuePair>() {
+            public int compare(final RealPointValuePair o1, final RealPointValuePair 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 (goalType == GoalType.MINIMIZE) ?
+                        Double.compare(v1, v2) : Double.compare(v2, v1);
+            }
+        });
+
+        if (optima[0] == null) {
+            throw new OptimizationException(
+                    LocalizedFormats.NO_CONVERGENCE_WITH_ANY_START_POINT,
+                    starts);
+        }
+
+        // return the found point given the best objective function value
+        return optima[0];
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/MultiStartDifferentiableMultivariateVectorialOptimizer.java b/src/main/java/org/apache/commons/math/optimization/MultiStartDifferentiableMultivariateVectorialOptimizer.java
new file mode 100644
index 0000000..d2d5268
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/MultiStartDifferentiableMultivariateVectorialOptimizer.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.math.optimization;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.analysis.DifferentiableMultivariateVectorialFunction;
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.random.RandomVectorGenerator;
+
+/**
+ * Special implementation of the {@link DifferentiableMultivariateVectorialOptimizer} 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.
+ * </p>
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @since 2.0
+ */
+public class MultiStartDifferentiableMultivariateVectorialOptimizer
+    implements DifferentiableMultivariateVectorialOptimizer {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 9206382258980561530L;
+
+    /** Underlying classical optimizer. */
+    private final DifferentiableMultivariateVectorialOptimizer optimizer;
+
+    /** Maximal number of iterations allowed. */
+    private int maxIterations;
+
+    /** Number of iterations already performed for all starts. */
+    private int totalIterations;
+
+    /** Maximal number of evaluations allowed. */
+    private int maxEvaluations;
+
+    /** Number of evaluations already performed for all starts. */
+    private int totalEvaluations;
+
+    /** Number of jacobian evaluations already performed for all starts. */
+    private int totalJacobianEvaluations;
+
+    /** Number of starts to go. */
+    private int starts;
+
+    /** Random generator for multi-start. */
+    private RandomVectorGenerator generator;
+
+    /** Found optima. */
+    private VectorialPointValuePair[] 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 (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 MultiStartDifferentiableMultivariateVectorialOptimizer(
+                final DifferentiableMultivariateVectorialOptimizer optimizer,
+                final int starts,
+                final RandomVectorGenerator generator) {
+        this.optimizer                = optimizer;
+        this.totalIterations          = 0;
+        this.totalEvaluations         = 0;
+        this.totalJacobianEvaluations = 0;
+        this.starts                   = starts;
+        this.generator                = generator;
+        this.optima                   = null;
+        setMaxIterations(Integer.MAX_VALUE);
+        setMaxEvaluations(Integer.MAX_VALUE);
+    }
+
+    /** Get all the optima found during the last call to {@link
+     * #optimize(DifferentiableMultivariateVectorialFunction,
+     * double[], double[], double[]) optimize}.
+     * <p>The optimizer stores all the optima found during a set of
+     * restarts. The {@link #optimize(DifferentiableMultivariateVectorialFunction,
+     * 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(DifferentiableMultivariateVectorialFunction, double[],
+     * double[], double[]) optimize} method.
+     * </p>
+     * <p>
+     * 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(DifferentiableMultivariateVectorialFunction,
+     * double[], double[], double[]) optimize} method did throw a {@link
+     * org.apache.commons.math.ConvergenceException ConvergenceException}).
+     * This also means that if the first element is non null, it is the best
+     * point found across all starts.</p>
+     * @return array containing the optima
+     * @exception IllegalStateException if {@link #optimize(DifferentiableMultivariateVectorialFunction,
+     * double[], double[], double[]) optimize} has not been called
+     */
+    public VectorialPointValuePair[] getOptima() throws IllegalStateException {
+        if (optima == null) {
+            throw MathRuntimeException.createIllegalStateException(LocalizedFormats.NO_OPTIMUM_COMPUTED_YET);
+        }
+        return optima.clone();
+    }
+
+    /** {@inheritDoc} */
+    public void setMaxIterations(int maxIterations) {
+        this.maxIterations = maxIterations;
+    }
+
+    /** {@inheritDoc} */
+    public int getMaxIterations() {
+        return maxIterations;
+    }
+
+    /** {@inheritDoc} */
+    public int getIterations() {
+        return totalIterations;
+    }
+
+    /** {@inheritDoc} */
+    public void setMaxEvaluations(int maxEvaluations) {
+        this.maxEvaluations = maxEvaluations;
+    }
+
+    /** {@inheritDoc} */
+    public int getMaxEvaluations() {
+        return maxEvaluations;
+    }
+
+    /** {@inheritDoc} */
+    public int getEvaluations() {
+        return totalEvaluations;
+    }
+
+    /** {@inheritDoc} */
+    public int getJacobianEvaluations() {
+        return totalJacobianEvaluations;
+    }
+
+    /** {@inheritDoc} */
+    public void setConvergenceChecker(VectorialConvergenceChecker checker) {
+        optimizer.setConvergenceChecker(checker);
+    }
+
+    /** {@inheritDoc} */
+    public VectorialConvergenceChecker getConvergenceChecker() {
+        return optimizer.getConvergenceChecker();
+    }
+
+    /** {@inheritDoc} */
+    public VectorialPointValuePair optimize(final DifferentiableMultivariateVectorialFunction f,
+                                            final double[] target, final double[] weights,
+                                            final double[] startPoint)
+        throws FunctionEvaluationException, OptimizationException, IllegalArgumentException {
+
+        optima                   = new VectorialPointValuePair[starts];
+        totalIterations          = 0;
+        totalEvaluations         = 0;
+        totalJacobianEvaluations = 0;
+
+        // multi-start loop
+        for (int i = 0; i < starts; ++i) {
+
+            try {
+                optimizer.setMaxIterations(maxIterations - totalIterations);
+                optimizer.setMaxEvaluations(maxEvaluations - totalEvaluations);
+                optima[i] = optimizer.optimize(f, target, weights,
+                                               (i == 0) ? startPoint : generator.nextVector());
+            } catch (FunctionEvaluationException fee) {
+                optima[i] = null;
+            } catch (OptimizationException oe) {
+                optima[i] = null;
+            }
+
+            totalIterations          += optimizer.getIterations();
+            totalEvaluations         += optimizer.getEvaluations();
+            totalJacobianEvaluations += optimizer.getJacobianEvaluations();
+
+        }
+
+        // sort the optima from best to worst, followed by null elements
+        Arrays.sort(optima, new Comparator<VectorialPointValuePair>() {
+            public int compare(final VectorialPointValuePair o1, final VectorialPointValuePair 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 VectorialPointValuePair 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;
+            }
+        });
+
+        if (optima[0] == null) {
+            throw new OptimizationException(
+                    LocalizedFormats.NO_CONVERGENCE_WITH_ANY_START_POINT,
+                    starts);
+        }
+
+        // return the found point given the best objective function value
+        return optima[0];
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/MultiStartMultivariateRealOptimizer.java b/src/main/java/org/apache/commons/math/optimization/MultiStartMultivariateRealOptimizer.java
new file mode 100644
index 0000000..fdba1d5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/MultiStartMultivariateRealOptimizer.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.math.optimization;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.analysis.MultivariateRealFunction;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.random.RandomVectorGenerator;
+
+/**
+ * Special implementation of the {@link MultivariateRealOptimizer} 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.
+ * </p>
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @since 2.0
+ */
+public class MultiStartMultivariateRealOptimizer
+    implements MultivariateRealOptimizer {
+
+    /** Underlying classical optimizer. */
+    private final MultivariateRealOptimizer optimizer;
+
+    /** Maximal number of iterations allowed. */
+    private int maxIterations;
+
+    /** Maximal number of evaluations allowed. */
+    private int maxEvaluations;
+
+    /** Number of iterations already performed for all starts. */
+    private int totalIterations;
+
+    /** 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 RealPointValuePair[] 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 (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 MultiStartMultivariateRealOptimizer(final MultivariateRealOptimizer optimizer,
+                                               final int starts,
+                                               final RandomVectorGenerator generator) {
+        this.optimizer        = optimizer;
+        this.totalIterations  = 0;
+        this.totalEvaluations = 0;
+        this.starts           = starts;
+        this.generator        = generator;
+        this.optima           = null;
+        setMaxIterations(Integer.MAX_VALUE);
+        setMaxEvaluations(Integer.MAX_VALUE);
+    }
+
+    /** Get all the optima found during the last call to {@link
+     * #optimize(MultivariateRealFunction, GoalType, double[]) optimize}.
+     * <p>The optimizer stores all the optima found during a set of
+     * restarts. The {@link #optimize(MultivariateRealFunction, 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(MultivariateRealFunction, GoalType, double[]) optimize}
+     * method.
+     * </p>
+     * <p>
+     * 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(MultivariateRealFunction,
+     * GoalType, double[]) optimize} method did throw a {@link
+     * org.apache.commons.math.ConvergenceException ConvergenceException}).
+     * This also means that if the first element is non null, it is the best
+     * point found across all starts.</p>
+     * @return array containing the optima
+     * @exception IllegalStateException if {@link #optimize(MultivariateRealFunction,
+     * GoalType, double[]) optimize} has not been called
+     */
+    public RealPointValuePair[] getOptima() throws IllegalStateException {
+        if (optima == null) {
+            throw MathRuntimeException.createIllegalStateException(LocalizedFormats.NO_OPTIMUM_COMPUTED_YET);
+        }
+        return optima.clone();
+    }
+
+    /** {@inheritDoc} */
+    public void setMaxIterations(int maxIterations) {
+        this.maxIterations = maxIterations;
+    }
+
+    /** {@inheritDoc} */
+    public int getMaxIterations() {
+        return maxIterations;
+    }
+
+    /** {@inheritDoc} */
+    public void setMaxEvaluations(int maxEvaluations) {
+        this.maxEvaluations = maxEvaluations;
+    }
+
+    /** {@inheritDoc} */
+    public int getMaxEvaluations() {
+        return maxEvaluations;
+    }
+
+    /** {@inheritDoc} */
+    public int getIterations() {
+        return totalIterations;
+    }
+
+    /** {@inheritDoc} */
+    public int getEvaluations() {
+        return totalEvaluations;
+    }
+
+    /** {@inheritDoc} */
+    public void setConvergenceChecker(RealConvergenceChecker checker) {
+        optimizer.setConvergenceChecker(checker);
+    }
+
+    /** {@inheritDoc} */
+    public RealConvergenceChecker getConvergenceChecker() {
+        return optimizer.getConvergenceChecker();
+    }
+
+    /** {@inheritDoc} */
+    public RealPointValuePair optimize(final MultivariateRealFunction f,
+                                         final GoalType goalType,
+                                         double[] startPoint)
+        throws FunctionEvaluationException, OptimizationException, FunctionEvaluationException {
+
+        optima           = new RealPointValuePair[starts];
+        totalIterations  = 0;
+        totalEvaluations = 0;
+
+        // multi-start loop
+        for (int i = 0; i < starts; ++i) {
+
+            try {
+                optimizer.setMaxIterations(maxIterations - totalIterations);
+                optimizer.setMaxEvaluations(maxEvaluations - totalEvaluations);
+                optima[i] = optimizer.optimize(f, goalType,
+                                               (i == 0) ? startPoint : generator.nextVector());
+            } catch (FunctionEvaluationException fee) {
+                optima[i] = null;
+            } catch (OptimizationException oe) {
+                optima[i] = null;
+            }
+
+            totalIterations  += optimizer.getIterations();
+            totalEvaluations += optimizer.getEvaluations();
+
+        }
+
+        // sort the optima from best to worst, followed by null elements
+        Arrays.sort(optima, new Comparator<RealPointValuePair>() {
+            public int compare(final RealPointValuePair o1, final RealPointValuePair 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 (goalType == GoalType.MINIMIZE) ?
+                        Double.compare(v1, v2) : Double.compare(v2, v1);
+            }
+        });
+
+        if (optima[0] == null) {
+            throw new OptimizationException(
+                    LocalizedFormats.NO_CONVERGENCE_WITH_ANY_START_POINT,
+                    starts);
+        }
+
+        // return the found point given the best objective function value
+        return optima[0];
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/MultiStartUnivariateRealOptimizer.java b/src/main/java/org/apache/commons/math/optimization/MultiStartUnivariateRealOptimizer.java
new file mode 100644
index 0000000..b5ccc5f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/MultiStartUnivariateRealOptimizer.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.math.optimization;
+
+import org.apache.commons.math.ConvergenceException;
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.random.RandomGenerator;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Special implementation of the {@link UnivariateRealOptimizer} 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.
+ * </p>
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ * @since 2.0
+ */
+public class MultiStartUnivariateRealOptimizer implements UnivariateRealOptimizer {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 5983375963110961019L;
+
+    /** Underlying classical optimizer. */
+    private final UnivariateRealOptimizer optimizer;
+
+    /** Maximal number of iterations allowed. */
+    private int maxIterations;
+
+    /** Maximal number of evaluations allowed. */
+    private int maxEvaluations;
+
+    /** Number of iterations already performed for all starts. */
+    private int totalIterations;
+
+    /** 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 double[] optima;
+
+    /** Found function values at optima. */
+    private double[] optimaValues;
+
+    /**
+     * 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 generator to use for restarts
+     */
+    public MultiStartUnivariateRealOptimizer(final UnivariateRealOptimizer optimizer,
+                                             final int starts,
+                                             final RandomGenerator generator) {
+        this.optimizer        = optimizer;
+        this.totalIterations  = 0;
+        this.starts           = starts;
+        this.generator        = generator;
+        this.optima           = null;
+        setMaximalIterationCount(Integer.MAX_VALUE);
+        setMaxEvaluations(Integer.MAX_VALUE);
+    }
+
+    /** {@inheritDoc} */
+    public double getFunctionValue() {
+        return optimaValues[0];
+    }
+
+    /** {@inheritDoc} */
+    public double getResult() {
+        return optima[0];
+    }
+
+    /** {@inheritDoc} */
+    public double getAbsoluteAccuracy() {
+        return optimizer.getAbsoluteAccuracy();
+    }
+
+    /** {@inheritDoc} */
+    public int getIterationCount() {
+        return totalIterations;
+    }
+
+    /** {@inheritDoc} */
+    public int getMaximalIterationCount() {
+        return maxIterations;
+    }
+
+    /** {@inheritDoc} */
+    public int getMaxEvaluations() {
+        return maxEvaluations;
+    }
+
+    /** {@inheritDoc} */
+    public int getEvaluations() {
+        return totalEvaluations;
+    }
+
+    /** {@inheritDoc} */
+    public double getRelativeAccuracy() {
+        return optimizer.getRelativeAccuracy();
+    }
+
+    /** {@inheritDoc} */
+    public void resetAbsoluteAccuracy() {
+        optimizer.resetAbsoluteAccuracy();
+    }
+
+    /** {@inheritDoc} */
+    public void resetMaximalIterationCount() {
+        optimizer.resetMaximalIterationCount();
+    }
+
+    /** {@inheritDoc} */
+    public void resetRelativeAccuracy() {
+        optimizer.resetRelativeAccuracy();
+    }
+
+    /** {@inheritDoc} */
+    public void setAbsoluteAccuracy(double accuracy) {
+        optimizer.setAbsoluteAccuracy(accuracy);
+    }
+
+    /** {@inheritDoc} */
+    public void setMaximalIterationCount(int count) {
+        this.maxIterations = count;
+    }
+
+    /** {@inheritDoc} */
+    public void setMaxEvaluations(int maxEvaluations) {
+        this.maxEvaluations = maxEvaluations;
+    }
+
+    /** {@inheritDoc} */
+    public void setRelativeAccuracy(double accuracy) {
+        optimizer.setRelativeAccuracy(accuracy);
+    }
+
+    /** Get all the optima found during the last call to {@link
+     * #optimize(UnivariateRealFunction, GoalType, double, double) optimize}.
+     * <p>The optimizer stores all the optima found during a set of
+     * restarts. The {@link #optimize(UnivariateRealFunction, 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(UnivariateRealFunction, GoalType, double, double) optimize}
+     * method.
+     * </p>
+     * <p>
+     * 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 Double.NaN elements
+     * corresponding to the runs that did not converge. This means all
+     * elements will be NaN if the {@link #optimize(UnivariateRealFunction,
+     * GoalType, double, double) optimize} method did throw a {@link
+     * ConvergenceException ConvergenceException}). This also means that
+     * if the first element is not NaN, it is the best point found across
+     * all starts.</p>
+     * @return array containing the optima
+     * @exception IllegalStateException if {@link #optimize(UnivariateRealFunction,
+     * GoalType, double, double) optimize} has not been called
+     * @see #getOptimaValues()
+     */
+    public double[] getOptima() throws IllegalStateException {
+        if (optima == null) {
+            throw MathRuntimeException.createIllegalStateException(LocalizedFormats.NO_OPTIMUM_COMPUTED_YET);
+        }
+        return optima.clone();
+    }
+
+    /** Get all the function values at optima found during the last call to {@link
+     * #optimize(UnivariateRealFunction, GoalType, double, double) optimize}.
+     * <p>
+     * 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 Double.NaN elements
+     * corresponding to the runs that did not converge. This means all
+     * elements will be NaN if the {@link #optimize(UnivariateRealFunction,
+     * GoalType, double, double) optimize} method did throw a {@link
+     * ConvergenceException ConvergenceException}). This also means that
+     * if the first element is not NaN, it is the best point found across
+     * all starts.</p>
+     * @return array containing the optima
+     * @exception IllegalStateException if {@link #optimize(UnivariateRealFunction,
+     * GoalType, double, double) optimize} has not been called
+     * @see #getOptima()
+     */
+    public double[] getOptimaValues() throws IllegalStateException {
+        if (optimaValues == null) {
+            throw MathRuntimeException.createIllegalStateException(LocalizedFormats.NO_OPTIMUM_COMPUTED_YET);
+        }
+        return optimaValues.clone();
+    }
+
+    /** {@inheritDoc} */
+    public double optimize(final UnivariateRealFunction f, final GoalType goalType,
+                           final double min, final double max)
+        throws ConvergenceException, FunctionEvaluationException {
+
+        optima           = new double[starts];
+        optimaValues     = new double[starts];
+        totalIterations  = 0;
+        totalEvaluations = 0;
+
+        // multi-start loop
+        for (int i = 0; i < starts; ++i) {
+
+            try {
+                optimizer.setMaximalIterationCount(maxIterations - totalIterations);
+                optimizer.setMaxEvaluations(maxEvaluations - totalEvaluations);
+                final double bound1 = (i == 0) ? min : min + generator.nextDouble() * (max - min);
+                final double bound2 = (i == 0) ? max : min + generator.nextDouble() * (max - min);
+                optima[i]       = optimizer.optimize(f, goalType,
+                                                     FastMath.min(bound1, bound2),
+                                                     FastMath.max(bound1, bound2));
+                optimaValues[i] = optimizer.getFunctionValue();
+            } catch (FunctionEvaluationException fee) {
+                optima[i]       = Double.NaN;
+                optimaValues[i] = Double.NaN;
+            } catch (ConvergenceException ce) {
+                optima[i]       = Double.NaN;
+                optimaValues[i] = Double.NaN;
+            }
+
+            totalIterations  += optimizer.getIterationCount();
+            totalEvaluations += optimizer.getEvaluations();
+
+        }
+
+        // sort the optima from best to worst, followed by NaN elements
+        int lastNaN = optima.length;
+        for (int i = 0; i < lastNaN; ++i) {
+            if (Double.isNaN(optima[i])) {
+                optima[i] = optima[--lastNaN];
+                optima[lastNaN + 1] = Double.NaN;
+                optimaValues[i] = optimaValues[--lastNaN];
+                optimaValues[lastNaN + 1] = Double.NaN;
+            }
+        }
+
+        double currX = optima[0];
+        double currY = optimaValues[0];
+        for (int j = 1; j < lastNaN; ++j) {
+            final double prevY = currY;
+            currX = optima[j];
+            currY = optimaValues[j];
+            if ((goalType == GoalType.MAXIMIZE) ^ (currY < prevY)) {
+                // the current element should be inserted closer to the beginning
+                int i = j - 1;
+                double mIX = optima[i];
+                double mIY = optimaValues[i];
+                while ((i >= 0) && ((goalType == GoalType.MAXIMIZE) ^ (currY < mIY))) {
+                    optima[i + 1]       = mIX;
+                    optimaValues[i + 1] = mIY;
+                    if (i-- != 0) {
+                        mIX = optima[i];
+                        mIY = optimaValues[i];
+                    } else {
+                        mIX = Double.NaN;
+                        mIY = Double.NaN;
+                    }
+                }
+                optima[i + 1]       = currX;
+                optimaValues[i + 1] = currY;
+                currX = optima[j];
+                currY = optimaValues[j];
+            }
+        }
+
+        if (Double.isNaN(optima[0])) {
+            throw new OptimizationException(
+                    LocalizedFormats.NO_CONVERGENCE_WITH_ANY_START_POINT,
+                    starts);
+        }
+
+        // return the found point given the best objective function value
+        return optima[0];
+
+    }
+
+    /** {@inheritDoc} */
+    public double optimize(final UnivariateRealFunction f, final GoalType goalType,
+                           final double min, final double max, final double startValue)
+            throws ConvergenceException, FunctionEvaluationException {
+        return optimize(f, goalType, min, max);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/MultivariateRealOptimizer.java b/src/main/java/org/apache/commons/math/optimization/MultivariateRealOptimizer.java
new file mode 100644
index 0000000..d63afcd
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/MultivariateRealOptimizer.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.math.optimization;
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.analysis.MultivariateRealFunction;
+
+/**
+ * This interface represents an optimization algorithm for {@link MultivariateRealFunction
+ * scalar objective functions}.
+ * <p>Optimization algorithms find the input point set that either {@link GoalType
+ * maximize or minimize} an objective function.</p>
+ * @see DifferentiableMultivariateRealOptimizer
+ * @see DifferentiableMultivariateVectorialOptimizer
+ * @version $Revision: 1065481 $ $Date: 2011-01-31 06:31:41 +0100 (lun. 31 janv. 2011) $
+ * @since 2.0
+ */
+public interface MultivariateRealOptimizer {
+
+    /** Set the maximal number of iterations of the algorithm.
+     * @param maxIterations maximal number of algorithm iterations
+     */
+    void setMaxIterations(int maxIterations);
+
+    /** Get the maximal number of iterations of the algorithm.
+     * @return maximal number of iterations
+     */
+    int getMaxIterations();
+
+    /** Set the maximal number of functions evaluations.
+     * @param maxEvaluations maximal number of function evaluations
+     */
+    void setMaxEvaluations(int maxEvaluations);
+
+    /** Get the maximal number of functions evaluations.
+     * @return maximal number of functions evaluations
+     */
+    int getMaxEvaluations();
+
+    /** Get the number of iterations realized by the algorithm.
+     * <p>
+     * The number of evaluations corresponds to the last call to the
+     * {@link #optimize(MultivariateRealFunction, GoalType, double[]) optimize}
+     * method. It is 0 if the method has not been called yet.
+     * </p>
+     * @return number of iterations
+     */
+    int getIterations();
+
+    /** Get the number of evaluations of the objective function.
+     * <p>
+     * The number of evaluations corresponds to the last call to the
+     * {@link #optimize(MultivariateRealFunction, GoalType, double[]) optimize}
+     * method. It is 0 if the method has not been called yet.
+     * </p>
+     * @return number of evaluations of the objective function
+     */
+    int getEvaluations();
+
+    /** Set the convergence checker.
+     * @param checker object to use to check for convergence
+     */
+    void setConvergenceChecker(RealConvergenceChecker checker);
+
+    /** Get the convergence checker.
+     * @return object used to check for convergence
+     */
+    RealConvergenceChecker getConvergenceChecker();
+
+    /** Optimizes an objective function.
+     * @param f objective function
+     * @param goalType type of optimization goal: either {@link GoalType#MAXIMIZE}
+     * or {@link GoalType#MINIMIZE}
+     * @param startPoint the start point for optimization
+     * @return the point/value pair giving the optimal value for objective function
+     * @exception FunctionEvaluationException if the objective function throws one during
+     * the search
+     * @exception OptimizationException if the algorithm failed to converge
+     * @exception IllegalArgumentException if the start point dimension is wrong
+     */
+    RealPointValuePair optimize(MultivariateRealFunction f,
+                                  GoalType goalType,
+                                  double[] startPoint)
+        throws FunctionEvaluationException, OptimizationException, IllegalArgumentException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/OptimizationException.java b/src/main/java/org/apache/commons/math/optimization/OptimizationException.java
new file mode 100644
index 0000000..b76f51a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/OptimizationException.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.math.optimization;
+
+import org.apache.commons.math.ConvergenceException;
+import org.apache.commons.math.exception.util.DummyLocalizable;
+import org.apache.commons.math.exception.util.Localizable;
+
+/**
+ * This class represents exceptions thrown by optimizers.
+ *
+ * @version $Revision: 1044015 $ $Date: 2010-12-09 17:06:26 +0100 (jeu. 09 déc. 2010) $
+ * @since 1.2
+ * @deprecated in 2.2 (to be removed in 3.0).
+ */
+
+public class OptimizationException extends ConvergenceException {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -4605887730798282127L;
+
+    /**
+     * 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)
+     * @deprecated as of 2.2 replaced by {@link #OptimizationException(Localizable, Object...)}
+     */
+    @Deprecated
+    public OptimizationException(String specifier, Object ... parts) {
+        this(new DummyLocalizable(specifier), parts);
+    }
+
+    /**
+     * 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 OptimizationException(Localizable specifier, Object ... parts) {
+        super(specifier, parts);
+    }
+
+    /**
+     * Create an exception with a given root cause.
+     * @param cause  the exception or error that caused this exception to be thrown
+     */
+    public OptimizationException(Throwable cause) {
+        super(cause);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/RealConvergenceChecker.java b/src/main/java/org/apache/commons/math/optimization/RealConvergenceChecker.java
new file mode 100644
index 0000000..0ab37f5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/RealConvergenceChecker.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.math.optimization;
+
+/** This interface specifies how to check if an {@link MultivariateRealOptimizer optimization
+ * algorithm} has converged.
+ *
+ * <p>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.</p>
+ * <p>For convenience, two implementations that fit simple needs are already provided:
+ * {@link SimpleScalarValueChecker} and {@link SimpleRealPointChecker}. The first
+ * one considers convergence is reached when the objective function value does not
+ * change much anymore, it does not use the point set at all. The second one
+ * considers convergence is reached when the input point set does not change
+ * much anymore, it does not use objective function value at all.</p>
+ *
+ * @version $Revision: 799857 $ $Date: 2009-08-01 15:07:12 +0200 (sam. 01 août 2009) $
+ * @since 2.0
+ */
+
+public interface RealConvergenceChecker {
+
+  /** Check if the optimization algorithm has converged considering the last points.
+   * <p>
+   * 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.
+   * </p>
+   * @param iteration index of current iteration
+   * @param previous point from previous iteration
+   * @param current point from current iteration
+   * @return true if the algorithm is considered to have converged
+   */
+  boolean converged(int iteration, RealPointValuePair previous, RealPointValuePair current);
+
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/RealPointValuePair.java b/src/main/java/org/apache/commons/math/optimization/RealPointValuePair.java
new file mode 100644
index 0000000..25ecec9
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/RealPointValuePair.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.math.optimization;
+
+import java.io.Serializable;
+
+
+/**
+ * This class holds a point and the value of an objective function at this point.
+ * <p>This is a simple immutable container.</p>
+ * @see VectorialPointValuePair
+ * @see org.apache.commons.math.analysis.MultivariateRealFunction
+ * @version $Revision: 980981 $ $Date: 2010-07-31 00:03:04 +0200 (sam. 31 juil. 2010) $
+ * @since 2.0
+ */
+public class RealPointValuePair implements Serializable {
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 1003888396256744753L;
+
+    /** Point coordinates. */
+    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 coordinates (the built instance will store
+     * a copy of the array, not the array passed as argument)
+     * @param value value of an objective function at the point
+     */
+    public RealPointValuePair(final double[] point, final double value) {
+        this.point = (point == null) ? null : point.clone();
+        this.value  = value;
+    }
+
+    /** Build a point/objective function value pair.
+     * @param point point coordinates (the built instance will store
+     * a copy of the array, not the array passed as argument)
+     * @param value value of an objective function at the point
+     * @param copyArray if true, the input array will be copied, otherwise
+     * it will be referenced
+     */
+    public RealPointValuePair(final double[] point, final double value,
+                              final boolean copyArray) {
+        this.point = copyArray ?
+                     ((point == null) ? null : point.clone()) :
+                     point;
+        this.value  = value;
+    }
+
+    /** Get the point.
+     * @return a copy of the stored point
+     */
+    public double[] getPoint() {
+        return (point == null) ? null : point.clone();
+    }
+
+    /** Get a reference to the point.
+     * <p>This method is provided as a convenience to avoid copying
+     * the array, the elements of the array should <em>not</em> be modified.</p>
+     * @return a reference to the internal array storing the point
+     */
+    public double[] getPointRef() {
+        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/math/optimization/SimpleRealPointChecker.java b/src/main/java/org/apache/commons/math/optimization/SimpleRealPointChecker.java
new file mode 100644
index 0000000..6cf0725
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/SimpleRealPointChecker.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.math.optimization;
+
+import org.apache.commons.math.util.FastMath;
+import org.apache.commons.math.util.MathUtils;
+
+/**
+ * Simple implementation of the {@link RealConvergenceChecker} 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.
+ * </p>
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ * @since 2.0
+ */
+public class SimpleRealPointChecker implements RealConvergenceChecker {
+
+    /** Default relative threshold. */
+    private static final double DEFAULT_RELATIVE_THRESHOLD = 100 * MathUtils.EPSILON;
+
+    /** Default absolute threshold. */
+    private static final double DEFAULT_ABSOLUTE_THRESHOLD = 100 * MathUtils.SAFE_MIN;
+
+    /** Relative tolerance threshold. */
+    private final double relativeThreshold;
+
+    /** Absolute tolerance threshold. */
+    private final double absoluteThreshold;
+
+   /** Build an instance with default threshold.
+     */
+    public SimpleRealPointChecker() {
+        this.relativeThreshold = DEFAULT_RELATIVE_THRESHOLD;
+        this.absoluteThreshold = DEFAULT_ABSOLUTE_THRESHOLD;
+    }
+
+    /** Build an instance with a specified threshold.
+     * <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.
+     * </p>
+     * @param relativeThreshold relative tolerance threshold
+     * @param absoluteThreshold absolute tolerance threshold
+     */
+    public SimpleRealPointChecker(final double relativeThreshold,
+                                 final double absoluteThreshold) {
+        this.relativeThreshold = relativeThreshold;
+        this.absoluteThreshold = absoluteThreshold;
+    }
+
+    /** {@inheritDoc} */
+    public boolean converged(final int iteration,
+                             final RealPointValuePair previous,
+                             final RealPointValuePair current) {
+        final double[] p        = previous.getPoint();
+        final double[] c        = current.getPoint();
+        for (int i = 0; i < p.length; ++i) {
+            final double difference = FastMath.abs(p[i] - c[i]);
+            final double size       = FastMath.max(FastMath.abs(p[i]), FastMath.abs(c[i]));
+            if ((difference > (size * relativeThreshold)) && (difference > absoluteThreshold)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/SimpleScalarValueChecker.java b/src/main/java/org/apache/commons/math/optimization/SimpleScalarValueChecker.java
new file mode 100644
index 0000000..fbfd30c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/SimpleScalarValueChecker.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.math.optimization;
+
+import org.apache.commons.math.util.FastMath;
+import org.apache.commons.math.util.MathUtils;
+
+/**
+ * Simple implementation of the {@link RealConvergenceChecker} 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.
+ * </p>
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ * @since 2.0
+ */
+public class SimpleScalarValueChecker implements RealConvergenceChecker {
+
+    /** Default relative threshold. */
+    private static final double DEFAULT_RELATIVE_THRESHOLD = 100 * MathUtils.EPSILON;
+
+    /** Default absolute threshold. */
+    private static final double DEFAULT_ABSOLUTE_THRESHOLD = 100 * MathUtils.SAFE_MIN;
+
+    /** Relative tolerance threshold. */
+    private final double relativeThreshold;
+
+    /** Absolute tolerance threshold. */
+    private final double absoluteThreshold;
+
+   /** Build an instance with default threshold.
+     */
+    public SimpleScalarValueChecker() {
+        this.relativeThreshold = DEFAULT_RELATIVE_THRESHOLD;
+        this.absoluteThreshold = DEFAULT_ABSOLUTE_THRESHOLD;
+    }
+
+    /** Build an instance with a specified threshold.
+     * <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.
+     * </p>
+     * @param relativeThreshold relative tolerance threshold
+     * @param absoluteThreshold absolute tolerance threshold
+     */
+    public SimpleScalarValueChecker(final double relativeThreshold,
+                                    final double absoluteThreshold) {
+        this.relativeThreshold = relativeThreshold;
+        this.absoluteThreshold = absoluteThreshold;
+    }
+
+    /** {@inheritDoc} */
+    public boolean converged(final int iteration,
+                             final RealPointValuePair previous,
+                             final RealPointValuePair current) {
+        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 * relativeThreshold)) || (difference <= absoluteThreshold);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/SimpleVectorialPointChecker.java b/src/main/java/org/apache/commons/math/optimization/SimpleVectorialPointChecker.java
new file mode 100644
index 0000000..0c69ca8
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/SimpleVectorialPointChecker.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.math.optimization;
+
+import org.apache.commons.math.util.FastMath;
+import org.apache.commons.math.util.MathUtils;
+
+/**
+ * Simple implementation of the {@link VectorialConvergenceChecker} 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.
+ * </p>
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ * @since 2.0
+ */
+public class SimpleVectorialPointChecker implements VectorialConvergenceChecker {
+
+    /** Default relative threshold. */
+    private static final double DEFAULT_RELATIVE_THRESHOLD = 100 * MathUtils.EPSILON;
+
+    /** Default absolute threshold. */
+    private static final double DEFAULT_ABSOLUTE_THRESHOLD = 100 * MathUtils.SAFE_MIN;
+
+    /** Relative tolerance threshold. */
+    private final double relativeThreshold;
+
+    /** Absolute tolerance threshold. */
+    private final double absoluteThreshold;
+
+   /** Build an instance with default threshold.
+     */
+    public SimpleVectorialPointChecker() {
+        this.relativeThreshold = DEFAULT_RELATIVE_THRESHOLD;
+        this.absoluteThreshold = DEFAULT_ABSOLUTE_THRESHOLD;
+    }
+
+    /** Build an instance with a specified threshold.
+     * <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.
+     * </p>
+     * @param relativeThreshold relative tolerance threshold
+     * @param absoluteThreshold absolute tolerance threshold
+     */
+    public SimpleVectorialPointChecker(final double relativeThreshold,
+                                       final double absoluteThreshold) {
+        this.relativeThreshold = relativeThreshold;
+        this.absoluteThreshold = absoluteThreshold;
+    }
+
+    /** {@inheritDoc} */
+    public boolean converged(final int iteration,
+                             final VectorialPointValuePair previous,
+                             final VectorialPointValuePair current) {
+        final double[] p = previous.getPointRef();
+        final double[] c = current.getPointRef();
+        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 * relativeThreshold)) &&
+                (difference > absoluteThreshold)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/SimpleVectorialValueChecker.java b/src/main/java/org/apache/commons/math/optimization/SimpleVectorialValueChecker.java
new file mode 100644
index 0000000..8be67ee
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/SimpleVectorialValueChecker.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.math.optimization;
+
+import org.apache.commons.math.util.FastMath;
+import org.apache.commons.math.util.MathUtils;
+
+/**
+ * Simple implementation of the {@link VectorialConvergenceChecker} 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.
+ * </p>
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ * @since 2.0
+ */
+public class SimpleVectorialValueChecker implements VectorialConvergenceChecker {
+
+    /** Default relative threshold. */
+    private static final double DEFAULT_RELATIVE_THRESHOLD = 100 * MathUtils.EPSILON;
+
+    /** Default absolute threshold. */
+    private static final double DEFAULT_ABSOLUTE_THRESHOLD = 100 * MathUtils.SAFE_MIN;
+
+    /** Relative tolerance threshold. */
+    private final double relativeThreshold;
+
+    /** Absolute tolerance threshold. */
+    private final double absoluteThreshold;
+
+   /** Build an instance with default threshold.
+     */
+    public SimpleVectorialValueChecker() {
+        this.relativeThreshold = DEFAULT_RELATIVE_THRESHOLD;
+        this.absoluteThreshold = DEFAULT_ABSOLUTE_THRESHOLD;
+    }
+
+    /** Build an instance with a specified threshold.
+     * <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.
+     * </p>
+     * @param relativeThreshold relative tolerance threshold
+     * @param absoluteThreshold absolute tolerance threshold
+     */
+    public SimpleVectorialValueChecker(final double relativeThreshold,
+                                       final double absoluteThreshold) {
+        this.relativeThreshold = relativeThreshold;
+        this.absoluteThreshold = absoluteThreshold;
+    }
+
+    /** {@inheritDoc} */
+    public boolean converged(final int iteration,
+                             final VectorialPointValuePair previous,
+                             final VectorialPointValuePair current) {
+        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 * relativeThreshold)) &&
+                (difference > absoluteThreshold)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/UnivariateRealOptimizer.java b/src/main/java/org/apache/commons/math/optimization/UnivariateRealOptimizer.java
new file mode 100644
index 0000000..df17133
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/UnivariateRealOptimizer.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.math.optimization;
+
+import org.apache.commons.math.ConvergenceException;
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.ConvergingAlgorithm;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+
+
+/**
+ * Interface for (univariate real) optimization algorithms.
+ *
+ * @version $Revision: 1073658 $ $Date: 2011-02-23 10:45:42 +0100 (mer. 23 févr. 2011) $
+ * @since 2.0
+ */
+public interface UnivariateRealOptimizer extends ConvergingAlgorithm {
+
+    /** Set the maximal number of functions evaluations.
+     * @param maxEvaluations maximal number of function evaluations
+     */
+    void setMaxEvaluations(int maxEvaluations);
+
+    /** Get the maximal number of functions evaluations.
+     * @return the maximal number of functions evaluations.
+     */
+    int getMaxEvaluations();
+
+    /** Get the number of evaluations of the objective function.
+     * <p>
+     * The number of evaluations corresponds to the last call to the
+     * {@link #optimize(UnivariateRealFunction, GoalType, double, double) optimize}
+     * method. It is 0 if the method has not been called yet.
+     * </p>
+     * @return the number of evaluations of the objective function.
+     */
+    int getEvaluations();
+
+    /**
+     * Find an optimum in the given interval.
+     * <p>
+     * An optimizer may require that the interval brackets a single optimum.
+     * </p>
+     * @param f the function to optimize.
+     * @param goalType type of optimization goal: either {@link GoalType#MAXIMIZE}
+     * or {@link GoalType#MINIMIZE}.
+     * @param min the lower bound for the interval.
+     * @param max the upper bound for the interval.
+     * @return a value where the function is optimum.
+     * @throws ConvergenceException if the maximum iteration count is exceeded
+     * or the optimizer detects convergence problems otherwise.
+     * @throws FunctionEvaluationException if an error occurs evaluating the function.
+     * @throws IllegalArgumentException if min > max or the endpoints do not
+     * satisfy the requirements specified by the optimizer.
+     */
+    double optimize(UnivariateRealFunction f, GoalType goalType,
+                    double min, double max)
+        throws ConvergenceException, FunctionEvaluationException;
+
+    /**
+     * Find an optimum in the given interval, start at startValue.
+     * <p>
+     * An optimizer may require that the interval brackets a single optimum.
+     * </p>
+     * @param f the function to optimize.
+     * @param goalType type of optimization goal: either {@link GoalType#MAXIMIZE}
+     * or {@link GoalType#MINIMIZE}.
+     * @param min the lower bound for the interval.
+     * @param max the upper bound for the interval.
+     * @param startValue the start value to use.
+     * @return a value where the function is optimum.
+     * @throws ConvergenceException if the maximum iteration count is exceeded
+     * or the optimizer detects convergence problems otherwise.
+     * @throws FunctionEvaluationException if an error occurs evaluating the function.
+     * @throws IllegalArgumentException if min > max or the arguments do not
+     * satisfy the requirements specified by the optimizer.
+     * @throws IllegalStateException if there are no data.
+     */
+    double optimize(UnivariateRealFunction f, GoalType goalType,
+                    double min, double max, double startValue)
+        throws ConvergenceException, FunctionEvaluationException;
+
+    /**
+     * Get the result of the last run of the optimizer.
+     *
+     * @return the optimum.
+     * @throws IllegalStateException if there is no result available, either
+     * because no result was yet computed or the last attempt failed.
+     */
+    double getResult();
+
+    /**
+     * Get the result of the last run of the optimizer.
+     *
+     * @return the value of the function at the optimum.
+     * @throws FunctionEvaluationException if an error occurs evaluating the function.
+     * @throws IllegalStateException if there is no result available, either
+     * because no result was yet computed or the last attempt failed.
+     */
+    double getFunctionValue() throws FunctionEvaluationException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/VectorialConvergenceChecker.java b/src/main/java/org/apache/commons/math/optimization/VectorialConvergenceChecker.java
new file mode 100644
index 0000000..e2befd6
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/VectorialConvergenceChecker.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.math.optimization;
+
+/** This interface specifies how to check if a {@link
+ * DifferentiableMultivariateVectorialOptimizer optimization algorithm} has converged.
+ *
+ * <p>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.</p>
+ * <p>For convenience, two implementations that fit simple needs are already provided:
+ * {@link SimpleVectorialValueChecker} and {@link SimpleVectorialPointChecker}. The first
+ * one considers convergence is reached when the objective function value does not
+ * change much anymore, it does not use the point set at all. The second one
+ * considers convergence is reached when the input point set does not change
+ * much anymore, it does not use objective function value at all.</p>
+ *
+ * @version $Revision: 780674 $ $Date: 2009-06-01 17:10:55 +0200 (lun. 01 juin 2009) $
+ * @since 2.0
+ */
+
+public interface VectorialConvergenceChecker {
+
+  /** Check if the optimization algorithm has converged considering the last points.
+   * <p>
+   * 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.
+   * </p>
+   * @param iteration index of current iteration
+   * @param previous point from previous iteration
+   * @param current point from current iteration
+   * @return true if the algorithm is considered to have converged
+   */
+  boolean converged(int iteration, VectorialPointValuePair previous, VectorialPointValuePair current);
+
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/VectorialPointValuePair.java b/src/main/java/org/apache/commons/math/optimization/VectorialPointValuePair.java
new file mode 100644
index 0000000..13f1134
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/VectorialPointValuePair.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.math.optimization;
+
+import java.io.Serializable;
+
+/**
+ * This class holds a point and the vectorial value of an objective function at this point.
+ * <p>This is a simple immutable container.</p>
+ * @see RealPointValuePair
+ * @see org.apache.commons.math.analysis.MultivariateVectorialFunction
+ * @version $Revision: 980981 $ $Date: 2010-07-31 00:03:04 +0200 (sam. 31 juil. 2010) $
+ * @since 2.0
+ */
+public class VectorialPointValuePair implements Serializable {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 1003888396256744753L;
+
+    /** Point coordinates. */
+    private final double[] point;
+
+    /** Vectorial value of the objective function at the point. */
+    private final double[] value;
+
+    /** Build a point/objective function value pair.
+     * @param point point coordinates (the built instance will store
+     * a copy of the array, not the array passed as argument)
+     * @param value value of an objective function at the point
+     */
+    public VectorialPointValuePair(final double[] point, final double[] value) {
+        this.point = (point == null) ? null : point.clone();
+        this.value = (value == null) ? null : value.clone();
+    }
+
+    /** Build a point/objective function value pair.
+     * @param point point coordinates (the built instance will store
+     * a copy of the array, not the array passed as argument)
+     * @param value value of an objective function at the point
+     * @param copyArray if true, the input arrays will be copied, otherwise
+     * they will be referenced
+     */
+    public VectorialPointValuePair(final double[] point, final double[] value,
+                                   final boolean copyArray) {
+        this.point = copyArray ?
+                      ((point == null) ? null : point.clone()) :
+                      point;
+        this.value = copyArray ?
+                      ((value == null) ? null : value.clone()) :
+                      value;
+    }
+
+    /** Get the point.
+     * @return a copy of the stored point
+     */
+    public double[] getPoint() {
+        return (point == null) ? null : point.clone();
+    }
+
+    /** Get a reference to the point.
+     * <p>This method is provided as a convenience to avoid copying
+     * the array, the elements of the array should <em>not</em> be modified.</p>
+     * @return a reference to the internal array storing the point
+     */
+    public double[] getPointRef() {
+        return point;
+    }
+
+    /** Get the value of the objective function.
+     * @return a copy of the stored value of the objective function
+     */
+    public double[] getValue() {
+        return (value == null) ? null : value.clone();
+    }
+
+    /** Get a reference to the value of the objective function.
+     * <p>This method is provided as a convenience to avoid copying
+     * the array, the elements of the array should <em>not</em> be modified.</p>
+     * @return a reference to the internal array storing the value of the objective function
+     */
+    public double[] getValueRef() {
+        return value;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/direct/DirectSearchOptimizer.java b/src/main/java/org/apache/commons/math/optimization/direct/DirectSearchOptimizer.java
new file mode 100644
index 0000000..3f9fac2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/direct/DirectSearchOptimizer.java
@@ -0,0 +1,418 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.optimization.direct;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.MaxEvaluationsExceededException;
+import org.apache.commons.math.MaxIterationsExceededException;
+import org.apache.commons.math.analysis.MultivariateRealFunction;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.optimization.GoalType;
+import org.apache.commons.math.optimization.MultivariateRealOptimizer;
+import org.apache.commons.math.optimization.OptimizationException;
+import org.apache.commons.math.optimization.RealConvergenceChecker;
+import org.apache.commons.math.optimization.RealPointValuePair;
+import org.apache.commons.math.optimization.SimpleScalarValueChecker;
+
+/**
+ * This class implements simplex-based direct search optimization
+ * algorithms.
+ *
+ * <p>Direct search methods only use objective function values, they don't
+ * 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 initial configuration of the simplex can be set using either
+ * {@link #setStartConfiguration(double[])} or {@link
+ * #setStartConfiguration(double[][])}. If neither method has been called
+ * before optimization is attempted, an explicit call to the first method
+ * with all steps set to +1 is triggered, thus building a default
+ * configuration from a unit hypercube. Each call to {@link
+ * #optimize(MultivariateRealFunction, GoalType, double[]) optimize} will reuse
+ * the current start configuration 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 start configuration will be reset to a default one with the
+ * appropriate dimensions.</p>
+ *
+ * <p>If {@link #setConvergenceChecker(RealConvergenceChecker)} is not called,
+ * a default {@link SimpleScalarValueChecker} is used.</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 class is the base class performing the boilerplate simplex
+ * initialization and handling. The simplex update by itself is
+ * performed by the derived classes according to the implemented
+ * algorithms.</p>
+ *
+ * implements MultivariateRealOptimizer since 2.0
+ *
+ * @see MultivariateRealFunction
+ * @see NelderMead
+ * @see MultiDirectional
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ * @since 1.2
+ */
+public abstract class DirectSearchOptimizer implements MultivariateRealOptimizer {
+
+    /** Simplex. */
+    protected RealPointValuePair[] simplex;
+
+    /** Objective function. */
+    private MultivariateRealFunction f;
+
+    /** Convergence checker. */
+    private RealConvergenceChecker checker;
+
+    /** Maximal number of iterations allowed. */
+    private int maxIterations;
+
+    /** Number of iterations already performed. */
+    private int iterations;
+
+    /** Maximal number of evaluations allowed. */
+    private int maxEvaluations;
+
+    /** Number of evaluations already performed. */
+    private int evaluations;
+
+    /** Start simplex configuration. */
+    private double[][] startConfiguration;
+
+    /** Simple constructor.
+     */
+    protected DirectSearchOptimizer() {
+        setConvergenceChecker(new SimpleScalarValueChecker());
+        setMaxIterations(Integer.MAX_VALUE);
+        setMaxEvaluations(Integer.MAX_VALUE);
+    }
+
+    /** Set start configuration for simplex.
+     * <p>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.</p>
+     * <p>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).</p>
+     * @param steps steps along the canonical axes representing box edges,
+     * they may be negative but not null
+     * @exception IllegalArgumentException if one step is null
+     */
+    public void setStartConfiguration(final double[] steps)
+        throws IllegalArgumentException {
+        // only the relative position of the n final vertices with respect
+        // to the first one are stored
+        final int n = steps.length;
+        startConfiguration = new double[n][n];
+        for (int i = 0; i < n; ++i) {
+            final double[] vertexI = startConfiguration[i];
+            for (int j = 0; j < i + 1; ++j) {
+                if (steps[j] == 0.0) {
+                    throw MathRuntimeException.createIllegalArgumentException(
+                          LocalizedFormats.EQUAL_VERTICES_IN_SIMPLEX, j, j + 1);
+                }
+                System.arraycopy(steps, 0, vertexI, 0, j + 1);
+            }
+        }
+    }
+
+    /** Set start configuration for simplex.
+     * <p>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.</p>
+     * @param referenceSimplex reference simplex
+     * @exception IllegalArgumentException if the reference simplex does not
+     * contain at least one point, or if there is a dimension mismatch
+     * in the reference simplex or if one of its vertices is duplicated
+     */
+    public void setStartConfiguration(final double[][] referenceSimplex)
+        throws IllegalArgumentException {
+
+        // only the relative position of the n final vertices with respect
+        // to the first one are stored
+        final int n = referenceSimplex.length - 1;
+        if (n < 0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.SIMPLEX_NEED_ONE_POINT);
+        }
+        startConfiguration = new double[n][n];
+        final double[] ref0 = referenceSimplex[0];
+
+        // vertices loop
+        for (int i = 0; i < n + 1; ++i) {
+
+            final double[] refI = referenceSimplex[i];
+
+            // safety checks
+            if (refI.length != n) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                      LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, refI.length, n);
+            }
+            for (int j = 0; j < i; ++j) {
+                final double[] refJ = referenceSimplex[j];
+                boolean allEquals = true;
+                for (int k = 0; k < n; ++k) {
+                    if (refI[k] != refJ[k]) {
+                        allEquals = false;
+                        break;
+                    }
+                }
+                if (allEquals) {
+                    throw MathRuntimeException.createIllegalArgumentException(
+                          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 < n; ++k) {
+                    confI[k] = refI[k] - ref0[k];
+                }
+            }
+
+        }
+
+    }
+
+    /** {@inheritDoc} */
+    public void setMaxIterations(int maxIterations) {
+        this.maxIterations = maxIterations;
+    }
+
+    /** {@inheritDoc} */
+    public int getMaxIterations() {
+        return maxIterations;
+    }
+
+    /** {@inheritDoc} */
+    public void setMaxEvaluations(int maxEvaluations) {
+        this.maxEvaluations = maxEvaluations;
+    }
+
+    /** {@inheritDoc} */
+    public int getMaxEvaluations() {
+        return maxEvaluations;
+    }
+
+    /** {@inheritDoc} */
+    public int getIterations() {
+        return iterations;
+    }
+
+    /** {@inheritDoc} */
+    public int getEvaluations() {
+        return evaluations;
+    }
+
+    /** {@inheritDoc} */
+    public void setConvergenceChecker(RealConvergenceChecker convergenceChecker) {
+        this.checker = convergenceChecker;
+    }
+
+    /** {@inheritDoc} */
+    public RealConvergenceChecker getConvergenceChecker() {
+        return checker;
+    }
+
+    /** {@inheritDoc} */
+    public RealPointValuePair optimize(final MultivariateRealFunction function,
+                                       final GoalType goalType,
+                                       final double[] startPoint)
+        throws FunctionEvaluationException, OptimizationException, IllegalArgumentException {
+
+        if ((startConfiguration == null) ||
+            (startConfiguration.length != startPoint.length)) {
+            // no initial configuration has been set up for simplex
+            // build a default one from a unit hypercube
+            final double[] unit = new double[startPoint.length];
+            Arrays.fill(unit, 1.0);
+            setStartConfiguration(unit);
+        }
+
+        this.f = function;
+        final Comparator<RealPointValuePair> comparator =
+            new Comparator<RealPointValuePair>() {
+                public int compare(final RealPointValuePair o1,
+                                   final RealPointValuePair o2) {
+                    final double v1 = o1.getValue();
+                    final double v2 = o2.getValue();
+                    return (goalType == GoalType.MINIMIZE) ?
+                            Double.compare(v1, v2) : Double.compare(v2, v1);
+                }
+            };
+
+        // initialize search
+        iterations  = 0;
+        evaluations = 0;
+        buildSimplex(startPoint);
+        evaluateSimplex(comparator);
+
+        RealPointValuePair[] previous = new RealPointValuePair[simplex.length];
+        while (true) {
+
+            if (iterations > 0) {
+                boolean converged = true;
+                for (int i = 0; i < simplex.length; ++i) {
+                    converged &= checker.converged(iterations, previous[i], simplex[i]);
+                }
+                if (converged) {
+                    // we have found an optimum
+                    return simplex[0];
+                }
+            }
+
+            // we still need to search
+            System.arraycopy(simplex, 0, previous, 0, simplex.length);
+            iterateSimplex(comparator);
+
+        }
+
+    }
+
+    /** Increment the iterations counter by 1.
+     * @exception OptimizationException if the maximal number
+     * of iterations is exceeded
+     */
+    protected void incrementIterationsCounter()
+        throws OptimizationException {
+        if (++iterations > maxIterations) {
+            throw new OptimizationException(new MaxIterationsExceededException(maxIterations));
+        }
+    }
+
+    /** Compute the next simplex of the algorithm.
+     * @param comparator comparator to use to sort simplex vertices from best to worst
+     * @exception FunctionEvaluationException if the function cannot be evaluated at
+     * some point
+     * @exception OptimizationException if the algorithm fails to converge
+     * @exception IllegalArgumentException if the start point dimension is wrong
+     */
+    protected abstract void iterateSimplex(final Comparator<RealPointValuePair> comparator)
+        throws FunctionEvaluationException, OptimizationException, IllegalArgumentException;
+
+    /** Evaluate the objective function on one point.
+     * <p>A side effect of this method is to count the number of
+     * function evaluations</p>
+     * @param x point on which the objective function should be evaluated
+     * @return objective function value at the given point
+     * @exception FunctionEvaluationException if no value can be computed for the
+     * parameters or if the maximal number of evaluations is exceeded
+     * @exception IllegalArgumentException if the start point dimension is wrong
+     */
+    protected double evaluate(final double[] x)
+        throws FunctionEvaluationException, IllegalArgumentException {
+        if (++evaluations > maxEvaluations) {
+            throw new FunctionEvaluationException(new MaxEvaluationsExceededException(maxEvaluations), x);
+        }
+        return f.value(x);
+    }
+
+    /** Build an initial simplex.
+     * @param startPoint the start point for optimization
+     * @exception IllegalArgumentException if the start point does not match
+     * simplex dimension
+     */
+    private void buildSimplex(final double[] startPoint)
+        throws IllegalArgumentException {
+
+        final int n = startPoint.length;
+        if (n != startConfiguration.length) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, n, startConfiguration.length);
+        }
+
+        // set first vertex
+        simplex = new RealPointValuePair[n + 1];
+        simplex[0] = new RealPointValuePair(startPoint, Double.NaN);
+
+        // set remaining vertices
+        for (int i = 0; i < n; ++i) {
+            final double[] confI   = startConfiguration[i];
+            final double[] vertexI = new double[n];
+            for (int k = 0; k < n; ++k) {
+                vertexI[k] = startPoint[k] + confI[k];
+            }
+            simplex[i + 1] = new RealPointValuePair(vertexI, Double.NaN);
+        }
+
+    }
+
+    /** Evaluate all the non-evaluated points of the simplex.
+     * @param comparator comparator to use to sort simplex vertices from best to worst
+     * @exception FunctionEvaluationException if no value can be computed for the parameters
+     * @exception OptimizationException if the maximal number of evaluations is exceeded
+     */
+    protected void evaluateSimplex(final Comparator<RealPointValuePair> comparator)
+        throws FunctionEvaluationException, OptimizationException {
+
+        // evaluate the objective function at all non-evaluated simplex points
+        for (int i = 0; i < simplex.length; ++i) {
+            final RealPointValuePair vertex = simplex[i];
+            final double[] point = vertex.getPointRef();
+            if (Double.isNaN(vertex.getValue())) {
+                simplex[i] = new RealPointValuePair(point, evaluate(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 to sort simplex vertices from best to worst
+     */
+    protected void replaceWorstPoint(RealPointValuePair pointValuePair,
+                                     final Comparator<RealPointValuePair> comparator) {
+        int n = simplex.length - 1;
+        for (int i = 0; i < n; ++i) {
+            if (comparator.compare(simplex[i], pointValuePair) > 0) {
+                RealPointValuePair tmp = simplex[i];
+                simplex[i]         = pointValuePair;
+                pointValuePair     = tmp;
+            }
+        }
+        simplex[n] = pointValuePair;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/direct/MultiDirectional.java b/src/main/java/org/apache/commons/math/optimization/direct/MultiDirectional.java
new file mode 100644
index 0000000..ea8816b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/direct/MultiDirectional.java
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.optimization.direct;
+
+import java.util.Comparator;
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.optimization.OptimizationException;
+import org.apache.commons.math.optimization.RealConvergenceChecker;
+import org.apache.commons.math.optimization.RealPointValuePair;
+
+/**
+ * This class implements the multi-directional direct search method.
+ *
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ * @see NelderMead
+ * @since 1.2
+ */
+public class MultiDirectional extends DirectSearchOptimizer {
+
+    /** Expansion coefficient. */
+    private final double khi;
+
+    /** Contraction coefficient. */
+    private final double gamma;
+
+    /** Build a multi-directional optimizer with default coefficients.
+     * <p>The default values are 2.0 for khi and 0.5 for gamma.</p>
+     */
+    public MultiDirectional() {
+        this.khi   = 2.0;
+        this.gamma = 0.5;
+    }
+
+    /** Build a multi-directional optimizer with specified coefficients.
+     * @param khi expansion coefficient
+     * @param gamma contraction coefficient
+     */
+    public MultiDirectional(final double khi, final double gamma) {
+        this.khi   = khi;
+        this.gamma = gamma;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void iterateSimplex(final Comparator<RealPointValuePair> comparator)
+        throws FunctionEvaluationException, OptimizationException, IllegalArgumentException {
+
+        final RealConvergenceChecker checker = getConvergenceChecker();
+        while (true) {
+
+            incrementIterationsCounter();
+
+            // save the original vertex
+            final RealPointValuePair[] original = simplex;
+            final RealPointValuePair best = original[0];
+
+            // perform a reflection step
+            final RealPointValuePair reflected = evaluateNewSimplex(original, 1.0, comparator);
+            if (comparator.compare(reflected, best) < 0) {
+
+                // compute the expanded simplex
+                final RealPointValuePair[] reflectedSimplex = simplex;
+                final RealPointValuePair expanded = evaluateNewSimplex(original, khi, comparator);
+                if (comparator.compare(reflected, expanded) <= 0) {
+                    // accept the reflected simplex
+                    simplex = reflectedSimplex;
+                }
+
+                return;
+
+            }
+
+            // compute the contracted simplex
+            final RealPointValuePair contracted = evaluateNewSimplex(original, gamma, comparator);
+            if (comparator.compare(contracted, best) < 0) {
+                // accept the contracted simplex
+                return;
+            }
+
+            // check convergence
+            final int iter = getIterations();
+            boolean converged = true;
+            for (int i = 0; i < simplex.length; ++i) {
+                converged &= checker.converged(iter, original[i], simplex[i]);
+            }
+            if (converged) {
+                return;
+            }
+
+        }
+
+    }
+
+    /** Compute and evaluate a new simplex.
+     * @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 best point in the transformed simplex
+     * @exception FunctionEvaluationException if the function cannot be evaluated at some point
+     * @exception OptimizationException if the maximal number of evaluations is exceeded
+     */
+    private RealPointValuePair evaluateNewSimplex(final RealPointValuePair[] original,
+                                              final double coeff,
+                                              final Comparator<RealPointValuePair> comparator)
+        throws FunctionEvaluationException, OptimizationException {
+
+        final double[] xSmallest = original[0].getPointRef();
+        final int n = xSmallest.length;
+
+        // create the linearly transformed simplex
+        simplex = new RealPointValuePair[n + 1];
+        simplex[0] = original[0];
+        for (int i = 1; i <= n; ++i) {
+            final double[] xOriginal    = original[i].getPointRef();
+            final double[] xTransformed = new double[n];
+            for (int j = 0; j < n; ++j) {
+                xTransformed[j] = xSmallest[j] + coeff * (xSmallest[j] - xOriginal[j]);
+            }
+            simplex[i] = new RealPointValuePair(xTransformed, Double.NaN, false);
+        }
+
+        // evaluate it
+        evaluateSimplex(comparator);
+        return simplex[0];
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/direct/NelderMead.java b/src/main/java/org/apache/commons/math/optimization/direct/NelderMead.java
new file mode 100644
index 0000000..8b8b205
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/direct/NelderMead.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.math.optimization.direct;
+
+import java.util.Comparator;
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.optimization.OptimizationException;
+import org.apache.commons.math.optimization.RealPointValuePair;
+
+/**
+ * This class implements the Nelder-Mead direct search method.
+ *
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ * @see MultiDirectional
+ * @since 1.2
+ */
+public class NelderMead extends DirectSearchOptimizer {
+
+    /** 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 optimizer with default coefficients.
+     * <p>The default coefficients are 1.0 for rho, 2.0 for khi and 0.5
+     * for both gamma and sigma.</p>
+     */
+    public NelderMead() {
+        this.rho   = 1.0;
+        this.khi   = 2.0;
+        this.gamma = 0.5;
+        this.sigma = 0.5;
+    }
+
+    /** Build a Nelder-Mead optimizer with specified coefficients.
+     * @param rho reflection coefficient
+     * @param khi expansion coefficient
+     * @param gamma contraction coefficient
+     * @param sigma shrinkage coefficient
+     */
+    public NelderMead(final double rho, final double khi,
+                      final double gamma, final double sigma) {
+        this.rho   = rho;
+        this.khi   = khi;
+        this.gamma = gamma;
+        this.sigma = sigma;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected void iterateSimplex(final Comparator<RealPointValuePair> comparator)
+        throws FunctionEvaluationException, OptimizationException {
+
+        incrementIterationsCounter();
+
+        // the simplex has n+1 point if dimension is n
+        final int n = simplex.length - 1;
+
+        // interesting values
+        final RealPointValuePair best       = simplex[0];
+        final RealPointValuePair secondBest = simplex[n-1];
+        final RealPointValuePair worst      = simplex[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 = simplex[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 RealPointValuePair reflected = new RealPointValuePair(xR, evaluate(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 RealPointValuePair expanded = new RealPointValuePair(xE, evaluate(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 RealPointValuePair outContracted = new RealPointValuePair(xC, evaluate(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 RealPointValuePair inContracted = new RealPointValuePair(xC, evaluate(xC), false);
+
+                if (comparator.compare(inContracted, worst) < 0) {
+                    // accept the contraction point
+                    replaceWorstPoint(inContracted, comparator);
+                    return;
+                }
+
+            }
+
+            // perform a shrink
+            final double[] xSmallest = simplex[0].getPointRef();
+            for (int i = 1; i < simplex.length; ++i) {
+                final double[] x = simplex[i].getPoint();
+                for (int j = 0; j < n; ++j) {
+                    x[j] = xSmallest[j] + sigma * (x[j] - xSmallest[j]);
+                }
+                simplex[i] = new RealPointValuePair(x, Double.NaN, false);
+            }
+            evaluateSimplex(comparator);
+
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/direct/PowellOptimizer.java b/src/main/java/org/apache/commons/math/optimization/direct/PowellOptimizer.java
new file mode 100644
index 0000000..6332951
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/direct/PowellOptimizer.java
@@ -0,0 +1,298 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.optimization.direct;
+
+import org.apache.commons.math.MaxIterationsExceededException;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.optimization.GoalType;
+import org.apache.commons.math.optimization.OptimizationException;
+import org.apache.commons.math.optimization.RealPointValuePair;
+import org.apache.commons.math.optimization.general.AbstractScalarDifferentiableOptimizer;
+import org.apache.commons.math.optimization.univariate.AbstractUnivariateRealOptimizer;
+import org.apache.commons.math.optimization.univariate.BracketFinder;
+import org.apache.commons.math.optimization.univariate.BrentOptimizer;
+
+/**
+ * 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>).
+ *
+ * @version $Revision$ $Date$
+ * @since 2.2
+ */
+public class PowellOptimizer
+    extends AbstractScalarDifferentiableOptimizer {
+    /**
+     * Default relative tolerance for line search ({@value}).
+     */
+    public static final double DEFAULT_LS_RELATIVE_TOLERANCE = 1e-7;
+    /**
+     * Default absolute tolerance for line search ({@value}).
+     */
+    public static final double DEFAULT_LS_ABSOLUTE_TOLERANCE = 1e-11;
+    /**
+     * Line search.
+     */
+    private final LineSearch line;
+
+    /**
+     * Constructor with default line search tolerances (see the
+     * {@link #PowellOptimizer(double,double) other constructor}).
+     */
+    public PowellOptimizer() {
+        this(DEFAULT_LS_RELATIVE_TOLERANCE,
+             DEFAULT_LS_ABSOLUTE_TOLERANCE);
+    }
+
+    /**
+     * Constructor with default absolute line search tolerances (see
+     * the {@link #PowellOptimizer(double,double) other constructor}).
+     *
+     * @param lsRelativeTolerance Relative error tolerance for
+     * the line search algorithm ({@link BrentOptimizer}).
+     */
+    public PowellOptimizer(double lsRelativeTolerance) {
+        this(lsRelativeTolerance,
+             DEFAULT_LS_ABSOLUTE_TOLERANCE);
+    }
+
+    /**
+     * @param lsRelativeTolerance Relative error tolerance for
+     * the line search algorithm ({@link BrentOptimizer}).
+     * @param lsAbsoluteTolerance Relative error tolerance for
+     * the line search algorithm ({@link BrentOptimizer}).
+     */
+    public PowellOptimizer(double lsRelativeTolerance,
+                           double lsAbsoluteTolerance) {
+        line = new LineSearch(lsRelativeTolerance,
+                              lsAbsoluteTolerance);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected RealPointValuePair doOptimize()
+        throws FunctionEvaluationException,
+               OptimizationException {
+        final double[] guess = point.clone();
+        final int n = guess.length;
+
+        final double[][] direc = new double[n][n];
+        for (int i = 0; i < n; i++) {
+            direc[i][i] = 1;
+        }
+
+        double[] x = guess;
+        double fVal = computeObjectiveValue(x);
+        double[] x1 = x.clone();
+        while (true) {
+            incrementIterationsCounter();
+
+            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 = /* Arrays.*/ copyOf(direc[i], n); // Java 1.5 does not support Arrays.copyOf()
+
+                fX2 = fVal;
+
+                line.search(x, d);
+                fVal = line.getValueAtOptimum();
+                alphaMin = line.getOptimum();
+                final double[][] result = newPointAndDirection(x, d, alphaMin);
+                x = result[0];
+
+                if ((fX2 - fVal) > delta) {
+                    delta = fX2 - fVal;
+                    bigInd = i;
+                }
+            }
+
+            final RealPointValuePair previous = new RealPointValuePair(x1, fX);
+            final RealPointValuePair current = new RealPointValuePair(x, fVal);
+            if (getConvergenceChecker().converged(getIterations(), previous, current)) {
+                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) {
+                    line.search(x, d);
+                    fVal = line.getValueAtOptimum();
+                    alphaMin = line.getOptimum();
+                    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.
+     * The parameters {@code p} and {@code d} will be changed in-place.
+     *
+     * @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[][] result = new double[2][n];
+        final double[] nP = result[0];
+        final double[] nD = result[1];
+        for (int i = 0; i < n; i++) {
+            nD[i] = d[i] * optimum;
+            nP[i] = p[i] + nD[i];
+        }
+        return result;
+    }
+
+    /**
+     * Class for finding the minimum of the objective function along a given
+     * direction.
+     */
+    private class LineSearch {
+        /**
+         * Optimizer.
+         */
+        private final AbstractUnivariateRealOptimizer optim = new BrentOptimizer();
+        /**
+         * Automatic bracketing.
+         */
+        private final BracketFinder bracket = new BracketFinder();
+        /**
+         * Value of the optimum.
+         */
+        private double optimum = Double.NaN;
+        /**
+         * Value of the objective function at the optimum.
+         */
+        private double valueAtOptimum = Double.NaN;
+
+        /**
+         * @param relativeTolerance Relative tolerance.
+         * @param absoluteTolerance Absolute tolerance.
+         */
+        public LineSearch(double relativeTolerance,
+                          double absoluteTolerance) {
+            optim.setRelativeAccuracy(relativeTolerance);
+            optim.setAbsoluteAccuracy(absoluteTolerance);
+        }
+
+        /**
+         * Find the minimum of the function {@code f(p + alpha * d)}.
+         *
+         * @param p Starting point.
+         * @param d Search direction.
+         * @throws FunctionEvaluationException if function cannot be evaluated at some test point
+         * @throws OptimizationException if algorithm fails to converge
+         */
+        public void search(final double[] p, final double[] d)
+            throws OptimizationException, FunctionEvaluationException {
+
+            // Reset.
+            optimum = Double.NaN;
+            valueAtOptimum = Double.NaN;
+
+            try {
+                final int n = p.length;
+                final UnivariateRealFunction f = new UnivariateRealFunction() {
+                        public double value(double alpha)
+                            throws FunctionEvaluationException {
+
+                            final double[] x = new double[n];
+                            for (int i = 0; i < n; i++) {
+                                x[i] = p[i] + alpha * d[i];
+                            }
+                            final double obj;
+                            obj = computeObjectiveValue(x);
+                            return obj;
+                        }
+                    };
+
+                bracket.search(f, goal, 0, 1);
+                optimum = optim.optimize(f, goal,
+                                         bracket.getLo(),
+                                         bracket.getHi(),
+                                         bracket.getMid());
+                valueAtOptimum = optim.getFunctionValue();
+            } catch (MaxIterationsExceededException e) {
+                throw new OptimizationException(e);
+            }
+        }
+
+        /**
+         * @return the optimum.
+         */
+        public double getOptimum() {
+            return optimum;
+        }
+        /**
+         * @return the value of the function at the optimum.
+         */
+        public double getValueAtOptimum() {
+            return valueAtOptimum;
+        }
+    }
+
+    /**
+     * Java 1.5 does not support Arrays.copyOf()
+     *
+     * @param source the array to be copied
+     * @param newLen the length of the copy to be returned
+     * @return the copied array, truncated or padded as necessary.
+     */
+     private double[] copyOf(double[] source, int newLen) {
+         double[] output = new double[newLen];
+         System.arraycopy(source, 0, output, 0, Math.min(source.length, newLen));
+         return output;
+     }
+
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/direct/package.html b/src/main/java/org/apache/commons/math/optimization/direct/package.html
new file mode 100644
index 0000000..8e9c48e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/direct/package.html
@@ -0,0 +1,24 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 748274 $ -->
+<body>
+<p>
+This package provides optimization algorithms that don't require derivatives.
+</p>
+</body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/optimization/fitting/CurveFitter.java b/src/main/java/org/apache/commons/math/optimization/fitting/CurveFitter.java
new file mode 100644
index 0000000..9bb70d1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/fitting/CurveFitter.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.math.optimization.fitting;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.math.analysis.DifferentiableMultivariateVectorialFunction;
+import org.apache.commons.math.analysis.MultivariateMatrixFunction;
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.optimization.DifferentiableMultivariateVectorialOptimizer;
+import org.apache.commons.math.optimization.OptimizationException;
+import org.apache.commons.math.optimization.VectorialPointValuePair;
+
+/** Fitter for parametric univariate real functions y = f(x).
+ * <p>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.</p>
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @since 2.0
+ */
+public class CurveFitter {
+
+    /** Optimizer to use for the fitting. */
+    private final DifferentiableMultivariateVectorialOptimizer optimizer;
+
+    /** Observed points. */
+    private final List<WeightedObservedPoint> observations;
+
+    /** Simple constructor.
+     * @param optimizer optimizer to use for the fitting
+     */
+    public CurveFitter(final DifferentiableMultivariateVectorialOptimizer 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)</code>.</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.
+     * <p>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.</p>
+     * @param f parametric function to fit
+     * @param initialGuess first guess of the function parameters
+     * @return fitted parameters
+     * @exception FunctionEvaluationException if the objective function throws one during the search
+     * @exception OptimizationException if the algorithm failed to converge
+     * @exception IllegalArgumentException if the start point dimension is wrong
+     */
+    public double[] fit(final ParametricRealFunction f,
+                        final double[] initialGuess)
+        throws FunctionEvaluationException, OptimizationException, IllegalArgumentException {
+
+        // 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
+        VectorialPointValuePair optimum =
+            optimizer.optimize(new TheoreticalValuesFunction(f), target, weights, initialGuess);
+
+        // extract the coefficients
+        return optimum.getPointRef();
+
+    }
+
+    /** Vectorial function computing function theoretical values. */
+    private class TheoreticalValuesFunction
+        implements DifferentiableMultivariateVectorialFunction {
+
+        /** Function to fit. */
+        private final ParametricRealFunction f;
+
+        /** Simple constructor.
+         * @param f function to fit.
+         */
+        public TheoreticalValuesFunction(final ParametricRealFunction f) {
+            this.f = f;
+        }
+
+        /** {@inheritDoc} */
+        public MultivariateMatrixFunction jacobian() {
+            return new MultivariateMatrixFunction() {
+                public double[][] value(double[] point)
+                    throws FunctionEvaluationException, IllegalArgumentException {
+
+                    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) throws FunctionEvaluationException, IllegalArgumentException {
+
+            // 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;
+
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/fitting/GaussianDerivativeFunction.java b/src/main/java/org/apache/commons/math/optimization/fitting/GaussianDerivativeFunction.java
new file mode 100644
index 0000000..7112d17
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/fitting/GaussianDerivativeFunction.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.math.optimization.fitting;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.exception.DimensionMismatchException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.exception.ZeroException;
+import org.apache.commons.math.exception.NullArgumentException;
+
+/**
+ * The derivative of {@link GaussianFunction}.  Specifically:
+ * <p>
+ * <tt>f'(x) = (-b / (d^2)) * (x - c) * exp(-((x - c)^2) / (2*(d^2)))</tt>
+ * <p>
+ * Notation key:
+ * <ul>
+ * <li><tt>x^n</tt>: <tt>x</tt> raised to the power of <tt>n</tt>
+ * <li><tt>exp(x)</tt>: <i>e</i><tt>^x</tt>
+ * </ul>
+ *
+ * @since 2.2
+ * @version $Revision: 1037327 $ $Date: 2010-11-20 21:57:37 +0100 (sam. 20 nov. 2010) $
+ */
+public class GaussianDerivativeFunction implements UnivariateRealFunction, Serializable {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -6500229089670174766L;
+
+    /** Parameter b of this function. */
+    private final double b;
+
+    /** Parameter c of this function. */
+    private final double c;
+
+    /** Square of the parameter d of this function. */
+    private final double d2;
+
+    /**
+     * Constructs an instance with the specified parameters.
+     *
+     * @param b <tt>b</tt> parameter value
+     * @param c <tt>c</tt> parameter value
+     * @param d <tt>d</tt> parameter value
+     *
+     * @throws IllegalArgumentException if <code>d</code> is 0
+     */
+    public GaussianDerivativeFunction(double b, double c, double d) {
+        if (d == 0.0) {
+            throw new ZeroException();
+        }
+        this.b = b;
+        this.c = c;
+        this.d2 = d * d;
+    }
+
+    /**
+     * Constructs an instance with the specified parameters.
+     *
+     * @param parameters <tt>b</tt>, <tt>c</tt>, and <tt>d</tt> parameter values
+     *
+     * @throws IllegalArgumentException if <code>parameters</code> is null,
+     *         <code>parameters</code> length is not 3, or if
+     *         <code>parameters[2]</code> is 0
+     */
+    public GaussianDerivativeFunction(double[] parameters) {
+        if (parameters == null) {
+            throw new NullArgumentException(LocalizedFormats.INPUT_ARRAY);
+        }
+        if (parameters.length != 3) {
+            throw new DimensionMismatchException(3, parameters.length);
+        }
+        if (parameters[2] == 0.0) {
+            throw new ZeroException();
+        }
+        this.b = parameters[0];
+        this.c = parameters[1];
+        this.d2 = parameters[2] * parameters[2];
+    }
+
+    /** {@inheritDoc} */
+    public double value(double x) {
+        final double xMc = x - c;
+        return (-b / d2) * xMc * Math.exp(-(xMc * xMc) / (2.0 * d2));
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/fitting/GaussianFitter.java b/src/main/java/org/apache/commons/math/optimization/fitting/GaussianFitter.java
new file mode 100644
index 0000000..6bc9f22
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/fitting/GaussianFitter.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.math.optimization.fitting;
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.optimization.DifferentiableMultivariateVectorialOptimizer;
+import org.apache.commons.math.optimization.OptimizationException;
+import org.apache.commons.math.optimization.fitting.CurveFitter;
+import org.apache.commons.math.optimization.fitting.WeightedObservedPoint;
+
+/**
+ * Fits points to a Gaussian function (that is, a {@link GaussianFunction}).
+ * <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);
+ *  GaussianFunction fitFunction = fitter.fit();
+ * </pre>
+ *
+ * @see ParametricGaussianFunction
+ * @since 2.2
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ */
+public class GaussianFitter {
+
+    /** Fitter used for fitting. */
+    private final CurveFitter fitter;
+
+    /**
+     * Constructs an instance using the specified optimizer.
+     *
+     * @param optimizer optimizer to use for the fitting
+     */
+    public GaussianFitter(DifferentiableMultivariateVectorialOptimizer optimizer) {
+        fitter = new CurveFitter(optimizer);
+    }
+
+    /**
+     * Adds point (<code>x</code>, <code>y</code>) to list of observed points
+     * with a weight of 1.0.
+     *
+     * @param x <tt>x</tt> point value
+     * @param y <tt>y</tt> point value
+     */
+    public void addObservedPoint(double x, double y) {
+        addObservedPoint(1.0, x, y);
+    }
+
+    /**
+     * Adds point (<code>x</code>, <code>y</code>) to list of observed points
+     * with a weight of <code>weight</code>.
+     *
+     * @param weight weight assigned to point
+     * @param x <tt>x</tt> point value
+     * @param y <tt>y</tt> point value
+     */
+    public void addObservedPoint(double weight, double x, double y) {
+        fitter.addObservedPoint(weight, x, y);
+    }
+
+    /**
+     * Fits Gaussian function to the observed points.
+     *
+     * @return Gaussian function best fitting the observed points
+     *
+     * @throws FunctionEvaluationException if <code>CurveFitter.fit</code> throws it
+     * @throws OptimizationException if <code>CurveFitter.fit</code> throws it
+     * @throws IllegalArgumentException if <code>CurveFitter.fit</code> throws it
+     *
+     * @see CurveFitter
+     */
+    public GaussianFunction fit() throws FunctionEvaluationException, OptimizationException {
+        return new GaussianFunction(fitter.fit(new ParametricGaussianFunction(),
+                                               createParametersGuesser(fitter.getObservations()).guess()));
+    }
+
+    /**
+     * Factory method to create a <code>GaussianParametersGuesser</code>
+     * instance initialized with the specified observations.
+     *
+     * @param observations points used to initialize the created
+     *        <code>GaussianParametersGuesser</code> instance
+     *
+     * @return new <code>GaussianParametersGuesser</code> instance
+     */
+    protected GaussianParametersGuesser createParametersGuesser(WeightedObservedPoint[] observations) {
+        return new GaussianParametersGuesser(observations);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/fitting/GaussianFunction.java b/src/main/java/org/apache/commons/math/optimization/fitting/GaussianFunction.java
new file mode 100644
index 0000000..06a0391
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/fitting/GaussianFunction.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.math.optimization.fitting;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.analysis.DifferentiableUnivariateRealFunction;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.exception.DimensionMismatchException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.exception.ZeroException;
+import org.apache.commons.math.exception.NullArgumentException;
+
+/**
+ * A Gaussian function.  Specifically:
+ * <p>
+ * <tt>f(x) = a + b*exp(-((x - c)^2 / (2*d^2)))</tt>
+ * <p>
+ * Notation key:
+ * <ul>
+ * <li><tt>x^n</tt>: <tt>x</tt> raised to the power of <tt>n</tt>
+ * <li><tt>exp(x)</tt>: <i>e</i><tt>^x</tt>
+ * </ul>
+ * References:
+ * <ul>
+ * <li><a href="http://en.wikipedia.org/wiki/Gaussian_function">Wikipedia:
+ *   Gaussian function</a>
+ * </ul>
+ *
+ * @see GaussianDerivativeFunction
+ * @see ParametricGaussianFunction
+ * @since 2.2
+ * @version $Revision: 1037327 $ $Date: 2010-11-20 21:57:37 +0100 (sam. 20 nov. 2010) $
+ */
+public class GaussianFunction implements DifferentiableUnivariateRealFunction, Serializable {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -3195385616125629512L;
+
+    /** Parameter a of this function. */
+    private final double a;
+
+    /** Parameter b of this function. */
+    private final double b;
+
+    /** Parameter c of this function. */
+    private final double c;
+
+    /** Parameter d of this function. */
+    private final double d;
+
+    /**
+     * Constructs an instance with the specified parameters.
+     *
+     * @param a <tt>a</tt> parameter value
+     * @param b <tt>b</tt> parameter value
+     * @param c <tt>c</tt> parameter value
+     * @param d <tt>d</tt> parameter value
+     *
+     * @throws IllegalArgumentException if <code>d</code> is 0
+     */
+    public GaussianFunction(double a, double b, double c, double d) {
+        if (d == 0.0) {
+            throw new ZeroException();
+        }
+        this.a = a;
+        this.b = b;
+        this.c = c;
+        this.d = d;
+    }
+
+    /**
+     * Constructs an instance with the specified parameters.
+     *
+     * @param parameters <tt>a</tt>, <tt>b</tt>, <tt>c</tt>, and <tt>d</tt>
+     *        parameter values
+     *
+     * @throws IllegalArgumentException if <code>parameters</code> is null,
+     *         <code>parameters</code> length is not 4, or if
+     *         <code>parameters[3]</code> is 0
+     */
+    public GaussianFunction(double[] parameters) {
+        if (parameters == null) {
+            throw new NullArgumentException(LocalizedFormats.INPUT_ARRAY);
+        }
+        if (parameters.length != 4) {
+            throw new DimensionMismatchException(4, parameters.length);
+        }
+        if (parameters[3] == 0.0) {
+            throw new ZeroException();
+        }
+        this.a = parameters[0];
+        this.b = parameters[1];
+        this.c = parameters[2];
+        this.d = parameters[3];
+    }
+
+    /** {@inheritDoc} */
+    public UnivariateRealFunction derivative() {
+        return new GaussianDerivativeFunction(b, c, d);
+    }
+
+    /** {@inheritDoc} */
+    public double value(double x) {
+        final double xMc = x - c;
+        return a + b * Math.exp(-xMc * xMc / (2.0 * (d * d)));
+    }
+
+    /**
+     * Gets <tt>a</tt> parameter value.
+     *
+     * @return <tt>a</tt> parameter value
+     */
+    public double getA() {
+        return a;
+    }
+
+    /**
+     * Gets <tt>b</tt> parameter value.
+     *
+     * @return <tt>b</tt> parameter value
+     */
+    public double getB() {
+        return b;
+    }
+
+    /**
+     * Gets <tt>c</tt> parameter value.
+     *
+     * @return <tt>c</tt> parameter value
+     */
+    public double getC() {
+        return c;
+    }
+
+    /**
+     * Gets <tt>d</tt> parameter value.
+     *
+     * @return <tt>d</tt> parameter value
+     */
+    public double getD() {
+        return d;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/fitting/GaussianParametersGuesser.java b/src/main/java/org/apache/commons/math/optimization/fitting/GaussianParametersGuesser.java
new file mode 100644
index 0000000..3fb3698
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/fitting/GaussianParametersGuesser.java
@@ -0,0 +1,271 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.optimization.fitting;
+
+import java.util.Arrays;
+import java.util.Comparator;
+
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.exception.NumberIsTooSmallException;
+import org.apache.commons.math.exception.OutOfRangeException;
+import org.apache.commons.math.exception.ZeroException;
+import org.apache.commons.math.exception.NullArgumentException;
+
+/**
+ * Guesses the parameters ({@code a}, {@code b}, {@code c}, and {@code d})
+ * of a {@link ParametricGaussianFunction} based on the specified observed
+ * points.
+ *
+ * @since 2.2
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ */
+public class GaussianParametersGuesser {
+
+    /** Observed points. */
+    private final WeightedObservedPoint[] observations;
+
+    /** Resulting guessed parameters. */
+    private double[] parameters;
+
+    /**
+     * Constructs instance with the specified observed points.
+     *
+     * @param observations observed points upon which should base guess
+     */
+    public GaussianParametersGuesser(WeightedObservedPoint[] observations) {
+        if (observations == null) {
+            throw new NullArgumentException(LocalizedFormats.INPUT_ARRAY);
+        }
+        if (observations.length < 3) {
+            throw new NumberIsTooSmallException(observations.length, 3, true);
+        }
+        this.observations = observations.clone();
+    }
+
+    /**
+     * Guesses the parameters based on the observed points.
+     *
+     * @return guessed parameters array <code>{a, b, c, d}</code>
+     */
+    public double[] guess() {
+        if (parameters == null) {
+            parameters = basicGuess(observations);
+        }
+        return parameters.clone();
+    }
+
+    /**
+     * Guesses the parameters based on the specified observed points.
+     *
+     * @param points observed points upon which should base guess
+     *
+     * @return guessed parameters array <code>{a, b, c, d}</code>
+     */
+    private double[] basicGuess(WeightedObservedPoint[] points) {
+        Arrays.sort(points, createWeightedObservedPointComparator());
+        double[] params = new double[4];
+
+        int minYIdx = findMinY(points);
+        params[0] = points[minYIdx].getY();
+
+        int maxYIdx = findMaxY(points);
+        params[1] = points[maxYIdx].getY();
+        params[2] = points[maxYIdx].getX();
+
+        double fwhmApprox;
+        try {
+            double halfY = params[0] + ((params[1] - params[0]) / 2.0);
+            double fwhmX1 = interpolateXAtY(points, maxYIdx, -1, halfY);
+            double fwhmX2 = interpolateXAtY(points, maxYIdx, +1, halfY);
+            fwhmApprox = fwhmX2 - fwhmX1;
+        } catch (OutOfRangeException e) {
+            fwhmApprox = points[points.length - 1].getX() - points[0].getX();
+        }
+        params[3] = fwhmApprox / (2.0 * Math.sqrt(2.0 * Math.log(2.0)));
+
+        return params;
+    }
+
+    /**
+     * Finds index of point in specified points with the smallest Y.
+     *
+     * @param points points to search
+     *
+     * @return index in specified points array
+     */
+    private int findMinY(WeightedObservedPoint[] points) {
+        int minYIdx = 0;
+        for (int i = 1; i < points.length; i++) {
+            if (points[i].getY() < points[minYIdx].getY()) {
+                minYIdx = i;
+            }
+        }
+        return minYIdx;
+    }
+
+    /**
+     * Finds index of point in specified points with the largest Y.
+     *
+     * @param points points to search
+     *
+     * @return 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 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 value of X at the specified Y
+     *
+     * @throws IllegalArgumentException if idxStep is 0
+     * @throws OutOfRangeException if specified <code>y</code> is not within the
+     *         range of the specified <code>points</code>
+     */
+    private double interpolateXAtY(WeightedObservedPoint[] points,
+                                   int startIdx, int idxStep, double y) throws OutOfRangeException {
+        if (idxStep == 0) {
+            throw new ZeroException();
+        }
+        WeightedObservedPoint[] twoPoints = getInterpolationPointsForY(points, startIdx, idxStep, y);
+        WeightedObservedPoint pointA = twoPoints[0];
+        WeightedObservedPoint pointB = twoPoints[1];
+        if (pointA.getY() == y) {
+            return pointA.getX();
+        }
+        if (pointB.getY() == y) {
+            return pointB.getX();
+        }
+        return pointA.getX() +
+               (((y - pointA.getY()) * (pointB.getX() - pointA.getX())) / (pointB.getY() - pointA.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 array containing two points suitable for determining X at the
+     *         specified Y
+     *
+     * @throws IllegalArgumentException if idxStep is 0
+     * @throws OutOfRangeException if specified <code>y</code> is not within the
+     *         range of the specified <code>points</code>
+     */
+    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) {
+            if (isBetween(y, points[i].getY(), points[i + idxStep].getY())) {
+                return (idxStep < 0) ?
+                       new WeightedObservedPoint[] { points[i + idxStep], points[i] } :
+                       new WeightedObservedPoint[] { points[i], points[i + idxStep] };
+            }
+        }
+
+        double minY = Double.POSITIVE_INFINITY;
+        double maxY = Double.NEGATIVE_INFINITY;
+        for (final WeightedObservedPoint point : points) {
+            minY = Math.min(minY, point.getY());
+            maxY = Math.max(maxY, point.getY());
+        }
+        throw new OutOfRangeException(y, minY, maxY);
+
+    }
+
+    /**
+     * Determines whether a value is between two other values.
+     *
+     * @param value value to determine whether is between <code>boundary1</code>
+     *        and <code>boundary2</code>
+     * @param boundary1 one end of the range
+     * @param boundary2 other end of the range
+     *
+     * @return true if <code>value</code> is between <code>boundary1</code> and
+     *         <code>boundary2</code> (inclusive); false otherwise
+     */
+    private boolean isBetween(double value, double boundary1, double boundary2) {
+        return (value >= boundary1 && value <= boundary2) ||
+               (value >= boundary2 && value <= boundary1);
+    }
+
+    /**
+     * Factory method creating <code>Comparator</code> for comparing
+     * <code>WeightedObservedPoint</code> instances.
+     *
+     * @return new <code>Comparator</code> instance
+     */
+    private Comparator<WeightedObservedPoint> createWeightedObservedPointComparator() {
+        return new Comparator<WeightedObservedPoint>() {
+            public int compare(WeightedObservedPoint p1, WeightedObservedPoint p2) {
+                if (p1 == null && p2 == null) {
+                    return 0;
+                }
+                if (p1 == null) {
+                    return -1;
+                }
+                if (p2 == null) {
+                    return 1;
+                }
+                if (p1.getX() < p2.getX()) {
+                    return -1;
+                }
+                if (p1.getX() > p2.getX()) {
+                    return 1;
+                }
+                if (p1.getY() < p2.getY()) {
+                    return -1;
+                }
+                if (p1.getY() > p2.getY()) {
+                    return 1;
+                }
+                if (p1.getWeight() < p2.getWeight()) {
+                    return -1;
+                }
+                if (p1.getWeight() > p2.getWeight()) {
+                    return 1;
+                }
+                return 0;
+            }
+        };
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/fitting/HarmonicCoefficientsGuesser.java b/src/main/java/org/apache/commons/math/optimization/fitting/HarmonicCoefficientsGuesser.java
new file mode 100644
index 0000000..0796f67
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/fitting/HarmonicCoefficientsGuesser.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.math.optimization.fitting;
+
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.optimization.OptimizationException;
+import org.apache.commons.math.util.FastMath;
+
+/** 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>
+
+ * @version $Revision: 1056034 $ $Date: 2011-01-06 20:41:43 +0100 (jeu. 06 janv. 2011) $
+ * @since 2.0
+
+ */
+public class HarmonicCoefficientsGuesser {
+
+    /** Sampled observations. */
+    private final WeightedObservedPoint[] observations;
+
+    /** Guessed amplitude. */
+    private double a;
+
+    /** Guessed pulsation &omega;. */
+    private double omega;
+
+    /** Guessed phase &phi;. */
+    private double phi;
+
+    /** Simple constructor.
+     * @param observations sampled observations
+     */
+    public HarmonicCoefficientsGuesser(WeightedObservedPoint[] observations) {
+        this.observations = observations.clone();
+        a                 = Double.NaN;
+        omega             = Double.NaN;
+    }
+
+    /** Estimate a first guess of the coefficients.
+     * @exception OptimizationException if the sample is too short or if
+     * the first guess cannot be computed (when the elements under the
+     * square roots are negative).
+     * */
+    public void guess() throws OptimizationException {
+        sortObservations();
+        guessAOmega();
+        guessPhi();
+    }
+
+    /** Sort the observations with respect to the abscissa.
+     */
+    private void sortObservations() {
+
+        // 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];
+            }
+        }
+
+    }
+
+    /** Estimate a first guess of the a and &omega; coefficients.
+     * @exception OptimizationException if the sample is too short or if
+     * the first guess cannot be computed (when the elements under the
+     * square roots are negative).
+     */
+    private void guessAOmega() throws OptimizationException {
+
+        // initialize the sums for the linear model between the two integrals
+        double sx2 = 0.0;
+        double sy2 = 0.0;
+        double sxy = 0.0;
+        double sxz = 0.0;
+        double syz = 0.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.0) || (c2 / c3 < 0.0)) {
+            throw new OptimizationException(LocalizedFormats.UNABLE_TO_FIRST_GUESS_HARMONIC_COEFFICIENTS);
+        }
+        a     = FastMath.sqrt(c1 / c2);
+        omega = FastMath.sqrt(c2 / c3);
+
+    }
+
+    /** Estimate a first guess of the &phi; coefficient.
+     */
+    private void guessPhi() {
+
+        // initialize the means
+        double fcMean = 0.0;
+        double fsMean = 0.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;
+
+        }
+
+        phi = FastMath.atan2(-fsMean, fcMean);
+
+    }
+
+    /** Get the guessed amplitude a.
+     * @return guessed amplitude a;
+     */
+    public double getGuessedAmplitude() {
+        return a;
+    }
+
+    /** Get the guessed pulsation &omega;.
+     * @return guessed pulsation &omega;
+     */
+    public double getGuessedPulsation() {
+        return omega;
+    }
+
+    /** Get the guessed phase &phi;.
+     * @return guessed phase &phi;
+     */
+    public double getGuessedPhase() {
+        return phi;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/fitting/HarmonicFitter.java b/src/main/java/org/apache/commons/math/optimization/fitting/HarmonicFitter.java
new file mode 100644
index 0000000..ef019c9
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/fitting/HarmonicFitter.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.optimization.fitting;
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.optimization.DifferentiableMultivariateVectorialOptimizer;
+import org.apache.commons.math.optimization.OptimizationException;
+import org.apache.commons.math.util.FastMath;
+
+/** This class 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.</p>
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @since 2.0
+ */
+public class HarmonicFitter {
+
+    /** Fitter for the coefficients. */
+    private final CurveFitter fitter;
+
+    /** Values for amplitude, pulsation &omega; and phase &phi;. */
+    private double[] parameters;
+
+    /** Simple constructor.
+     * @param optimizer optimizer to use for the fitting
+     */
+    public HarmonicFitter(final DifferentiableMultivariateVectorialOptimizer optimizer) {
+        this.fitter = new CurveFitter(optimizer);
+        parameters  = null;
+    }
+
+    /** Simple constructor.
+     * <p>This constructor can be used when a first guess of the
+     * coefficients is already known.</p>
+     * @param optimizer optimizer to use for the fitting
+     * @param initialGuess guessed values for amplitude (index 0),
+     * pulsation &omega; (index 1) and phase &phi; (index 2)
+     */
+    public HarmonicFitter(final DifferentiableMultivariateVectorialOptimizer optimizer,
+                          final double[] initialGuess) {
+        this.fitter     = new CurveFitter(optimizer);
+        this.parameters = initialGuess.clone();
+    }
+
+    /** 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 P(x) as close as possible to this value
+     */
+    public void addObservedPoint(double weight, double x, double y) {
+        fitter.addObservedPoint(weight, x, y);
+    }
+
+    /** Fit an harmonic function to the observed points.
+     * @return harmonic function best fitting the observed points
+     * @throws OptimizationException if the sample is too short or if
+     * the first guess cannot be computed
+     */
+    public HarmonicFunction fit() throws OptimizationException {
+
+        // shall we compute the first guess of the parameters ourselves ?
+        if (parameters == null) {
+            final WeightedObservedPoint[] observations = fitter.getObservations();
+            if (observations.length < 4) {
+                throw new OptimizationException(LocalizedFormats.INSUFFICIENT_OBSERVED_POINTS_IN_SAMPLE,
+                                                observations.length, 4);
+            }
+
+            HarmonicCoefficientsGuesser guesser = new HarmonicCoefficientsGuesser(observations);
+            guesser.guess();
+            parameters = new double[] {
+                guesser.getGuessedAmplitude(),
+                guesser.getGuessedPulsation(),
+                guesser.getGuessedPhase()
+            };
+
+        }
+
+        try {
+            double[] fitted = fitter.fit(new ParametricHarmonicFunction(), parameters);
+            return new HarmonicFunction(fitted[0], fitted[1], fitted[2]);
+        } catch (FunctionEvaluationException fee) {
+            // should never happen
+            throw new RuntimeException(fee);
+        }
+
+    }
+
+    /** Parametric harmonic function. */
+    private static class ParametricHarmonicFunction implements ParametricRealFunction {
+
+        /** {@inheritDoc} */
+        public double value(double x, double[] parameters) {
+            final double a     = parameters[0];
+            final double omega = parameters[1];
+            final double phi   = parameters[2];
+            return a * FastMath.cos(omega * x + phi);
+        }
+
+        /** {@inheritDoc} */
+        public double[] gradient(double x, double[] parameters) {
+            final double a     = parameters[0];
+            final double omega = parameters[1];
+            final double phi   = parameters[2];
+            final double alpha = omega * x + phi;
+            final double cosAlpha = FastMath.cos(alpha);
+            final double sinAlpha = FastMath.sin(alpha);
+            return new double[] { cosAlpha, -a * x * sinAlpha, -a * sinAlpha };
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/fitting/HarmonicFunction.java b/src/main/java/org/apache/commons/math/optimization/fitting/HarmonicFunction.java
new file mode 100644
index 0000000..cce3533
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/fitting/HarmonicFunction.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.math.optimization.fitting;
+
+import org.apache.commons.math.analysis.DifferentiableUnivariateRealFunction;
+import org.apache.commons.math.util.FastMath;
+
+/** Harmonic function of the form <code>f (t) = a cos (&omega; t + &phi;)</code>.
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ * @since 2.0
+ */
+public class HarmonicFunction implements DifferentiableUnivariateRealFunction {
+
+    /** Amplitude a. */
+    private final double a;
+
+    /** Pulsation &omega;. */
+    private final double omega;
+
+    /** Phase &phi;. */
+    private final double phi;
+
+    /** Simple constructor.
+     * @param a amplitude
+     * @param omega pulsation
+     * @param phi phase
+     */
+    public HarmonicFunction(double a, double omega, double phi) {
+        this.a     = a;
+        this.omega = omega;
+        this.phi   = phi;
+    }
+
+    /** {@inheritDoc} */
+    public double value(double x) {
+        return a * FastMath.cos(omega * x + phi);
+    }
+
+    /** {@inheritDoc} */
+    public HarmonicFunction derivative() {
+        return new HarmonicFunction(a * omega, omega, phi + FastMath.PI / 2);
+    }
+
+    /** Get the amplitude a.
+     * @return amplitude a;
+     */
+    public double getAmplitude() {
+        return a;
+    }
+
+    /** Get the pulsation &omega;.
+     * @return pulsation &omega;
+     */
+    public double getPulsation() {
+        return omega;
+    }
+
+    /** Get the phase &phi;.
+     * @return phase &phi;
+     */
+    public double getPhase() {
+        return phi;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/fitting/ParametricGaussianFunction.java b/src/main/java/org/apache/commons/math/optimization/fitting/ParametricGaussianFunction.java
new file mode 100644
index 0000000..9deb731
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/fitting/ParametricGaussianFunction.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.math.optimization.fitting;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.exception.DimensionMismatchException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.exception.ZeroException;
+import org.apache.commons.math.exception.NullArgumentException;
+import org.apache.commons.math.optimization.fitting.ParametricRealFunction;
+
+/**
+ * A Gaussian function.  Specifically:
+ * <p>
+ * <tt>f(x) = a + b*exp(-((x - c)^2 / (2*d^2)))</tt>
+ * <p>
+ * The parameters have the following meaning:
+ * <ul>
+ * <li><tt>a</tt> is a constant offset that shifts <tt>f(x)</tt> up or down
+ * <li><tt>b</tt> is the height of the peak
+ * <li><tt>c</tt> is the position of the center of the peak
+ * <li><tt>d</tt> is related to the FWHM by <tt>FWHM = 2*sqrt(2*ln(2))*d</tt>
+ * </ul>
+ * Notation key:
+ * <ul>
+ * <li><tt>x^n</tt>: <tt>x</tt> raised to the power of <tt>n</tt>
+ * <li><tt>exp(x)</tt>: <i>e</i><tt>^x</tt>
+ * <li><tt>sqrt(x)</tt>: the square root of <tt>x</tt>
+ * <li><tt>ln(x)</tt>: the natural logarithm of <tt>x</tt>
+ * </ul>
+ * References:
+ * <ul>
+ * <li><a href="http://en.wikipedia.org/wiki/Gaussian_function">Wikipedia:
+ *   Gaussian function</a>
+ * </ul>
+ *
+ * @since 2.2
+ * @version $Revision: 1037327 $ $Date: 2010-11-20 21:57:37 +0100 (sam. 20 nov. 2010) $
+ */
+public class ParametricGaussianFunction implements ParametricRealFunction, Serializable {
+
+    /** Serializable version Id. */
+    private static final long serialVersionUID = -3875578602503903233L;
+
+    /**
+     * Constructs an instance.
+     */
+    public ParametricGaussianFunction() {
+    }
+
+    /**
+     * Computes value of function <tt>f(x)</tt> for the specified <tt>x</tt> and
+     * parameters <tt>a</tt>, <tt>b</tt>, <tt>c</tt>, and <tt>d</tt>.
+     *
+     * @param x <tt>x</tt> value
+     * @param parameters values of <tt>a</tt>, <tt>b</tt>, <tt>c</tt>, and
+     *        <tt>d</tt>
+     *
+     * @return value of <tt>f(x)</tt> evaluated at <tt>x</tt> with the specified
+     *         parameters
+     *
+     * @throws IllegalArgumentException if <code>parameters</code> is invalid as
+     *         determined by {@link #validateParameters(double[])}
+     * @throws ZeroException if <code>parameters</code> values are
+     *         invalid as determined by {@link #validateParameters(double[])}
+     */
+    public double value(double x, double[] parameters) throws ZeroException {
+        validateParameters(parameters);
+        final double a = parameters[0];
+        final double b = parameters[1];
+        final double c = parameters[2];
+        final double d = parameters[3];
+        final double xMc = x - c;
+        return a + b * Math.exp(-xMc * xMc / (2.0 * (d * d)));
+    }
+
+    /**
+     * Computes the gradient vector for a four variable version of the function
+     * where the parameters, <tt>a</tt>, <tt>b</tt>, <tt>c</tt>, and <tt>d</tt>,
+     * are considered the variables, not <tt>x</tt>.  That is, instead of
+     * computing the gradient vector for the function <tt>f(x)</tt> (which would
+     * just be the derivative of <tt>f(x)</tt> with respect to <tt>x</tt> since
+     * it's a one-dimensional function), computes the gradient vector for the
+     * function <tt>f(a, b, c, d) = a + b*exp(-((x - c)^2 / (2*d^2)))</tt>
+     * treating the specified <tt>x</tt> as a constant.
+     * <p>
+     * The components of the computed gradient vector are the partial
+     * derivatives of <tt>f(a, b, c, d)</tt> with respect to each variable.
+     * That is, the partial derivative of <tt>f(a, b, c, d)</tt> with respect to
+     * <tt>a</tt>, the partial derivative of <tt>f(a, b, c, d)</tt> with respect
+     * to <tt>b</tt>, the partial derivative of <tt>f(a, b, c, d)</tt> with
+     * respect to <tt>c</tt>, and the partial derivative of <tt>f(a, b, c,
+     * d)</tt> with respect to <tt>d</tt>.
+     *
+     * @param x <tt>x</tt> value to be used as constant in <tt>f(a, b, c,
+     *        d)</tt>
+     * @param parameters values of <tt>a</tt>, <tt>b</tt>, <tt>c</tt>, and
+     *        <tt>d</tt> for computation of gradient vector of <tt>f(a, b, c,
+     *        d)</tt>
+     *
+     * @return gradient vector of <tt>f(a, b, c, d)</tt>
+     *
+     * @throws IllegalArgumentException if <code>parameters</code> is invalid as
+     *         determined by {@link #validateParameters(double[])}
+     * @throws ZeroException if <code>parameters</code> values are
+     *         invalid as determined by {@link #validateParameters(double[])}
+     */
+    public double[] gradient(double x, double[] parameters) throws ZeroException {
+
+        validateParameters(parameters);
+        final double b = parameters[1];
+        final double c = parameters[2];
+        final double d = parameters[3];
+
+        final double xMc  = x - c;
+        final double d2   = d * d;
+        final double exp  = Math.exp(-xMc * xMc / (2 * d2));
+        final double f    = b * exp * xMc / d2;
+
+        return new double[] { 1.0, exp, f, f * xMc / d };
+
+    }
+
+    /**
+     * Validates parameters to ensure they are appropriate for the evaluation of
+     * the <code>value</code> and <code>gradient</code> methods.
+     *
+     * @param parameters values of <tt>a</tt>, <tt>b</tt>, <tt>c</tt>, and
+     *        <tt>d</tt>
+     *
+     * @throws IllegalArgumentException if <code>parameters</code> is
+     *         <code>null</code> or if <code>parameters</code> does not have
+     *         length == 4
+     * @throws ZeroException if <code>parameters[3]</code>
+     *         (<tt>d</tt>) is 0
+     */
+    private void validateParameters(double[] parameters) throws ZeroException {
+        if (parameters == null) {
+            throw new NullArgumentException(LocalizedFormats.INPUT_ARRAY);
+        }
+        if (parameters.length != 4) {
+            throw new DimensionMismatchException(4, parameters.length);
+        }
+        if (parameters[3] == 0.0) {
+            throw new ZeroException();
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/fitting/ParametricRealFunction.java b/src/main/java/org/apache/commons/math/optimization/fitting/ParametricRealFunction.java
new file mode 100644
index 0000000..0c2843e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/fitting/ParametricRealFunction.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.math.optimization.fitting;
+
+import org.apache.commons.math.FunctionEvaluationException;
+
+/**
+ * An interface representing a real function that depends on one independent
+ * variable plus some extra parameters.
+ *
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ */
+public interface ParametricRealFunction {
+
+    /**
+     * Compute the value of the function.
+     * @param x the point for which the function value should be computed
+     * @param parameters function parameters
+     * @return the value
+     * @throws FunctionEvaluationException if the function evaluation fails
+     */
+    double value(double x, double[] parameters)
+        throws FunctionEvaluationException;
+
+    /**
+     * Compute the gradient of the function with respect to its parameters.
+     * @param x the point for which the function value should be computed
+     * @param parameters function parameters
+     * @return the value
+     * @throws FunctionEvaluationException if the function evaluation fails
+     */
+    double[] gradient(double x, double[] parameters)
+        throws FunctionEvaluationException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/fitting/PolynomialFitter.java b/src/main/java/org/apache/commons/math/optimization/fitting/PolynomialFitter.java
new file mode 100644
index 0000000..3e8e62a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/fitting/PolynomialFitter.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.math.optimization.fitting;
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.analysis.polynomials.PolynomialFunction;
+import org.apache.commons.math.optimization.DifferentiableMultivariateVectorialOptimizer;
+import org.apache.commons.math.optimization.OptimizationException;
+
+/** This class implements a curve fitting specialized for polynomials.
+ * <p>Polynomial fitting is a very simple case of curve fitting. The
+ * estimated coefficients are the polynomial coefficients. They are
+ * searched by a least square estimator.</p>
+ * @version $Revision: 1073270 $ $Date: 2011-02-22 10:19:27 +0100 (mar. 22 févr. 2011) $
+ * @since 2.0
+ */
+
+public class PolynomialFitter {
+
+    /** Fitter for the coefficients. */
+    private final CurveFitter fitter;
+
+    /** Polynomial degree. */
+    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
+     */
+    public PolynomialFitter(int degree, final DifferentiableMultivariateVectorialOptimizer optimizer) {
+        this.fitter = new CurveFitter(optimizer);
+        this.degree = degree;
+    }
+
+    /** 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 P(x) as close as possible to this value
+     */
+    public void addObservedPoint(double weight, double x, double y) {
+        fitter.addObservedPoint(weight, x, y);
+    }
+
+    /**
+     * Remove all observations.
+     * @since 2.2
+     */
+    public void clearObservations() {
+        fitter.clearObservations();
+    }
+
+    /** Get the polynomial fitting the weighted (x, y) points.
+     * @return polynomial function best fitting the observed points
+     * @exception OptimizationException if the algorithm failed to converge
+     */
+    public PolynomialFunction fit() throws OptimizationException {
+        try {
+            return new PolynomialFunction(fitter.fit(new ParametricPolynomial(), new double[degree + 1]));
+        } catch (FunctionEvaluationException fee) {
+            // should never happen
+            throw new RuntimeException(fee);
+        }
+    }
+
+    /** Dedicated parametric polynomial class. */
+    private static class ParametricPolynomial implements ParametricRealFunction {
+
+        /** {@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) {
+            double y = 0;
+            for (int i = parameters.length - 1; i >= 0; --i) {
+                y = y * x + parameters[i];
+            }
+            return y;
+        }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/fitting/WeightedObservedPoint.java b/src/main/java/org/apache/commons/math/optimization/fitting/WeightedObservedPoint.java
new file mode 100644
index 0000000..8153190
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/fitting/WeightedObservedPoint.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.math.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>
+ * @version $Revision: 786479 $ $Date: 2009-06-19 14:36:16 +0200 (ven. 19 juin 2009) $
+ * @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;
+    }
+
+    /** 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/math/optimization/fitting/package.html b/src/main/java/org/apache/commons/math/optimization/fitting/package.html
new file mode 100644
index 0000000..9f9fa06
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/fitting/package.html
@@ -0,0 +1,30 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision$ -->
+<body>
+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>
+
+</body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/optimization/general/AbstractLeastSquaresOptimizer.java b/src/main/java/org/apache/commons/math/optimization/general/AbstractLeastSquaresOptimizer.java
new file mode 100644
index 0000000..6271f53
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/general/AbstractLeastSquaresOptimizer.java
@@ -0,0 +1,374 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.optimization.general;
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.MaxEvaluationsExceededException;
+import org.apache.commons.math.MaxIterationsExceededException;
+import org.apache.commons.math.analysis.DifferentiableMultivariateVectorialFunction;
+import org.apache.commons.math.analysis.MultivariateMatrixFunction;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.linear.InvalidMatrixException;
+import org.apache.commons.math.linear.LUDecompositionImpl;
+import org.apache.commons.math.linear.MatrixUtils;
+import org.apache.commons.math.linear.RealMatrix;
+import org.apache.commons.math.optimization.OptimizationException;
+import org.apache.commons.math.optimization.SimpleVectorialValueChecker;
+import org.apache.commons.math.optimization.VectorialConvergenceChecker;
+import org.apache.commons.math.optimization.DifferentiableMultivariateVectorialOptimizer;
+import org.apache.commons.math.optimization.VectorialPointValuePair;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Base class for implementing least squares optimizers.
+ * <p>This base class handles the boilerplate methods associated to thresholds
+ * settings, jacobian and error estimation.</p>
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @since 1.2
+ *
+ */
+public abstract class AbstractLeastSquaresOptimizer implements DifferentiableMultivariateVectorialOptimizer {
+
+    /** Default maximal number of iterations allowed. */
+    public static final int DEFAULT_MAX_ITERATIONS = 100;
+
+    /** Convergence checker. */
+    protected VectorialConvergenceChecker checker;
+
+    /**
+     * Jacobian matrix.
+     * <p>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).</p>
+     */
+    protected double[][] jacobian;
+
+    /** Number of columns of the jacobian matrix. */
+    protected int cols;
+
+    /** Number of rows of the jacobian matrix. */
+    protected int rows;
+
+    /**
+     * Target value for the objective functions at optimum.
+     * @since 2.1
+     */
+    protected double[] targetValues;
+
+    /**
+     * Weight for the least squares cost computation.
+     * @since 2.1
+     */
+    protected double[] residualsWeights;
+
+    /** Current point. */
+    protected double[] point;
+
+    /** Current objective function value. */
+    protected double[] objective;
+
+    /** Current residuals. */
+    protected double[] residuals;
+
+    /** Weighted Jacobian */
+    protected double[][] wjacobian;
+
+    /** Weighted residuals */
+    protected double[] wresiduals;
+
+    /** Cost value (square root of the sum of the residuals). */
+    protected double cost;
+
+    /** Maximal number of iterations allowed. */
+    private int maxIterations;
+
+    /** Number of iterations already performed. */
+    private int iterations;
+
+    /** Maximal number of evaluations allowed. */
+    private int maxEvaluations;
+
+    /** Number of evaluations already performed. */
+    private int objectiveEvaluations;
+
+    /** Number of jacobian evaluations. */
+    private int jacobianEvaluations;
+
+    /** Objective function. */
+    private DifferentiableMultivariateVectorialFunction function;
+
+    /** Objective function derivatives. */
+    private MultivariateMatrixFunction jF;
+
+    /** Simple constructor with default settings.
+     * <p>The convergence check is set to a {@link SimpleVectorialValueChecker}
+     * and the maximal number of evaluation is set to its default value.</p>
+     */
+    protected AbstractLeastSquaresOptimizer() {
+        setConvergenceChecker(new SimpleVectorialValueChecker());
+        setMaxIterations(DEFAULT_MAX_ITERATIONS);
+        setMaxEvaluations(Integer.MAX_VALUE);
+    }
+
+    /** {@inheritDoc} */
+    public void setMaxIterations(int maxIterations) {
+        this.maxIterations = maxIterations;
+    }
+
+    /** {@inheritDoc} */
+    public int getMaxIterations() {
+        return maxIterations;
+    }
+
+    /** {@inheritDoc} */
+    public int getIterations() {
+        return iterations;
+    }
+
+    /** {@inheritDoc} */
+    public void setMaxEvaluations(int maxEvaluations) {
+        this.maxEvaluations = maxEvaluations;
+    }
+
+    /** {@inheritDoc} */
+    public int getMaxEvaluations() {
+        return maxEvaluations;
+    }
+
+    /** {@inheritDoc} */
+    public int getEvaluations() {
+        return objectiveEvaluations;
+    }
+
+    /** {@inheritDoc} */
+    public int getJacobianEvaluations() {
+        return jacobianEvaluations;
+    }
+
+    /** {@inheritDoc} */
+    public void setConvergenceChecker(VectorialConvergenceChecker convergenceChecker) {
+        this.checker = convergenceChecker;
+    }
+
+    /** {@inheritDoc} */
+    public VectorialConvergenceChecker getConvergenceChecker() {
+        return checker;
+    }
+
+    /** Increment the iterations counter by 1.
+     * @exception OptimizationException if the maximal number
+     * of iterations is exceeded
+     */
+    protected void incrementIterationsCounter()
+        throws OptimizationException {
+        if (++iterations > maxIterations) {
+            throw new OptimizationException(new MaxIterationsExceededException(maxIterations));
+        }
+    }
+
+    /**
+     * Update the jacobian matrix.
+     * @exception FunctionEvaluationException if the function jacobian
+     * cannot be evaluated or its dimension doesn't match problem dimension
+     */
+    protected void updateJacobian() throws FunctionEvaluationException {
+        ++jacobianEvaluations;
+        jacobian = jF.value(point);
+        if (jacobian.length != rows) {
+            throw new FunctionEvaluationException(point, LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE,
+                                                  jacobian.length, rows);
+        }
+        for (int i = 0; i < rows; i++) {
+            final double[] ji = jacobian[i];
+            double wi = FastMath.sqrt(residualsWeights[i]);
+            for (int j = 0; j < cols; ++j) {
+                ji[j] *=  -1.0;
+                wjacobian[i][j] = ji[j]*wi;
+            }
+        }
+    }
+
+    /**
+     * Update the residuals array and cost function value.
+     * @exception FunctionEvaluationException if the function cannot be evaluated
+     * or its dimension doesn't match problem dimension or maximal number of
+     * of evaluations is exceeded
+     */
+    protected void updateResidualsAndCost()
+        throws FunctionEvaluationException {
+
+        if (++objectiveEvaluations > maxEvaluations) {
+            throw new FunctionEvaluationException(new MaxEvaluationsExceededException(maxEvaluations),
+                                                  point);
+        }
+        objective = function.value(point);
+        if (objective.length != rows) {
+            throw new FunctionEvaluationException(point, LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE,
+                                                  objective.length, rows);
+        }
+        cost = 0;
+        int index = 0;
+        for (int i = 0; i < rows; i++) {
+            final double residual = targetValues[i] - objective[i];
+            residuals[i] = residual;
+            wresiduals[i]= residual*FastMath.sqrt(residualsWeights[i]);
+            cost += residualsWeights[i] * residual * residual;
+            index += cols;
+        }
+        cost = FastMath.sqrt(cost);
+
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * Get the covariance matrix of optimized parameters.
+     * @return covariance matrix
+     * @exception FunctionEvaluationException if the function jacobian cannot
+     * be evaluated
+     * @exception OptimizationException if the covariance matrix
+     * cannot be computed (singular problem)
+     */
+    public double[][] getCovariances()
+        throws FunctionEvaluationException, OptimizationException {
+
+        // set up the jacobian
+        updateJacobian();
+
+        // compute transpose(J).J, avoiding building big intermediate matrices
+        double[][] jTj = new double[cols][cols];
+        for (int i = 0; i < cols; ++i) {
+            for (int j = i; j < cols; ++j) {
+                double sum = 0;
+                for (int k = 0; k < rows; ++k) {
+                    sum += wjacobian[k][i] * wjacobian[k][j];
+                }
+                jTj[i][j] = sum;
+                jTj[j][i] = sum;
+            }
+        }
+
+        try {
+            // compute the covariance matrix
+            RealMatrix inverse =
+                new LUDecompositionImpl(MatrixUtils.createRealMatrix(jTj)).getSolver().getInverse();
+            return inverse.getData();
+        } catch (InvalidMatrixException ime) {
+            throw new OptimizationException(LocalizedFormats.UNABLE_TO_COMPUTE_COVARIANCE_SINGULAR_PROBLEM);
+        }
+
+    }
+
+    /**
+     * Guess the errors in optimized parameters.
+     * <p>Guessing is covariance-based, it only gives rough order of magnitude.</p>
+     * @return errors in optimized parameters
+     * @exception FunctionEvaluationException if the function jacobian cannot b evaluated
+     * @exception OptimizationException if the covariances matrix cannot be computed
+     * or the number of degrees of freedom is not positive (number of measurements
+     * lesser or equal to number of parameters)
+     */
+    public double[] guessParametersErrors()
+        throws FunctionEvaluationException, OptimizationException {
+        if (rows <= cols) {
+            throw new OptimizationException(
+                    LocalizedFormats.NO_DEGREES_OF_FREEDOM,
+                    rows, cols);
+        }
+        double[] errors = new double[cols];
+        final double c = FastMath.sqrt(getChiSquare() / (rows - cols));
+        double[][] covar = getCovariances();
+        for (int i = 0; i < errors.length; ++i) {
+            errors[i] = FastMath.sqrt(covar[i][i]) * c;
+        }
+        return errors;
+    }
+
+    /** {@inheritDoc} */
+    public VectorialPointValuePair optimize(final DifferentiableMultivariateVectorialFunction f,
+                                            final double[] target, final double[] weights,
+                                            final double[] startPoint)
+        throws FunctionEvaluationException, OptimizationException, IllegalArgumentException {
+
+        if (target.length != weights.length) {
+            throw new OptimizationException(LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE,
+                                            target.length, weights.length);
+        }
+
+        // reset counters
+        iterations           = 0;
+        objectiveEvaluations = 0;
+        jacobianEvaluations  = 0;
+
+        // store least squares problem characteristics
+        function         = f;
+        jF               = f.jacobian();
+        targetValues     = target.clone();
+        residualsWeights = weights.clone();
+        this.point       = startPoint.clone();
+        this.residuals   = new double[target.length];
+
+        // arrays shared with the other private methods
+        rows      = target.length;
+        cols      = point.length;
+        jacobian  = new double[rows][cols];
+
+        wjacobian = new double[rows][cols];
+        wresiduals = new double[rows];
+
+        cost = Double.POSITIVE_INFINITY;
+
+        return doOptimize();
+
+    }
+
+    /** Perform the bulk of optimization algorithm.
+     * @return the point/value pair giving the optimal value for objective function
+     * @exception FunctionEvaluationException if the objective function throws one during
+     * the search
+     * @exception OptimizationException if the algorithm failed to converge
+     * @exception IllegalArgumentException if the start point dimension is wrong
+     */
+    protected abstract VectorialPointValuePair doOptimize()
+        throws FunctionEvaluationException, OptimizationException, IllegalArgumentException;
+
+
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/general/AbstractScalarDifferentiableOptimizer.java b/src/main/java/org/apache/commons/math/optimization/general/AbstractScalarDifferentiableOptimizer.java
new file mode 100644
index 0000000..70f0a23
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/general/AbstractScalarDifferentiableOptimizer.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.math.optimization.general;
+
+import org.apache.commons.math.analysis.DifferentiableMultivariateRealFunction;
+import org.apache.commons.math.analysis.MultivariateVectorialFunction;
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.MaxEvaluationsExceededException;
+import org.apache.commons.math.MaxIterationsExceededException;
+import org.apache.commons.math.optimization.DifferentiableMultivariateRealOptimizer;
+import org.apache.commons.math.optimization.GoalType;
+import org.apache.commons.math.optimization.OptimizationException;
+import org.apache.commons.math.optimization.RealConvergenceChecker;
+import org.apache.commons.math.optimization.RealPointValuePair;
+import org.apache.commons.math.optimization.SimpleScalarValueChecker;
+
+/**
+ * Base class for implementing optimizers for multivariate scalar functions.
+ * <p>This base class handles the boilerplate methods associated to thresholds
+ * settings, iterations and evaluations counting.</p>
+ * @version $Revision: 1069567 $ $Date: 2011-02-10 22:07:26 +0100 (jeu. 10 févr. 2011) $
+ * @since 2.0
+ */
+public abstract class AbstractScalarDifferentiableOptimizer
+    implements DifferentiableMultivariateRealOptimizer {
+
+    /** Default maximal number of iterations allowed. */
+    public static final int DEFAULT_MAX_ITERATIONS = 100;
+
+    /** Convergence checker. */
+    @Deprecated
+    protected RealConvergenceChecker checker;
+
+    /**
+     * Type of optimization.
+     * @since 2.1
+     */
+    @Deprecated
+    protected GoalType goal;
+
+    /** Current point set. */
+    @Deprecated
+    protected double[] point;
+
+    /** Maximal number of iterations allowed. */
+    private int maxIterations;
+
+    /** Number of iterations already performed. */
+    private int iterations;
+
+    /** Maximal number of evaluations allowed. */
+    private int maxEvaluations;
+
+    /** Number of evaluations already performed. */
+    private int evaluations;
+
+    /** Number of gradient evaluations. */
+    private int gradientEvaluations;
+
+    /** Objective function. */
+    private DifferentiableMultivariateRealFunction function;
+
+    /** Objective function gradient. */
+    private MultivariateVectorialFunction gradient;
+
+    /** Simple constructor with default settings.
+     * <p>The convergence check is set to a {@link SimpleScalarValueChecker}
+     * and the maximal number of evaluation is set to its default value.</p>
+     */
+    protected AbstractScalarDifferentiableOptimizer() {
+        setConvergenceChecker(new SimpleScalarValueChecker());
+        setMaxIterations(DEFAULT_MAX_ITERATIONS);
+        setMaxEvaluations(Integer.MAX_VALUE);
+    }
+
+    /** {@inheritDoc} */
+    public void setMaxIterations(int maxIterations) {
+        this.maxIterations = maxIterations;
+    }
+
+    /** {@inheritDoc} */
+    public int getMaxIterations() {
+        return maxIterations;
+    }
+
+    /** {@inheritDoc} */
+    public int getIterations() {
+        return iterations;
+    }
+
+    /** {@inheritDoc} */
+    public void setMaxEvaluations(int maxEvaluations) {
+        this.maxEvaluations = maxEvaluations;
+    }
+
+    /** {@inheritDoc} */
+    public int getMaxEvaluations() {
+        return maxEvaluations;
+    }
+
+    /** {@inheritDoc} */
+    public int getEvaluations() {
+        return evaluations;
+    }
+
+    /** {@inheritDoc} */
+    public int getGradientEvaluations() {
+        return gradientEvaluations;
+    }
+
+    /** {@inheritDoc} */
+    public void setConvergenceChecker(RealConvergenceChecker convergenceChecker) {
+        this.checker = convergenceChecker;
+    }
+
+    /** {@inheritDoc} */
+    public RealConvergenceChecker getConvergenceChecker() {
+        return checker;
+    }
+
+    /** Increment the iterations counter by 1.
+     * @exception OptimizationException if the maximal number
+     * of iterations is exceeded
+     */
+    protected void incrementIterationsCounter()
+        throws OptimizationException {
+        if (++iterations > maxIterations) {
+            throw new OptimizationException(new MaxIterationsExceededException(maxIterations));
+        }
+    }
+
+    /**
+     * Compute the gradient vector.
+     * @param evaluationPoint point at which the gradient must be evaluated
+     * @return gradient at the specified point
+     * @exception FunctionEvaluationException if the function gradient
+     */
+    protected double[] computeObjectiveGradient(final double[] evaluationPoint)
+        throws FunctionEvaluationException {
+        ++gradientEvaluations;
+        return gradient.value(evaluationPoint);
+    }
+
+    /**
+     * Compute the objective function value.
+     * @param evaluationPoint point at which the objective function must be evaluated
+     * @return objective function value at specified point
+     * @exception FunctionEvaluationException if the function cannot be evaluated
+     * or its dimension doesn't match problem dimension or the maximal number
+     * of iterations is exceeded
+     */
+    protected double computeObjectiveValue(final double[] evaluationPoint)
+        throws FunctionEvaluationException {
+        if (++evaluations > maxEvaluations) {
+            throw new FunctionEvaluationException(new MaxEvaluationsExceededException(maxEvaluations),
+                                                  evaluationPoint);
+        }
+        return function.value(evaluationPoint);
+    }
+
+    /** {@inheritDoc} */
+    public RealPointValuePair optimize(final DifferentiableMultivariateRealFunction f,
+                                         final GoalType goalType,
+                                         final double[] startPoint)
+        throws FunctionEvaluationException, OptimizationException, IllegalArgumentException {
+
+        // reset counters
+        iterations          = 0;
+        evaluations         = 0;
+        gradientEvaluations = 0;
+
+        // store optimization problem characteristics
+        function = f;
+        gradient = f.gradient();
+        goal     = goalType;
+        point    = startPoint.clone();
+
+        return doOptimize();
+
+    }
+
+    /** Perform the bulk of optimization algorithm.
+     * @return the point/value pair giving the optimal value for objective function
+     * @exception FunctionEvaluationException if the objective function throws one during
+     * the search
+     * @exception OptimizationException if the algorithm failed to converge
+     * @exception IllegalArgumentException if the start point dimension is wrong
+     */
+    protected abstract RealPointValuePair doOptimize()
+        throws FunctionEvaluationException, OptimizationException, IllegalArgumentException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/general/ConjugateGradientFormula.java b/src/main/java/org/apache/commons/math/optimization/general/ConjugateGradientFormula.java
new file mode 100644
index 0000000..8a0fde3
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/general/ConjugateGradientFormula.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.math.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
+ * @version $Revision: 758059 $ $Date: 2009-03-24 23:16:21 +0100 (mar. 24 mars 2009) $
+ * @since 2.0
+ */
+public enum ConjugateGradientFormula {
+
+    /** Fletcher-Reeves formula. */
+    FLETCHER_REEVES,
+
+    /** Polak-Ribi&egrave;re formula. */
+    POLAK_RIBIERE
+
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/general/GaussNewtonOptimizer.java b/src/main/java/org/apache/commons/math/optimization/general/GaussNewtonOptimizer.java
new file mode 100644
index 0000000..e7ba606
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/general/GaussNewtonOptimizer.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.math.optimization.general;
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.linear.BlockRealMatrix;
+import org.apache.commons.math.linear.DecompositionSolver;
+import org.apache.commons.math.linear.InvalidMatrixException;
+import org.apache.commons.math.linear.LUDecompositionImpl;
+import org.apache.commons.math.linear.QRDecompositionImpl;
+import org.apache.commons.math.linear.RealMatrix;
+import org.apache.commons.math.optimization.OptimizationException;
+import org.apache.commons.math.optimization.VectorialPointValuePair;
+
+/**
+ * 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>
+ *
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @since 2.0
+ *
+ */
+
+public class GaussNewtonOptimizer extends AbstractLeastSquaresOptimizer {
+
+    /** Indicator for using LU decomposition. */
+    private final boolean useLU;
+
+    /** Simple constructor with default settings.
+     * <p>The convergence check is set to a {@link
+     * org.apache.commons.math.optimization.SimpleVectorialValueChecker}
+     * and the maximal number of evaluation is set to
+     * {@link AbstractLeastSquaresOptimizer#DEFAULT_MAX_ITERATIONS}.
+     * @param useLU if true, the normal equations will be solved using LU
+     * decomposition, otherwise they will be solved using QR decomposition
+     */
+    public GaussNewtonOptimizer(final boolean useLU) {
+        this.useLU = useLU;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public VectorialPointValuePair doOptimize()
+        throws FunctionEvaluationException, OptimizationException, IllegalArgumentException {
+
+        // iterate until convergence is reached
+        VectorialPointValuePair current = null;
+        for (boolean converged = false; !converged;) {
+
+            incrementIterationsCounter();
+
+            // evaluate the objective function and its jacobian
+            VectorialPointValuePair previous = current;
+            updateResidualsAndCost();
+            updateJacobian();
+            current = new VectorialPointValuePair(point, objective);
+
+            // build the linear problem
+            final double[]   b = new double[cols];
+            final double[][] a = new double[cols][cols];
+            for (int i = 0; i < rows; ++i) {
+
+                final double[] grad   = jacobian[i];
+                final double weight   = residualsWeights[i];
+                final double residual = objective[i] - targetValues[i];
+
+                // compute the normal equation
+                final double wr = weight * residual;
+                for (int j = 0; j < cols; ++j) {
+                    b[j] += wr * grad[j];
+                }
+
+                // build the contribution matrix for measurement i
+                for (int k = 0; k < cols; ++k) {
+                    double[] ak = a[k];
+                    double wgk = weight * grad[k];
+                    for (int l = 0; l < cols; ++l) {
+                        ak[l] += wgk * grad[l];
+                    }
+                }
+
+            }
+
+            try {
+
+                // solve the linearized least squares problem
+                RealMatrix mA = new BlockRealMatrix(a);
+                DecompositionSolver solver = useLU ?
+                        new LUDecompositionImpl(mA).getSolver() :
+                        new QRDecompositionImpl(mA).getSolver();
+                final double[] dX = solver.solve(b);
+
+                // update the estimated parameters
+                for (int i = 0; i < cols; ++i) {
+                    point[i] += dX[i];
+                }
+
+            } catch(InvalidMatrixException e) {
+                throw new OptimizationException(LocalizedFormats.UNABLE_TO_SOLVE_SINGULAR_PROBLEM);
+            }
+
+            // check convergence
+            if (previous != null) {
+                converged = checker.converged(getIterations(), previous, current);
+            }
+
+        }
+
+        // we have converged
+        return current;
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/general/LevenbergMarquardtOptimizer.java b/src/main/java/org/apache/commons/math/optimization/general/LevenbergMarquardtOptimizer.java
new file mode 100644
index 0000000..ea9bc03
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/general/LevenbergMarquardtOptimizer.java
@@ -0,0 +1,888 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.optimization.general;
+
+import java.util.Arrays;
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.optimization.OptimizationException;
+import org.apache.commons.math.optimization.VectorialPointValuePair;
+import org.apache.commons.math.util.FastMath;
+import org.apache.commons.math.util.MathUtils;
+
+
+/**
+ * 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>
+ * @version $Revision: 1073272 $ $Date: 2011-02-22 10:22:25 +0100 (mar. 22 févr. 2011) $
+ * @since 2.0
+ *
+ */
+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 double initialStepBoundFactor;
+
+    /** Desired relative error in the sum of squares. */
+    private double costRelativeTolerance;
+
+    /**  Desired relative error in the approximate solution parameters. */
+    private double parRelativeTolerance;
+
+    /** Desired max cosine on the orthogonality between the function vector
+     * and the columns of the jacobian. */
+    private double orthoTolerance;
+
+    /** Threshold for QR ranking. */
+    private double qrRankingThreshold;
+
+    /**
+     * Build an optimizer for least squares problems.
+     * <p>The default values for the algorithm settings are:
+     *   <ul>
+     *    <li>{@link #setConvergenceChecker(VectorialConvergenceChecker) vectorial convergence checker}: null</li>
+     *    <li>{@link #setInitialStepBoundFactor(double) initial step bound factor}: 100.0</li>
+     *    <li>{@link #setMaxIterations(int) maximal iterations}: 1000</li>
+     *    <li>{@link #setCostRelativeTolerance(double) cost relative tolerance}: 1.0e-10</li>
+     *    <li>{@link #setParRelativeTolerance(double) parameters relative tolerance}: 1.0e-10</li>
+     *    <li>{@link #setOrthoTolerance(double) orthogonality tolerance}: 1.0e-10</li>
+     *    <li>{@link #setQRRankingThreshold(double) QR ranking threshold}: {@link MathUtils#SAFE_MIN}</li>
+     *   </ul>
+     * </p>
+     * <p>These default values may be overridden after construction. If the {@link
+     * #setConvergenceChecker vectorial convergence checker} is set to a non-null value, it
+     * will be used instead of the {@link #setCostRelativeTolerance cost relative tolerance}
+     * and {@link #setParRelativeTolerance parameters relative tolerance} settings.
+     */
+    public LevenbergMarquardtOptimizer() {
+
+        // set up the superclass with a default  max cost evaluations setting
+        setMaxIterations(1000);
+
+        // default values for the tuning parameters
+        setConvergenceChecker(null);
+        setInitialStepBoundFactor(100.0);
+        setCostRelativeTolerance(1.0e-10);
+        setParRelativeTolerance(1.0e-10);
+        setOrthoTolerance(1.0e-10);
+        setQRRankingThreshold(MathUtils.SAFE_MIN);
+
+    }
+
+    /**
+     * Set the positive input variable used in determining the initial step bound.
+     * This bound is set to the product of initialStepBoundFactor and the euclidean
+     * norm of diag*x if nonzero, or else to initialStepBoundFactor itself. In most
+     * cases factor should lie in the interval (0.1, 100.0). 100.0 is a generally
+     * recommended value.
+     *
+     * @param initialStepBoundFactor initial step bound factor
+     */
+    public void setInitialStepBoundFactor(double initialStepBoundFactor) {
+        this.initialStepBoundFactor = initialStepBoundFactor;
+    }
+
+    /**
+     * Set the desired relative error in the sum of squares.
+     * <p>This setting is used only if the {@link #setConvergenceChecker vectorial
+     * convergence checker} is set to null.</p>
+     * @param costRelativeTolerance desired relative error in the sum of squares
+     */
+    public void setCostRelativeTolerance(double costRelativeTolerance) {
+        this.costRelativeTolerance = costRelativeTolerance;
+    }
+
+    /**
+     * Set the desired relative error in the approximate solution parameters.
+     * <p>This setting is used only if the {@link #setConvergenceChecker vectorial
+     * convergence checker} is set to null.</p>
+     * @param parRelativeTolerance desired relative error
+     * in the approximate solution parameters
+     */
+    public void setParRelativeTolerance(double parRelativeTolerance) {
+        this.parRelativeTolerance = parRelativeTolerance;
+    }
+
+    /**
+     * Set the desired max cosine on the orthogonality.
+     * <p>This setting is always used, regardless of the {@link #setConvergenceChecker
+     * vectorial convergence checker} being null or non-null.</p>
+     * @param orthoTolerance desired max cosine on the orthogonality
+     * between the function vector and the columns of the jacobian
+     */
+    public void setOrthoTolerance(double orthoTolerance) {
+        this.orthoTolerance = orthoTolerance;
+    }
+
+    /**
+     * Set the desired threshold for QR ranking.
+     * <p>
+     * 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.
+     * </p>
+     * @param threshold threshold for QR ranking
+     * @since 2.2
+     */
+    public void setQRRankingThreshold(final double threshold) {
+        this.qrRankingThreshold = threshold;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected VectorialPointValuePair doOptimize()
+        throws FunctionEvaluationException, OptimizationException, IllegalArgumentException {
+
+        // arrays shared with the other private methods
+        solvedCols  = Math.min(rows, cols);
+        diagR       = new double[cols];
+        jacNorm     = new double[cols];
+        beta        = new double[cols];
+        permutation = new int[cols];
+        lmDir       = new double[cols];
+
+        // local point
+        double   delta   = 0;
+        double   xNorm   = 0;
+        double[] diag    = new double[cols];
+        double[] oldX    = new double[cols];
+        double[] oldRes  = new double[rows];
+        double[] oldObj  = new double[rows];
+        double[] qtf     = new double[rows];
+        double[] work1   = new double[cols];
+        double[] work2   = new double[cols];
+        double[] work3   = new double[cols];
+
+        // evaluate the function at the starting point and calculate its norm
+        updateResidualsAndCost();
+
+        // outer loop
+        lmPar = 0;
+        boolean firstIteration = true;
+        VectorialPointValuePair current = new VectorialPointValuePair(point, objective);
+        while (true) {
+            for (int i=0;i<rows;i++) {
+                qtf[i]=wresiduals[i];
+            }
+            incrementIterationsCounter();
+
+            // compute the Q.R. decomposition of the jacobian matrix
+            VectorialPointValuePair previous = current;
+            updateJacobian();
+            qrDecomposition();
+
+            // 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];
+                wjacobian[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 < cols; ++k) {
+                    double dk = jacNorm[k];
+                    if (dk == 0) {
+                        dk = 1.0;
+                    }
+                    double xk = dk * point[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 (cost != 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 += wjacobian[i][pj] * qtf[i];
+                        }
+                        maxCosine = FastMath.max(maxCosine, FastMath.abs(sum) / (s * cost));
+                    }
+                }
+            }
+            if (maxCosine <= orthoTolerance) {
+                // convergence has been reached
+                updateResidualsAndCost();
+                current = new VectorialPointValuePair(point, objective);
+                return current;
+            }
+
+            // rescale if necessary
+            for (int j = 0; j < cols; ++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] = point[pj];
+                }
+                double previousCost = cost;
+                double[] tmpVec = residuals;
+                residuals = oldRes;
+                oldRes    = tmpVec;
+                tmpVec    = objective;
+                objective = 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];
+                    point[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
+                updateResidualsAndCost();
+
+                // compute the scaled actual reduction
+                double actRed = -1.0;
+                if (0.1 * cost < previousCost) {
+                    double r = cost / 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] += wjacobian[i][pj] * dirJ;
+                    }
+                }
+                double coeff1 = 0;
+                for (int j = 0; j < solvedCols; ++j) {
+                    coeff1 += work1[j] * work1[j];
+                }
+                double pc2 = previousCost * previousCost;
+                coeff1 = 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 * cost >= 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 < cols; ++k) {
+                        double xK = diag[k] * point[k];
+                        xNorm    += xK * xK;
+                    }
+                    xNorm = FastMath.sqrt(xNorm);
+                    current = new VectorialPointValuePair(point, objective);
+
+                    // tests for convergence.
+                    if (checker != null) {
+                    // we use the vectorial convergence checker
+                        if (checker.converged(getIterations(), previous, current)) {
+                            return current;
+                        }
+                    }
+                } else {
+                    // failed iteration, reset the previous values
+                    cost = previousCost;
+                    for (int j = 0; j < solvedCols; ++j) {
+                        int pj = permutation[j];
+                        point[pj] = oldX[pj];
+                    }
+                    tmpVec    = residuals;
+                    residuals = oldRes;
+                    oldRes    = tmpVec;
+                    tmpVec    = objective;
+                    objective = oldObj;
+                    oldObj    = tmpVec;
+                }
+                if (checker==null) {
+                    if (((FastMath.abs(actRed) <= costRelativeTolerance) &&
+                        (preRed <= costRelativeTolerance) &&
+                        (ratio <= 2.0)) ||
+                       (delta <= parRelativeTolerance * xNorm)) {
+                       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 OptimizationException(LocalizedFormats.TOO_SMALL_COST_RELATIVE_TOLERANCE,
+                            costRelativeTolerance);
+                } else if (delta <= 2.2204e-16 * xNorm) {
+                    throw new OptimizationException(LocalizedFormats.TOO_SMALL_PARAMETERS_RELATIVE_TOLERANCE,
+                            parRelativeTolerance);
+                } else if (maxCosine <= 2.2204e-16)  {
+                    throw new OptimizationException(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) {
+
+        // 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 < cols; ++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 * wjacobian[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 += wjacobian[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 += wjacobian[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]] -= wjacobian[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) {
+                wjacobian[i][pj] = wjacobian[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 = wjacobian[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)
+                    wjacobian[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 = wjacobian[i][pk];
+                        final double temp2 = cos * rik + sin * lmDiag[i];
+                        lmDiag[i] = -sin * rik + cos * lmDiag[i];
+                        wjacobian[i][pk] = temp2;
+                    }
+
+                }
+            }
+
+            // store the diagonal element of s and restore
+            // the corresponding diagonal element of R
+            lmDiag[j] = wjacobian[j][permutation[j]];
+            wjacobian[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 += wjacobian[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>
+     * @exception OptimizationException if the decomposition cannot be performed
+     */
+    private void qrDecomposition() throws OptimizationException {
+
+        // initializations
+        for (int k = 0; k < cols; ++k) {
+            permutation[k] = k;
+            double norm2 = 0;
+            for (int i = 0; i < wjacobian.length; ++i) {
+                double akk = wjacobian[i][k];
+                norm2 += akk * akk;
+            }
+            jacNorm[k] = FastMath.sqrt(norm2);
+        }
+
+        // transform the matrix column after column
+        for (int k = 0; k < cols; ++k) {
+
+            // select the column with the greatest norm on active components
+            int nextColumn = -1;
+            double ak2 = Double.NEGATIVE_INFINITY;
+            for (int i = k; i < cols; ++i) {
+                double norm2 = 0;
+                for (int j = k; j < wjacobian.length; ++j) {
+                    double aki = wjacobian[j][permutation[i]];
+                    norm2 += aki * aki;
+                }
+                if (Double.isInfinite(norm2) || Double.isNaN(norm2)) {
+                    throw new OptimizationException(LocalizedFormats.UNABLE_TO_PERFORM_QR_DECOMPOSITION_ON_JACOBIAN,
+                            rows, cols);
+                }
+                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   = wjacobian[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;
+            wjacobian[k][pk] -= alpha;
+
+            // transform the remaining columns
+            for (int dk = cols - 1 - k; dk > 0; --dk) {
+                double gamma = 0;
+                for (int j = k; j < wjacobian.length; ++j) {
+                    gamma += wjacobian[j][pk] * wjacobian[j][permutation[k + dk]];
+                }
+                gamma *= betak;
+                for (int j = k; j < wjacobian.length; ++j) {
+                    wjacobian[j][permutation[k + dk]] -= gamma * wjacobian[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) {
+        for (int k = 0; k < cols; ++k) {
+            int pk = permutation[k];
+            double gamma = 0;
+            for (int i = k; i < rows; ++i) {
+                gamma += wjacobian[i][pk] * y[i];
+            }
+            gamma *= beta[pk];
+            for (int i = k; i < rows; ++i) {
+                y[i] -= gamma * wjacobian[i][pk];
+            }
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/general/NonLinearConjugateGradientOptimizer.java b/src/main/java/org/apache/commons/math/optimization/general/NonLinearConjugateGradientOptimizer.java
new file mode 100644
index 0000000..17f1d8b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/general/NonLinearConjugateGradientOptimizer.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.math.optimization.general;
+
+import org.apache.commons.math.ConvergenceException;
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.analysis.solvers.BrentSolver;
+import org.apache.commons.math.analysis.solvers.UnivariateRealSolver;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.optimization.GoalType;
+import org.apache.commons.math.optimization.OptimizationException;
+import org.apache.commons.math.optimization.RealPointValuePair;
+import org.apache.commons.math.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>
+ *
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ * @since 2.0
+ *
+ */
+
+public class NonLinearConjugateGradientOptimizer
+    extends AbstractScalarDifferentiableOptimizer {
+
+    /** Update formula for the beta parameter. */
+    private final ConjugateGradientFormula updateFormula;
+
+    /** Preconditioner (may be null). */
+    private Preconditioner preconditioner;
+
+    /** solver to use in the line search (may be null). */
+    private UnivariateRealSolver solver;
+
+    /** Initial step used to bracket the optimum in line search. */
+    private double initialStep;
+
+    /** Simple constructor with default settings.
+     * <p>The convergence check is set to a {@link
+     * org.apache.commons.math.optimization.SimpleVectorialValueChecker}
+     * and the maximal number of iterations is set to
+     * {@link AbstractScalarDifferentiableOptimizer#DEFAULT_MAX_ITERATIONS}.
+     * @param updateFormula formula to use for updating the &beta; parameter,
+     * must be one of {@link ConjugateGradientFormula#FLETCHER_REEVES} or {@link
+     * ConjugateGradientFormula#POLAK_RIBIERE}
+     */
+    public NonLinearConjugateGradientOptimizer(final ConjugateGradientFormula updateFormula) {
+        this.updateFormula = updateFormula;
+        preconditioner     = null;
+        solver             = null;
+        initialStep        = 1.0;
+    }
+
+    /**
+     * Set the preconditioner.
+     * @param preconditioner preconditioner to use for next optimization,
+     * may be null to remove an already registered preconditioner
+     */
+    public void setPreconditioner(final Preconditioner preconditioner) {
+        this.preconditioner = preconditioner;
+    }
+
+    /**
+     * Set the solver to use during line search.
+     * @param lineSearchSolver solver to use during line search, may be null
+     * to remove an already registered solver and fall back to the
+     * default {@link BrentSolver Brent solver}.
+     */
+    public void setLineSearchSolver(final UnivariateRealSolver lineSearchSolver) {
+        this.solver = lineSearchSolver;
+    }
+
+    /**
+     * 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 RealPointValuePair doOptimize()
+        throws FunctionEvaluationException, OptimizationException, IllegalArgumentException {
+        try {
+
+            // initialization
+            if (preconditioner == null) {
+                preconditioner = new IdentityPreconditioner();
+            }
+            if (solver == null) {
+                solver = new BrentSolver();
+            }
+            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];
+            }
+
+            RealPointValuePair current = null;
+            while (true) {
+
+                final double objective = computeObjectiveValue(point);
+                RealPointValuePair previous = current;
+                current = new RealPointValuePair(point, objective);
+                if (previous != null) {
+                    if (checker.converged(getIterations(), previous, current)) {
+                        // we have found an optimum
+                        return current;
+                    }
+                }
+
+                incrementIterationsCounter();
+
+                double dTd = 0;
+                for (final double di : searchDirection) {
+                    dTd += di * di;
+                }
+
+                // find the optimal step in the search direction
+                final UnivariateRealFunction lsf = new LineSearchFunction(searchDirection);
+                final double step = solver.solve(lsf, 0, findUpperBound(lsf, 0, initialStep));
+
+                // 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 ((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];
+                    }
+                }
+
+            }
+
+        } catch (ConvergenceException ce) {
+            throw new OptimizationException(ce);
+        }
+    }
+
+    /**
+     * 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
+     * @exception FunctionEvaluationException if the function cannot be computed
+     * @exception OptimizationException if no bracket can be found
+     */
+    private double findUpperBound(final UnivariateRealFunction f,
+                                  final double a, final double h)
+        throws FunctionEvaluationException, OptimizationException {
+        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 OptimizationException(LocalizedFormats.UNABLE_TO_BRACKET_OPTIMUM_IN_LINE_SEARCH);
+    }
+
+    /** Default identity preconditioner. */
+    private 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 UnivariateRealFunction {
+        /** Search direction. */
+        private final double[] searchDirection;
+
+        /** Simple constructor.
+         * @param searchDirection search direction
+         */
+        public LineSearchFunction(final double[] searchDirection) {
+            this.searchDirection = searchDirection;
+        }
+
+        /** {@inheritDoc} */
+        public double value(double x) throws FunctionEvaluationException {
+
+            // 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;
+            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/math/optimization/general/Preconditioner.java b/src/main/java/org/apache/commons/math/optimization/general/Preconditioner.java
new file mode 100644
index 0000000..7bdde75
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/general/Preconditioner.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.math.optimization.general;
+
+import org.apache.commons.math.FunctionEvaluationException;
+
+/**
+ * This interface represents a preconditioner for differentiable scalar
+ * objective function optimizers.
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @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
+     * @exception FunctionEvaluationException if no cost can be computed for the parameters
+     * @exception IllegalArgumentException if point dimension is wrong
+     */
+    double[] precondition(double[] point, double[] r)
+        throws FunctionEvaluationException, IllegalArgumentException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/general/package.html b/src/main/java/org/apache/commons/math/optimization/general/package.html
new file mode 100644
index 0000000..51ccf95
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/general/package.html
@@ -0,0 +1,22 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 799857 $ -->
+<body>
+This package provides optimization algorithms that require derivatives.
+</body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/optimization/linear/AbstractLinearOptimizer.java b/src/main/java/org/apache/commons/math/optimization/linear/AbstractLinearOptimizer.java
new file mode 100644
index 0000000..b8b2390
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/linear/AbstractLinearOptimizer.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.math.optimization.linear;
+
+import java.util.Collection;
+
+import org.apache.commons.math.MaxIterationsExceededException;
+import org.apache.commons.math.optimization.GoalType;
+import org.apache.commons.math.optimization.OptimizationException;
+import org.apache.commons.math.optimization.RealPointValuePair;
+
+/**
+ * Base class for implementing linear optimizers.
+ * <p>This base class handles the boilerplate methods associated to thresholds
+ * settings and iterations counters.</p>
+ * @version $Revision: 925812 $ $Date: 2010-03-21 16:49:31 +0100 (dim. 21 mars 2010) $
+ * @since 2.0
+ *
+ */
+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
+     */
+    protected LinearObjectiveFunction function;
+
+    /**
+     * Linear constraints.
+     * @since 2.1
+     */
+    protected Collection<LinearConstraint> linearConstraints;
+
+    /**
+     * Type of optimization goal: either {@link GoalType#MAXIMIZE} or {@link GoalType#MINIMIZE}.
+     * @since 2.1
+     */
+    protected GoalType goal;
+
+    /**
+     * Whether to restrict the variables to non-negative values.
+     * @since 2.1
+     */
+    protected 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);
+    }
+
+    /** {@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 OptimizationException if the maximal number
+     * of iterations is exceeded
+     */
+    protected void incrementIterationsCounter()
+        throws OptimizationException {
+        if (++iterations > maxIterations) {
+            throw new OptimizationException(new MaxIterationsExceededException(maxIterations));
+        }
+    }
+
+    /** {@inheritDoc} */
+    public RealPointValuePair optimize(final LinearObjectiveFunction f,
+                                       final Collection<LinearConstraint> constraints,
+                                       final GoalType goalType, final boolean restrictToNonNegative)
+         throws OptimizationException {
+
+        // 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 OptimizationException if no solution fulfilling the constraints
+     * can be found in the allowed number of iterations
+     */
+    protected abstract RealPointValuePair doOptimize()
+        throws OptimizationException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/linear/LinearConstraint.java b/src/main/java/org/apache/commons/math/optimization/linear/LinearConstraint.java
new file mode 100644
index 0000000..85d6251
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/linear/LinearConstraint.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.math.optimization.linear;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+
+import org.apache.commons.math.linear.MatrixUtils;
+import org.apache.commons.math.linear.RealVector;
+import org.apache.commons.math.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>
+ * @version $Revision: 922713 $ $Date: 2010-03-14 02:26:13 +0100 (dim. 14 mars 2010) $
+ * @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;
+    }
+
+    /**
+     * 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/math/optimization/linear/LinearObjectiveFunction.java b/src/main/java/org/apache/commons/math/optimization/linear/LinearObjectiveFunction.java
new file mode 100644
index 0000000..b3c3eb8
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/linear/LinearObjectiveFunction.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.optimization.linear;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+
+import org.apache.commons.math.linear.MatrixUtils;
+import org.apache.commons.math.linear.RealVector;
+import org.apache.commons.math.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>
+ * @version $Revision: 922713 $ $Date: 2010-03-14 02:26:13 +0100 (dim. 14 mars 2010) $
+ * @since 2.0
+ */
+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(point) + 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/math/optimization/linear/LinearOptimizer.java b/src/main/java/org/apache/commons/math/optimization/linear/LinearOptimizer.java
new file mode 100644
index 0000000..41fccd9
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/linear/LinearOptimizer.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.math.optimization.linear;
+
+import java.util.Collection;
+
+import org.apache.commons.math.optimization.GoalType;
+import org.apache.commons.math.optimization.OptimizationException;
+import org.apache.commons.math.optimization.RealPointValuePair;
+
+/**
+ * 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>
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ * @since 2.0
+ */
+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 OptimizationException if no solution fulfilling the constraints
+     * can be found in the allowed number of iterations
+     */
+   RealPointValuePair optimize(LinearObjectiveFunction f, Collection<LinearConstraint> constraints,
+                               GoalType goalType, boolean restrictToNonNegative)
+        throws OptimizationException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/linear/NoFeasibleSolutionException.java b/src/main/java/org/apache/commons/math/optimization/linear/NoFeasibleSolutionException.java
new file mode 100644
index 0000000..b145dd4
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/linear/NoFeasibleSolutionException.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.math.optimization.linear;
+
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.optimization.OptimizationException;
+
+/**
+ * This class represents exceptions thrown by optimizers when no solution
+ * fulfills the constraints.
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ * @since 2.0
+ */
+public class NoFeasibleSolutionException extends OptimizationException {
+
+    /** 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/math/optimization/linear/Relationship.java b/src/main/java/org/apache/commons/math/optimization/linear/Relationship.java
new file mode 100644
index 0000000..500dcc1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/linear/Relationship.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.math.optimization.linear;
+
+/**
+ * Types of relationships between two cells in a Solver {@link LinearConstraint}.
+ * @version $Revision: 1003886 $ $Date: 2010-10-02 23:04:44 +0200 (sam. 02 oct. 2010) $
+ * @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
+     */
+    private 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/math/optimization/linear/SimplexSolver.java b/src/main/java/org/apache/commons/math/optimization/linear/SimplexSolver.java
new file mode 100644
index 0000000..830f65c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/linear/SimplexSolver.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.math.optimization.linear;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.math.optimization.OptimizationException;
+import org.apache.commons.math.optimization.RealPointValuePair;
+import org.apache.commons.math.util.MathUtils;
+
+
+/**
+ * Solves a linear problem using the Two-Phase Simplex Method.
+ * @version $Revision: 812831 $ $Date: 2009-09-09 10:48:03 +0200 (mer. 09 sept. 2009) $
+ * @since 2.0
+ */
+public class SimplexSolver extends AbstractLinearOptimizer {
+
+    /** Default amount of error to accept in floating point comparisons. */
+    private static final double DEFAULT_EPSILON = 1.0e-6;
+
+    /** Amount of error to accept in floating point comparisons. */
+    protected final double epsilon;
+
+    /**
+     * Build a simplex solver with default settings.
+     */
+    public SimplexSolver() {
+        this(DEFAULT_EPSILON);
+    }
+
+    /**
+     * Build a simplex solver with a specified accepted amount of error
+     * @param epsilon the amount of error to accept in floating point comparisons
+     */
+    public SimplexSolver(final double epsilon) {
+        this.epsilon = epsilon;
+    }
+
+    /**
+     * 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++) {
+            if (MathUtils.compareTo(tableau.getEntry(0, i), minValue, epsilon) < 0) {
+                minValue = tableau.getEntry(0, i);
+                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 (MathUtils.compareTo(entry, 0, epsilon) > 0) {
+                final double ratio = rhs / entry;
+                if (MathUtils.equals(ratio, minRatio, epsilon)) {
+                    minRatioPositions.add(i);
+                } else if (ratio < minRatio) {
+                    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
+          // check if there's an artificial variable that can be forced out of the basis
+          for (Integer row : minRatioPositions) {
+            for (int i = 0; i < tableau.getNumArtificialVariables(); i++) {
+              int column = i + tableau.getArtificialVariableOffset();
+              if (MathUtils.equals(tableau.getEntry(row, column), 1, epsilon) &&
+                  row.equals(tableau.getBasicRow(column))) {
+                return row;
+              }
+            }
+          }
+        }
+        return minRatioPositions.get(0);
+    }
+
+    /**
+     * Runs one iteration of the Simplex method on the given model.
+     * @param tableau simple tableau for the problem
+     * @throws OptimizationException if the maximal iteration count has been
+     * exceeded or if the model is found not to have a bounded solution
+     */
+    protected void doIteration(final SimplexTableau tableau)
+        throws OptimizationException {
+
+        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) {
+                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
+     * @exception OptimizationException if the maximal number of iterations is
+     * exceeded, or if the problem is found not to have a bounded solution, or
+     * if there is no feasible solution
+     */
+    protected void solvePhase1(final SimplexTableau tableau) throws OptimizationException {
+
+        // 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 (!MathUtils.equals(tableau.getEntry(0, tableau.getRhsOffset()), 0, epsilon)) {
+            throw new NoFeasibleSolutionException();
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RealPointValuePair doOptimize() throws OptimizationException {
+        final SimplexTableau tableau =
+            new SimplexTableau(function, linearConstraints, goal, nonNegative, epsilon);
+
+        solvePhase1(tableau);
+        tableau.dropPhase1Objective();
+
+        while (!tableau.isOptimal()) {
+            doIteration(tableau);
+        }
+        return tableau.getSolution();
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/linear/SimplexTableau.java b/src/main/java/org/apache/commons/math/optimization/linear/SimplexTableau.java
new file mode 100644
index 0000000..c25619a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/linear/SimplexTableau.java
@@ -0,0 +1,589 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.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 org.apache.commons.math.linear.Array2DRowRealMatrix;
+import org.apache.commons.math.linear.MatrixUtils;
+import org.apache.commons.math.linear.RealMatrix;
+import org.apache.commons.math.linear.RealVector;
+import org.apache.commons.math.optimization.GoalType;
+import org.apache.commons.math.optimization.RealPointValuePair;
+import org.apache.commons.math.util.MathUtils;
+
+/**
+ * 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>
+ * @version $Revision: 922713 $ $Date: 2010-03-14 02:26:13 +0100 (dim. 14 mars 2010) $
+ * @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 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 in floating point comparisons. */
+    private final double epsilon;
+
+    /**
+     * 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 in floating point comparisons
+     */
+    SimplexTableau(final LinearObjectiveFunction f,
+                   final Collection<LinearConstraint> constraints,
+                   final GoalType goalType, final boolean restrictToNonNegative,
+                   final double epsilon) {
+        this.f                      = f;
+        this.constraints            = normalizeConstraints(constraints);
+        this.restrictToNonNegative  = restrictToNonNegative;
+        this.epsilon                = epsilon;
+        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.getData(), matrix.getDataRef()[zIndex]);
+        matrix.setEntry(zIndex, width - 1,
+            maximize ? f.getConstantTerm() : -1 * f.getConstantTerm());
+
+        if (!restrictToNonNegative) {
+            matrix.setEntry(zIndex, getSlackVariableOffset() - 1,
+                getInvertedCoeffiecientSum(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().getData(), matrix.getDataRef()[row]);
+
+            // x-
+            if (!restrictToNonNegative) {
+                matrix.setEntry(row, getSlackVariableOffset() - 1,
+                    getInvertedCoeffiecientSum(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>();
+        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 getInvertedCoeffiecientSum(final RealVector coefficients) {
+        double sum = 0;
+        for (double coefficient : coefficients.getData()) {
+            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++) {
+            if (MathUtils.equals(getEntry(i, col), 1.0, epsilon) && (row == null)) {
+                row = i;
+            } else if (!MathUtils.equals(getEntry(i, col), 0.0, epsilon)) {
+                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;
+        }
+
+        List<Integer> columnsToDrop = new ArrayList<Integer>();
+        columnsToDrop.add(0);
+
+        // positive cost non-artificial variables
+        for (int i = getNumObjectiveFunctions(); i < getArtificialVariableOffset(); i++) {
+          if (MathUtils.compareTo(tableau.getEntry(0, i), 0, 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);
+            }
+          }
+        }
+
+        for (int i = columnsToDrop.size() - 1; i >= 0; i--) {
+          columnLabels.remove((int) columnsToDrop.get(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++) {
+            if (MathUtils.compareTo(tableau.getEntry(0, i), 0, epsilon) < 0) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Get the current solution.
+     *
+     * @return current solution
+     */
+    protected RealPointValuePair 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 (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;
+          } else {
+              basicRows.add(basicRow);
+              coefficients[i] =
+                  (basicRow == null ? 0 : getEntry(basicRow, getRhsOffset())) -
+                  (restrictToNonNegative ? 0 : mostNegative);
+          }
+      }
+      return new RealPointValuePair(coefficients, f.getValue(coefficients));
+    }
+
+    /**
+     * Subtracts a multiple of one row from another.
+     * <p>
+     * After application of this operation, the following will hold:
+     *   minuendRow = minuendRow - multiple * subtrahendRow
+     * </p>
+     * @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:
+     *   minuendRow = minuendRow - multiple * subtrahendRow
+     * </p>
+     * @param minuendRow row index
+     * @param subtrahendRow row index
+     * @param multiple multiplication factor
+     */
+    protected void subtractRow(final int minuendRow, final int subtrahendRow,
+                               final double multiple) {
+        tableau.setRowVector(minuendRow, tableau.getRowVector(minuendRow)
+            .subtract(tableau.getRowVector(subtrahendRow).mapMultiply(multiple)));
+    }
+
+    /**
+     * 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.
+     * </p>
+     * @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) &&
+                 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() ^
+               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/math/optimization/linear/UnboundedSolutionException.java b/src/main/java/org/apache/commons/math/optimization/linear/UnboundedSolutionException.java
new file mode 100644
index 0000000..b769c32
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/linear/UnboundedSolutionException.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.math.optimization.linear;
+
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.optimization.OptimizationException;
+
+/**
+ * This class represents exceptions thrown by optimizers when a solution
+ * escapes to infinity.
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ * @since 2.0
+ */
+public class UnboundedSolutionException extends OptimizationException {
+
+    /** 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/math/optimization/linear/package.html b/src/main/java/org/apache/commons/math/optimization/linear/package.html
new file mode 100644
index 0000000..beb79a1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/linear/package.html
@@ -0,0 +1,22 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 758920 $ -->
+<body>
+This package provides optimization algorithms for linear constrained problems.
+</body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/optimization/package.html b/src/main/java/org/apache/commons/math/optimization/package.html
new file mode 100644
index 0000000..8550a80
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/package.html
@@ -0,0 +1,72 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 758074 $ -->
+<body>
+<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>
+
+<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.math.optimization.UnivariateRealOptimizer
+      UnivariateRealOptimizer} for {@link org.apache.commons.math.analysis.UnivariateRealFunction
+      univariate real functions}</li>
+  <li>{@link org.apache.commons.math.optimization.MultivariateRealOptimizer
+      MultivariateRealOptimizer} for {@link org.apache.commons.math.analysis.MultivariateRealFunction
+      multivariate real functions}</li>
+  <li>{@link org.apache.commons.math.optimization.DifferentiableMultivariateRealOptimizer
+      DifferentiableMultivariateRealOptimizer} for {@link
+      org.apache.commons.math.analysis.DifferentiableMultivariateRealFunction
+      differentiable multivariate real functions}</li>
+  <li>{@link org.apache.commons.math.optimization.DifferentiableMultivariateVectorialOptimizer
+      DifferentiableMultivariateVectorialOptimizer} for {@link
+      org.apache.commons.math.analysis.DifferentiableMultivariateVectorialFunction
+      differentiable multivariate vectorial functions}</li>
+</ul>
+</p>
+
+<p>
+Despite there are only four types of supported optimizers, it is possible to optimize a
+transform a {@link org.apache.commons.math.analysis.MultivariateVectorialFunction
+non-differentiable multivariate vectorial function} by converting it to a {@link
+org.apache.commons.math.analysis.MultivariateRealFunction non-differentiable multivariate
+real function} thanks to the {@link
+org.apache.commons.math.optimization.LeastSquaresConverter LeastSquaresConverter} helper class.
+The transformed function can be optimized using any implementation of the {@link
+org.apache.commons.math.optimization.MultivariateRealOptimizer MultivariateRealOptimizer} interface.
+</p>
+
+<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.
+</p>
+</body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/optimization/univariate/AbstractUnivariateRealOptimizer.java b/src/main/java/org/apache/commons/math/optimization/univariate/AbstractUnivariateRealOptimizer.java
new file mode 100644
index 0000000..399a1b2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/univariate/AbstractUnivariateRealOptimizer.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.math.optimization.univariate;
+
+import org.apache.commons.math.ConvergingAlgorithmImpl;
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.MaxEvaluationsExceededException;
+import org.apache.commons.math.MaxIterationsExceededException;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.exception.MathUnsupportedOperationException;
+import org.apache.commons.math.exception.NoDataException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.optimization.GoalType;
+import org.apache.commons.math.optimization.UnivariateRealOptimizer;
+
+/**
+ * Provide a default implementation for several functions useful to generic
+ * optimizers.
+ *
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ * @since 2.0
+ */
+public abstract class AbstractUnivariateRealOptimizer
+    extends ConvergingAlgorithmImpl implements UnivariateRealOptimizer {
+    /** Indicates where a root has been computed. */
+    protected boolean resultComputed;
+    /** The last computed root. */
+    protected double result;
+    /** Value of the function at the last computed result. */
+    protected double functionValue;
+    /** Maximal number of evaluations allowed. */
+    private int maxEvaluations;
+    /** Number of evaluations already performed. */
+    private int evaluations;
+    /** Optimization type */
+    private GoalType optimizationGoal;
+    /** 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 UnivariateRealFunction function;
+
+    /**
+     * Construct a solver with given iteration count and accuracy.
+     * @param defaultAbsoluteAccuracy maximum absolute error
+     * @param defaultMaximalIterationCount maximum number of iterations
+     * @throws IllegalArgumentException if f is null or the
+     * defaultAbsoluteAccuracy is not valid
+     * @deprecated in 2.2. Please use the "setter" methods to assign meaningful
+     * values to the maximum numbers of iterations and evaluations, and to the
+     * absolute and relative accuracy thresholds.
+     */
+    @Deprecated
+    protected AbstractUnivariateRealOptimizer(final int defaultMaximalIterationCount,
+                                              final double defaultAbsoluteAccuracy) {
+        super(defaultMaximalIterationCount, defaultAbsoluteAccuracy);
+        resultComputed = false;
+        setMaxEvaluations(Integer.MAX_VALUE);
+    }
+
+    /**
+     * Default constructor.
+     * To be removed once the single non-default one has been removed.
+     */
+    protected AbstractUnivariateRealOptimizer() {}
+
+    /**
+     * Check whether a result has been computed.
+     * @throws NoDataException if no result has been computed
+     * @deprecated in 2.2 (no alternative).
+     */
+    @Deprecated
+    protected void checkResultComputed() {
+        if (!resultComputed) {
+            throw new NoDataException();
+        }
+    }
+
+    /** {@inheritDoc} */
+    public double getResult() {
+        if (!resultComputed) {
+            throw new NoDataException();
+        }
+        return result;
+    }
+
+    /** {@inheritDoc} */
+    public double getFunctionValue() throws FunctionEvaluationException {
+        if (Double.isNaN(functionValue)) {
+            final double opt = getResult();
+            functionValue = function.value(opt);
+        }
+        return functionValue;
+    }
+
+    /**
+     * Convenience function for implementations.
+     *
+     * @param x the result to set
+     * @param fx the result to set
+     * @param iterationCount the iteration count to set
+     * @deprecated in 2.2 (no alternative).
+     */
+    @Deprecated
+    protected final void setResult(final double x, final double fx,
+                                   final int iterationCount) {
+        this.result         = x;
+        this.functionValue  = fx;
+        this.iterationCount = iterationCount;
+        this.resultComputed = true;
+    }
+
+    /**
+     * Convenience function for implementations.
+     * @deprecated in 2.2 (no alternative).
+     */
+    @Deprecated
+    protected final void clearResult() {
+        this.resultComputed = false;
+    }
+
+    /** {@inheritDoc} */
+    public void setMaxEvaluations(int maxEvaluations) {
+        this.maxEvaluations = maxEvaluations;
+    }
+
+    /** {@inheritDoc} */
+    public int getMaxEvaluations() {
+        return maxEvaluations;
+    }
+
+    /** {@inheritDoc} */
+    public int getEvaluations() {
+        return evaluations;
+    }
+
+    /**
+     * @return the optimization type.
+     */
+    public GoalType getGoalType() {
+        return optimizationGoal;
+    }
+    /**
+     * @return the lower of the search interval.
+     */
+    public double getMin() {
+        return searchMin;
+    }
+    /**
+     * @return the higher of the search interval.
+     */
+    public double getMax() {
+        return searchMax;
+    }
+    /**
+     * @return the initial guess.
+     */
+    public double getStartValue() {
+        return searchStart;
+    }
+
+    /**
+     * Compute the objective function value.
+     * @param f objective function
+     * @param point point at which the objective function must be evaluated
+     * @return objective function value at specified point
+     * @exception FunctionEvaluationException if the function cannot be evaluated
+     * or the maximal number of iterations is exceeded
+     * @deprecated in 2.2. Use this {@link #computeObjectiveValue(double)
+     * replacement} instead.
+     */
+    @Deprecated
+    protected double computeObjectiveValue(final UnivariateRealFunction f,
+                                           final double point)
+        throws FunctionEvaluationException {
+        if (++evaluations > maxEvaluations) {
+            throw new FunctionEvaluationException(new MaxEvaluationsExceededException(maxEvaluations), point);
+        }
+        return f.value(point);
+    }
+
+    /**
+     * Compute the objective function value.
+     *
+     * @param point Point at which the objective function must be evaluated.
+     * @return the objective function value at specified point.
+     * @exception FunctionEvaluationException if the function cannot be evaluated
+     * or the maximal number of iterations is exceeded.
+     */
+    protected double computeObjectiveValue(double point)
+        throws FunctionEvaluationException {
+        if (++evaluations > maxEvaluations) {
+            resultComputed = false;
+            throw new FunctionEvaluationException(new MaxEvaluationsExceededException(maxEvaluations), point);
+        }
+        return function.value(point);
+    }
+
+    /** {@inheritDoc} */
+    public double optimize(UnivariateRealFunction f, GoalType goal,
+                           double min, double max, double startValue)
+        throws MaxIterationsExceededException, FunctionEvaluationException {
+        // Initialize.
+        this.searchMin = min;
+        this.searchMax = max;
+        this.searchStart = startValue;
+        this.optimizationGoal = goal;
+        this.function = f;
+
+        // Reset.
+        functionValue = Double.NaN;
+        evaluations = 0;
+        resetIterationsCounter();
+
+        // Perform computation.
+        result = doOptimize();
+        resultComputed = true;
+
+        return result;
+    }
+
+    /**
+     * Set the value at the optimum.
+     *
+     * @param functionValue Value of the objective function at the optimum.
+     */
+    protected void setFunctionValue(double functionValue) {
+        this.functionValue = functionValue;
+    }
+
+    /** {@inheritDoc} */
+    public double optimize(UnivariateRealFunction f, GoalType goal,
+                           double min, double max)
+        throws MaxIterationsExceededException, FunctionEvaluationException {
+        return optimize(f, goal, min, max, min + 0.5 * (max - min));
+    }
+
+    /**
+     * Method for implementing actual optimization algorithms in derived
+     * classes.
+     *
+     * From version 3.0 onwards, this method will be abstract - i.e.
+     * concrete implementations will have to implement it.  If this method
+     * is not implemented, subclasses must override
+     * {@link #optimize(UnivariateRealFunction, GoalType, double, double)}.
+     *
+     * @return the optimum.
+     * @throws MaxIterationsExceededException if the maximum iteration count
+     * is exceeded.
+     * @throws FunctionEvaluationException if an error occurs evaluating
+     * the function.
+     */
+    protected double doOptimize()
+        throws MaxIterationsExceededException, FunctionEvaluationException {
+        throw new MathUnsupportedOperationException(LocalizedFormats.NOT_OVERRIDEN);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/univariate/BracketFinder.java b/src/main/java/org/apache/commons/math/optimization/univariate/BracketFinder.java
new file mode 100644
index 0000000..18e7015
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/univariate/BracketFinder.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.math.optimization.univariate;
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.exception.NotStrictlyPositiveException;
+import org.apache.commons.math.MaxIterationsExceededException;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.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).
+ * @version $Revision$ $Date$
+ * @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;
+    /**
+     * Maximum number of iterations.
+     */
+    private final int maxIterations;
+    /**
+     * Number of iterations.
+     */
+    private int iterations;
+    /**
+     * Number of function evaluations.
+     */
+    private int 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, 50} (see the
+     * {@link #BracketFinder(double,int) other constructor}).
+     */
+    public BracketFinder() {
+        this(100, 50);
+    }
+
+    /**
+     * Create a bracketing interval finder.
+     *
+     * @param growLimit Expanding factor.
+     * @param maxIterations Maximum number of iterations allowed for finding
+     * a bracketing interval.
+     */
+    public BracketFinder(double growLimit,
+                         int maxIterations) {
+        if (growLimit <= 0) {
+            throw new NotStrictlyPositiveException(growLimit);
+        }
+        if (maxIterations <= 0) {
+            throw new NotStrictlyPositiveException(maxIterations);
+        }
+
+        this.growLimit = growLimit;
+        this.maxIterations = maxIterations;
+    }
+
+    /**
+     * Search new points that bracket a local optimum of the function.
+     *
+     * @param func Function whose optimum should be bracketted.
+     * @param goal {@link GoalType Goal type}.
+     * @param xA Initial point.
+     * @param xB Initial point.
+     * @throws MaxIterationsExceededException if the maximum iteration count
+     * is exceeded.
+     * @throws FunctionEvaluationException if an error occurs evaluating the function.
+     */
+    public void search(UnivariateRealFunction func,
+                       GoalType goal,
+                       double xA,
+                       double xB)
+        throws MaxIterationsExceededException, FunctionEvaluationException {
+        reset();
+        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) {
+            if (++iterations > maxIterations) {
+                throw new MaxIterationsExceededException(maxIterations);
+            }
+
+            double tmp1 = (xB - xA) * (fB - fC);
+            double tmp2 = (xB - xC) * (fB - fA);
+
+            double val = tmp2 - tmp1;
+            double denom = Math.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;
+            xB = xC;
+            xC = w;
+            fA = fB;
+            fB = fC;
+            fC = fW;
+        }
+
+        lo = xA;
+        mid = xB;
+        hi = xC;
+        fLo = fA;
+        fMid = fB;
+        fHi = fC;
+    }
+
+    /**
+     * @return the number of iterations.
+     */
+    public int getIterations() {
+        return iterations;
+    }
+    /**
+     * @return the number of evaluations.
+     */
+    public int getEvaluations() {
+        return evaluations;
+    }
+
+    /**
+     * @return the lower bound of the bracket.
+     * @see #getFLow()
+     */
+    public double getLo() {
+        return lo;
+    }
+
+    /**
+     * Get function value at {@link #getLo()}.
+     * @return function value at {@link #getLo()}
+     */
+    public double getFLow() {
+        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 FunctionEvaluationException if function cannot be evaluated at x
+     */
+    private double eval(UnivariateRealFunction f, double x)
+        throws FunctionEvaluationException {
+
+        ++evaluations;
+        return f.value(x);
+    }
+
+    /**
+     * Reset internal state.
+     */
+    private void reset() {
+        iterations = 0;
+        evaluations = 0;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/univariate/BrentOptimizer.java b/src/main/java/org/apache/commons/math/optimization/univariate/BrentOptimizer.java
new file mode 100644
index 0000000..73d1d8c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/univariate/BrentOptimizer.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.math.optimization.univariate;
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.MaxIterationsExceededException;
+import org.apache.commons.math.exception.NotStrictlyPositiveException;
+import org.apache.commons.math.optimization.GoalType;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Implements Richard Brent's algorithm (from his book "Algorithms for
+ * Minimization without Derivatives", p. 79) for finding minima of real
+ * univariate functions. This implementation is an adaptation partly
+ * based on the Python code from SciPy (module "optimize.py" v0.5).
+ *
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ * @since 2.0
+ */
+public class BrentOptimizer extends AbstractUnivariateRealOptimizer {
+    /**
+     * Golden section.
+     */
+    private static final double GOLDEN_SECTION = 0.5 * (3 - FastMath.sqrt(5));
+
+    /**
+     * Construct a solver.
+     */
+    public BrentOptimizer() {
+        setMaxEvaluations(1000);
+        setMaximalIterationCount(100);
+        setAbsoluteAccuracy(1e-11);
+        setRelativeAccuracy(1e-9);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    protected double doOptimize()
+        throws MaxIterationsExceededException, FunctionEvaluationException {
+        return localMin(getGoalType() == GoalType.MINIMIZE,
+                        getMin(), getStartValue(), getMax(),
+                        getRelativeAccuracy(), getAbsoluteAccuracy());
+    }
+
+    /**
+     * Find the minimum of the function within the interval {@code (lo, hi)}.
+     *
+     * If the function is defined on the interval {@code (lo, hi)}, then
+     * this method finds an approximation {@code x} to the point at which
+     * the function attains its minimum.<br/>
+     * {@code t} and {@code eps} define a tolerance {@code tol = eps |x| + t}
+     * and the function is never evaluated at two points closer together than
+     * {@code tol}. {@code eps} should be no smaller than <em>2 macheps</em> and
+     * preferable not much less than <em>sqrt(macheps)</em>, where
+     * <em>macheps</em> is the relative machine precision. {@code t} should be
+     * positive.
+     * @param isMinim {@code true} when minimizing the function.
+     * @param lo Lower bound of the interval.
+     * @param mid Point inside the interval {@code [lo, hi]}.
+     * @param hi Higher bound of the interval.
+     * @param eps Relative accuracy.
+     * @param t Absolute accuracy.
+     * @return the optimum point.
+     * @throws MaxIterationsExceededException if the maximum iteration count
+     * is exceeded.
+     * @throws FunctionEvaluationException if an error occurs evaluating the function.
+     */
+    private double localMin(boolean isMinim,
+                            double lo, double mid, double hi,
+                            double eps, double t)
+        throws MaxIterationsExceededException, FunctionEvaluationException {
+        if (eps <= 0) {
+            throw new NotStrictlyPositiveException(eps);
+        }
+        if (t <= 0) {
+            throw new NotStrictlyPositiveException(t);
+        }
+        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;
+
+        while (true) {
+            double m = 0.5 * (a + b);
+            final double tol1 = eps * FastMath.abs(x) + t;
+            final double tol2 = 2 * tol1;
+
+            // Check stopping criterion.
+            if (FastMath.abs(x - m) > tol2 - 0.5 * (b - a)) {
+                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;
+                }
+
+                // 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 || w == x) {
+                        v = w;
+                        fv = fw;
+                        w = u;
+                        fw = fu;
+                    } else if (fu <= fv || v == x || v == w) {
+                        v = u;
+                        fv = fu;
+                    }
+                }
+            } else { // termination
+                setFunctionValue(isMinim ? fx : -fx);
+                return x;
+            }
+            incrementIterationsCounter();
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/optimization/univariate/package.html b/src/main/java/org/apache/commons/math/optimization/univariate/package.html
new file mode 100644
index 0000000..d792fc0
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/univariate/package.html
@@ -0,0 +1,22 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $ -->
+    <body>
+     Univariate real functions minimum finding algorithms.
+    </body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/package.html b/src/main/java/org/apache/commons/math/package.html
new file mode 100644
index 0000000..bdf939c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/package.html
@@ -0,0 +1,20 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 480440 $ $Date: 2006-11-29 08:14:12 +0100 (mer. 29 nov. 2006) $ -->
+    <body>Common classes used throughout the commons-math library.</body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/random/AbstractRandomGenerator.java b/src/main/java/org/apache/commons/math/random/AbstractRandomGenerator.java
new file mode 100644
index 0000000..afec63e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/random/AbstractRandomGenerator.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.math.random;
+
+import org.apache.commons.math.exception.NotStrictlyPositiveException;
+import org.apache.commons.math.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.</p>
+ *
+ * @since 1.1
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ */
+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}. Implemementations 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 underyling 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.</p>
+     *
+     * @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}.</p>
+     *
+     * @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 = 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></p>
+     *
+     * @return the next pseudorandom, uniformly distributed {@code int}
+     *  value from this random number generator's sequence
+     */
+    public int nextInt() {
+        return (int) (nextDouble() * 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></p>
+     *
+     * @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></p>
+     *
+     * @return  the next pseudorandom, uniformly distributed {@code long}
+     *value from this random number generator's sequence
+     */
+    public long nextLong() {
+        return (long) (nextDouble() * 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></p>
+     *
+     * @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></p>
+     *
+     * @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.</p>
+     *
+     * @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>
+     * <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)}.</p>
+     *
+     * @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/math/random/AbstractWell.java b/src/main/java/org/apache/commons/math/random/AbstractWell.java
new file mode 100644
index 0000000..96e18a2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/random/AbstractWell.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.math.random;
+
+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>.</p>
+
+ * @see <a href="http://www.iro.umontreal.ca/~panneton/WELLRNG.html">WELL Random number generator</a>
+ * @version $Revision: 1003892 $ $Date: 2010-10-02 23:28:56 +0200 (sam. 02 oct. 2010) $
+ * @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 as the
+     * seed.</p>
+     * @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, System.currentTimeMillis());
+    }
+
+    /** 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.</p>
+     * @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.</p>
+     * @param seed the initial seed (32 bits integers array), if null
+     * the seed of the generator will be related to the current time
+     */
+    @Override
+    public void setSeed(final int[] seed) {
+
+        if (seed == null) {
+            setSeed(System.currentTimeMillis());
+            return;
+        }
+
+        System.arraycopy(seed, 0, v, 0, Math.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;
+
+    }
+
+    /** 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.</p>
+     * @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/math/random/BitsStreamGenerator.java b/src/main/java/org/apache/commons/math/random/BitsStreamGenerator.java
new file mode 100644
index 0000000..8979473
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/random/BitsStreamGenerator.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.math.random;
+
+import org.apache.commons.math.exception.NotStrictlyPositiveException;
+import org.apache.commons.math.util.FastMath;
+
+/** Base class for random number generators that generates bits streams.
+
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ * @since 2.0
+
+ */
+public abstract class BitsStreamGenerator implements RandomGenerator {
+
+    /** 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()}.</p>
+     * @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 void nextBytes(byte[] bytes) {
+        int i = 0;
+        final int iEnd = bytes.length - 3;
+        while (i < iEnd) {
+            final int random = next(32);
+            bytes[i]     = (byte) (random & 0xff);
+            bytes[i + 1] = (byte) ((random >>  8) & 0xff);
+            bytes[i + 2] = (byte) ((random >> 16) & 0xff);
+            bytes[i + 3] = (byte) ((random >> 24) & 0xff);
+            i += 4;
+        }
+        int random = next(32);
+        while (i < bytes.length) {
+            bytes[i++] = (byte) (random & 0xff);
+            random     = random >> 8;
+        }
+    }
+
+    /** {@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} */
+    public int nextInt(int n) throws IllegalArgumentException {
+
+        if (n < 1) {
+            throw new NotStrictlyPositiveException(n);
+        }
+
+        // find bit mask for n
+        int mask = n;
+        mask |= mask >> 1;
+        mask |= mask >> 2;
+        mask |= mask >> 4;
+        mask |= mask >> 8;
+        mask |= mask >> 16;
+
+        while (true) {
+            final int random = next(32) & mask;
+            if (random < n) {
+                return random;
+            }
+        }
+
+    }
+
+    /** {@inheritDoc} */
+    public long nextLong() {
+        final long high  = ((long) next(32)) << 32;
+        final long  low  = ((long) next(32)) & 0xffffffffL;
+        return high | low;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/random/CorrelatedRandomVectorGenerator.java b/src/main/java/org/apache/commons/math/random/CorrelatedRandomVectorGenerator.java
new file mode 100644
index 0000000..f1df51d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/random/CorrelatedRandomVectorGenerator.java
@@ -0,0 +1,304 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.random;
+
+import org.apache.commons.math.DimensionMismatchException;
+import org.apache.commons.math.linear.MatrixUtils;
+import org.apache.commons.math.linear.NotPositiveDefiniteMatrixException;
+import org.apache.commons.math.linear.RealMatrix;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * 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>
+ * <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>
+ * <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.</p>
+ *
+ * @version $Revision: 1043908 $ $Date: 2010-12-09 12:53:14 +0100 (jeu. 09 déc. 2010) $
+ * @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;
+
+    /** Permutated Cholesky root of the covariance matrix. */
+    private RealMatrix root;
+
+    /** Rank of the covariance matrix. */
+    private int rank;
+
+    /** Simple constructor.
+     * <p>Build a correlated random vector generator from its mean
+     * vector and covariance matrix.</p>
+     * @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
+     * @exception IllegalArgumentException if there is a dimension
+     * mismatch between the mean vector and the covariance matrix
+     * @exception NotPositiveDefiniteMatrixException if the
+     * covariance matrix is not strictly positive definite
+     * @exception DimensionMismatchException if the mean and covariance
+     * arrays dimensions don't match
+     */
+    public CorrelatedRandomVectorGenerator(double[] mean,
+                                           RealMatrix covariance, double small,
+                                           NormalizedRandomGenerator generator)
+    throws NotPositiveDefiniteMatrixException, DimensionMismatchException {
+
+        int order = covariance.getRowDimension();
+        if (mean.length != order) {
+            throw new DimensionMismatchException(mean.length, order);
+        }
+        this.mean = mean.clone();
+
+        decompose(covariance, small);
+
+        this.generator = generator;
+        normalized = new double[rank];
+
+    }
+
+    /** Simple constructor.
+     * <p>Build a null mean random correlated vector generator from its
+     * covariance matrix.</p>
+     * @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
+     * @exception NotPositiveDefiniteMatrixException if the
+     * covariance matrix is not strictly positive definite
+     */
+    public CorrelatedRandomVectorGenerator(RealMatrix covariance, double small,
+                                           NormalizedRandomGenerator generator)
+    throws NotPositiveDefiniteMatrixException {
+
+        int order = covariance.getRowDimension();
+        mean = new double[order];
+        for (int i = 0; i < order; ++i) {
+            mean[i] = 0;
+        }
+
+        decompose(covariance, small);
+
+        this.generator = generator;
+        normalized = new double[rank];
+
+    }
+
+    /** Get the underlying normalized components generator.
+     * @return underlying uncorrelated components generator
+     */
+    public NormalizedRandomGenerator getGenerator() {
+        return generator;
+    }
+
+    /** 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 covariance matrix.
+     * The rank is the number of independent rows in the covariance
+     * matrix, it is also the number of columns of the rectangular
+     * matrix of the decomposition.
+     * @return rank of the square matrix.
+     * @see #getRootMatrix()
+     */
+    public int getRank() {
+        return rank;
+    }
+
+    /** Decompose the original square matrix.
+     * <p>The decomposition is based on a Choleski decomposition
+     * where additional transforms are performed:
+     * <ul>
+     *   <li>the rows of the decomposed matrix are permuted</li>
+     *   <li>columns with the too small diagonal element are discarded</li>
+     *   <li>the matrix is permuted</li>
+     * </ul>
+     * This means that rather than computing M = U<sup>T</sup>.U where U
+     * is an upper triangular matrix, this method computed M=B.B<sup>T</sup>
+     * where B is a rectangular matrix.
+     * @param covariance covariance matrix
+     * @param small diagonal elements threshold under which  column are
+     * considered to be dependent on previous ones and are discarded
+     * @exception NotPositiveDefiniteMatrixException if the
+     * covariance matrix is not strictly positive definite
+     */
+    private void decompose(RealMatrix covariance, double small)
+    throws NotPositiveDefiniteMatrixException {
+
+        int order = covariance.getRowDimension();
+        double[][] c = covariance.getData();
+        double[][] b = new double[order][order];
+
+        int[] swap  = new int[order];
+        int[] index = new int[order];
+        for (int i = 0; i < order; ++i) {
+            index[i] = i;
+        }
+
+        rank = 0;
+        for (boolean loop = true; loop;) {
+
+            // find maximal diagonal element
+            swap[rank] = rank;
+            for (int i = rank + 1; i < order; ++i) {
+                int ii  = index[i];
+                int isi = index[swap[i]];
+                if (c[ii][ii] > c[isi][isi]) {
+                    swap[rank] = i;
+                }
+            }
+
+
+            // swap elements
+            if (swap[rank] != rank) {
+                int tmp = index[rank];
+                index[rank] = index[swap[rank]];
+                index[swap[rank]] = tmp;
+            }
+
+            // check diagonal element
+            int ir = index[rank];
+            if (c[ir][ir] < small) {
+
+                if (rank == 0) {
+                    throw new NotPositiveDefiniteMatrixException();
+                }
+
+                // check remaining diagonal elements
+                for (int i = rank; i < order; ++i) {
+                    if (c[index[i]][index[i]] < -small) {
+                        // there is at least one sufficiently negative diagonal element,
+                        // the covariance matrix is wrong
+                        throw new NotPositiveDefiniteMatrixException();
+                    }
+                }
+
+                // all remaining diagonal elements are close to zero,
+                // we consider we have found the rank of the covariance matrix
+                ++rank;
+                loop = false;
+
+            } else {
+
+                // transform the matrix
+                double sqrt = FastMath.sqrt(c[ir][ir]);
+                b[rank][rank] = sqrt;
+                double inverse = 1 / sqrt;
+                for (int i = rank + 1; i < order; ++i) {
+                    int ii = index[i];
+                    double e = inverse * c[ii][ir];
+                    b[i][rank] = e;
+                    c[ii][ii] -= e * e;
+                    for (int j = rank + 1; j < i; ++j) {
+                        int ij = index[j];
+                        double f = c[ii][ij] - e * b[j][rank];
+                        c[ii][ij] = f;
+                        c[ij][ii] = f;
+                    }
+                }
+
+                // prepare next iteration
+                loop = ++rank < order;
+
+            }
+
+        }
+
+        // build the root matrix
+        root = MatrixUtils.createRealMatrix(order, rank);
+        for (int i = 0; i < order; ++i) {
+            for (int j = 0; j < rank; ++j) {
+                root.setEntry(index[i], j, b[i][j]);
+            }
+        }
+
+    }
+
+    /** 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 < rank; ++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 < rank; ++j) {
+                correlated[i] += root.getEntry(i, j) * normalized[j];
+            }
+        }
+
+        return correlated;
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/random/EmpiricalDistribution.java b/src/main/java/org/apache/commons/math/random/EmpiricalDistribution.java
new file mode 100644
index 0000000..7f08a06
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/random/EmpiricalDistribution.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.random;
+
+import java.io.IOException;
+import java.io.File;
+import java.net.URL;
+import java.util.List;
+
+import org.apache.commons.math.stat.descriptive.StatisticalSummary;
+import org.apache.commons.math.stat.descriptive.SummaryStatistics;
+
+/**
+ * Represents an <a href="http://random.mat.sbg.ac.at/~ste/dipl/node11.html">
+ * 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>
+ * Implementations of this interface maintain 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>
+ * <li>dividing the input data into "bin ranges" and reporting bin frequency
+ *     counts (data for histogram)</li>
+ * <li>reporting univariate statistics describing the full set of data values
+ *     as well as the observations within each bin</li>
+ * <li>generating random values from the distribution</li>
+ * </ul>
+ * Applications can use <code>EmpiricalDistribution</code> implementations 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>
+ *
+ * @version $Revision: 817128 $ $Date: 2009-09-21 03:30:53 +0200 (lun. 21 sept. 2009) $
+ */
+public interface EmpiricalDistribution {
+
+    /**
+     * Computes the empirical distribution from the provided
+     * array of numbers.
+     *
+     * @param dataArray the data array
+     */
+    void load(double[] dataArray);
+
+    /**
+     * Computes the empirical distribution from the input file.
+     *
+     * @param file the input file
+     * @throws IOException if an IO error occurs
+     */
+    void load(File file) throws IOException;
+
+    /**
+     * Computes the empirical distribution using data read from a URL.
+     *
+     * @param url url of the input file
+     * @throws IOException if an IO error occurs
+     */
+    void load(URL url) throws IOException;
+
+    /**
+     * Generates a random value from this distribution.
+     * <strong>Preconditions:</strong><ul>
+     * <li>the distribution must be loaded before invoking this method</li></ul>
+     * @return the random value.
+     *
+     * @throws IllegalStateException if the distribution has not been loaded
+     */
+    double getNextValue() throws IllegalStateException;
+
+
+    /**
+     * Returns a
+     * {@link org.apache.commons.math.stat.descriptive.StatisticalSummary}
+     * describing this distribution.
+     * <strong>Preconditions:</strong><ul>
+     * <li>the distribution must be loaded before invoking this method</li>
+     * </ul>
+     *
+     * @return the sample statistics
+     * @throws IllegalStateException if the distribution has not been loaded
+     */
+    StatisticalSummary getSampleStats() throws IllegalStateException;
+
+    /**
+     * Property indicating whether or not the distribution has been loaded.
+     *
+     * @return true if the distribution has been loaded
+     */
+    boolean isLoaded();
+
+     /**
+     * Returns the number of bins.
+     *
+     * @return the number of bins
+     */
+    int getBinCount();
+
+    /**
+     * Returns a list of
+     * {@link org.apache.commons.math.stat.descriptive.SummaryStatistics}
+     * containing statistics describing the values in each of the bins.  The
+     * List is indexed on the bin number.
+     *
+     * @return List of bin statistics
+     */
+    List<SummaryStatistics> getBinStats();
+
+    /**
+     * Returns 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].
+     *
+     * @return array of bin upper bounds
+     */
+    double[] getUpperBounds();
+
+}
diff --git a/src/main/java/org/apache/commons/math/random/EmpiricalDistributionImpl.java b/src/main/java/org/apache/commons/math/random/EmpiricalDistributionImpl.java
new file mode 100644
index 0000000..a3ae8a7
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/random/EmpiricalDistributionImpl.java
@@ -0,0 +1,479 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.random;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Serializable;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.stat.descriptive.StatisticalSummary;
+import org.apache.commons.math.stat.descriptive.SummaryStatistics;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Implements <code>EmpiricalDistribution</code> interface.  This 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>
+ * <li>Divide the range from min-max into <code>binCount</code> "bins."</li>
+ * <li>Pass the data file again, computing bin counts and univariate
+ *     statistics (mean, std dev.) for each of the bins </li>
+ * <li>Divide the interval (0,1) into subintervals associated with the bins,
+ *     with the length of a bin's subinterval proportional to its count.</li></ol>
+ * <strong>Generating random values from the distribution</strong><ol>
+ * <li>Generate a uniformly distributed value in (0,1) </li>
+ * <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.</li></ol></p><p>
+ *<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>
+ *<li>The input file <i>must</i> be a plain text file containing one valid numeric
+ *    entry per line.</li>
+ * </ul></p>
+ *
+ * @version $Revision: 1003886 $ $Date: 2010-10-02 23:04:44 +0200 (sam. 02 oct. 2010) $
+ */
+public class EmpiricalDistributionImpl implements Serializable, EmpiricalDistribution {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 5729073523949762654L;
+
+    /** 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;
+
+    /** RandomData instance to use in repeated calls to getNext() */
+    private final RandomData randomData = new RandomDataImpl();
+
+    /**
+     * Creates a new EmpiricalDistribution with the default bin count.
+     */
+    public EmpiricalDistributionImpl() {
+        binCount = 1000;
+        binStats = new ArrayList<SummaryStatistics>();
+    }
+
+    /**
+     * Creates a new EmpiricalDistribution  with the specified bin count.
+     *
+     * @param binCount number of bins
+     */
+    public EmpiricalDistributionImpl(int binCount) {
+        this.binCount = binCount;
+        binStats = new ArrayList<SummaryStatistics>();
+    }
+
+     /**
+     * Computes the empirical distribution from the provided
+     * array of numbers.
+     *
+     * @param in the input data array
+     */
+    public void load(double[] in) {
+        DataAdapter da = new ArrayDataAdapter(in);
+        try {
+            da.computeStats();
+            fillBinStats(in);
+        } catch (IOException e) {
+            throw new MathRuntimeException(e);
+        }
+        loaded = true;
+
+    }
+
+    /**
+     * Computes the empirical distribution using data read from a URL.
+     * @param url  url of the input file
+     *
+     * @throws IOException if an IO error occurs
+     */
+    public void load(URL url) throws IOException {
+        BufferedReader in =
+            new BufferedReader(new InputStreamReader(url.openStream()));
+        try {
+            DataAdapter da = new StreamDataAdapter(in);
+            da.computeStats();
+            if (sampleStats.getN() == 0) {
+                throw MathRuntimeException.createEOFException(LocalizedFormats.URL_CONTAINS_NO_DATA,
+                                                              url);
+            }
+            in = new BufferedReader(new InputStreamReader(url.openStream()));
+            fillBinStats(in);
+            loaded = true;
+        } finally {
+           try {
+               in.close();
+           } catch (IOException ex) {
+               // ignore
+           }
+        }
+    }
+
+    /**
+     * Computes the empirical distribution from the input file.
+     *
+     * @param file the input file
+     * @throws IOException if an IO error occurs
+     */
+    public void load(File file) throws IOException {
+        BufferedReader in = new BufferedReader(new FileReader(file));
+        try {
+            DataAdapter da = new StreamDataAdapter(in);
+            da.computeStats();
+            in = new BufferedReader(new FileReader(file));
+            fillBinStats(in);
+            loaded = true;
+        } finally {
+            try {
+                in.close();
+            } catch (IOException ex) {
+                // 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;
+
+    }
+
+    /**
+     * Factory of <code>DataAdapter</code> objects. For every supported source
+     * of data (array of doubles, file, etc.) an instance of the proper object
+     * is returned.
+     */
+    private class DataAdapterFactory{
+        /**
+         * Creates a DataAdapter from a data object
+         *
+         * @param in object providing access to the data
+         * @return DataAdapter instance
+         */
+        public DataAdapter getAdapter(Object in) {
+            if (in instanceof BufferedReader) {
+                BufferedReader inputStream = (BufferedReader) in;
+                return new StreamDataAdapter(inputStream);
+            } else if (in instanceof double[]) {
+                double[] inputArray = (double[]) in;
+                return new ArrayDataAdapter(inputArray);
+            } else {
+                throw MathRuntimeException.createIllegalArgumentException(
+                      LocalizedFormats.INPUT_DATA_FROM_UNSUPPORTED_DATASOURCE,
+                      in.getClass().getName(),
+                      BufferedReader.class.getName(), double[].class.getName());
+            }
+        }
+    }
+    /**
+     * <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
+         */
+        public 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.valueOf(str).doubleValue();
+                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
+         */
+        public ArrayDataAdapter(double[] in){
+            super();
+            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 in object providing access to the data
+     * @throws IOException  if an IO error occurs
+     */
+    private void fillBinStats(Object in) throws IOException {
+        // Set up grid
+        min = sampleStats.getMin();
+        max = sampleStats.getMax();
+        delta = (max - min)/(Double.valueOf(binCount)).doubleValue();
+
+        // 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
+        DataAdapterFactory aFactory = new DataAdapterFactory();
+        DataAdapter da = aFactory.getAdapter(in);
+        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.
+     *
+     * @return the random value.
+     * @throws IllegalStateException if the distribution has not been loaded
+     */
+    public double getNextValue() throws IllegalStateException {
+
+        if (!loaded) {
+            throw MathRuntimeException.createIllegalStateException(LocalizedFormats.DISTRIBUTION_NOT_LOADED);
+        }
+
+        // Start with a uniformly distributed random number in (0,1)
+        double x = FastMath.random();
+
+        // Use this to select the bin and generate a Gaussian within the bin
+        for (int i = 0; i < binCount; i++) {
+           if (x <= upperBounds[i]) {
+               SummaryStatistics stats = binStats.get(i);
+               if (stats.getN() > 0) {
+                   if (stats.getStandardDeviation() > 0) {  // more than one obs
+                        return randomData.nextGaussian
+                            (stats.getMean(),stats.getStandardDeviation());
+                   } else {
+                       return stats.getMean(); // only one obs in bin
+                   }
+               }
+           }
+        }
+        throw new MathRuntimeException(LocalizedFormats.NO_BIN_SELECTED);
+    }
+
+    /**
+     * Returns a {@link StatisticalSummary} describing this distribution.
+     * <strong>Preconditions:</strong><ul>
+     * <li>the distribution must be loaded before invoking this method</li></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;
+    }
+
+    /**
+     * <p>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>
+     *
+     * <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()}.</p>
+     *
+     * @return array of bin upper bounds
+     * @since 2.1
+     */
+    public double[] getUpperBounds() {
+        double[] binUpperBounds = new double[binCount];
+        binUpperBounds[0] = min + delta;
+        for (int i = 1; i < binCount - 1; i++) {
+            binUpperBounds[i] = binUpperBounds[i-1] + delta;
+        }
+        binUpperBounds[binCount - 1] = max;
+        return binUpperBounds;
+    }
+
+    /**
+     * <p>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.</p>
+     *
+     * <p>In versions 1.0-2.0 of commons-math, this array was (incorrectly) returned
+     * by {@link #getUpperBounds()}.</p>
+     *
+     * @since 2.1
+     * @return array of upper bounds of subintervals used in data generation
+     */
+    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;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/random/GaussianRandomGenerator.java b/src/main/java/org/apache/commons/math/random/GaussianRandomGenerator.java
new file mode 100644
index 0000000..6bd9775
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/random/GaussianRandomGenerator.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.math.random;
+
+/**
+ * This class is a gaussian normalized random generator for scalars.
+ * <p>This class is a simple wrapper around the {@link
+ * RandomGenerator#nextGaussian} method.</p>
+ * @version $Revision: 811827 $ $Date: 2009-09-06 17:32:50 +0200 (dim. 06 sept. 2009) $
+ * @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/math/random/JDKRandomGenerator.java b/src/main/java/org/apache/commons/math/random/JDKRandomGenerator.java
new file mode 100644
index 0000000..573ef61
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/random/JDKRandomGenerator.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.math.random;
+
+import java.util.Random;
+
+/**
+ * Extension of <code>java.util.Random</code> to implement
+ * {@link RandomGenerator}.
+ *
+ * @since 1.1
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+public class JDKRandomGenerator extends Random implements RandomGenerator {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = -7745277476784028798L;
+
+    /** {@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);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/random/MersenneTwister.java b/src/main/java/org/apache/commons/math/random/MersenneTwister.java
new file mode 100644
index 0000000..6a6fadd
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/random/MersenneTwister.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.math.random;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.util.FastMath;
+
+
+/** 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>
+
+ * <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>
+
+ * <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:</p>
+
+ * <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>
+
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ * @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 as the
+     * seed.</p>
+     */
+    public MersenneTwister() {
+        mt = new int[N];
+        setSeed(System.currentTimeMillis());
+    }
+
+    /** 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.</p>
+     * @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;
+        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;
+        }
+    }
+
+    /** 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.</p>
+     * @param seed the initial seed (32 bits integers array), if null
+     * the seed of the generator will be related to the current time
+     */
+    @Override
+    public void setSeed(int[] seed) {
+
+        if (seed == null) {
+            setSeed(System.currentTimeMillis());
+            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
+
+    }
+
+    /** 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.</p>
+     * @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()}.</p>
+     * @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/math/random/NormalizedRandomGenerator.java b/src/main/java/org/apache/commons/math/random/NormalizedRandomGenerator.java
new file mode 100644
index 0000000..0f13b81
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/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.math.random;
+
+/**
+ * This interface represent a normalized random generator for
+ * scalars.
+ * Normalized generator provide null mean and unit standard deviation scalars.
+ * @version $Revision: 811786 $ $Date: 2009-09-06 11:36:08 +0200 (dim. 06 sept. 2009) $
+ * @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.</p>
+   * @return a random scalar with null mean and unit standard deviation
+   */
+  double nextNormalizedDouble();
+
+}
diff --git a/src/main/java/org/apache/commons/math/random/RandomAdaptor.java b/src/main/java/org/apache/commons/math/random/RandomAdaptor.java
new file mode 100644
index 0000000..8091a48
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/random/RandomAdaptor.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.math.random;
+
+import java.util.Random;
+
+/**
+ * Extension of <code>java.util.Random</code> wrapping a
+ * {@link RandomGenerator}.
+ *
+ * @since 1.1
+ * @version $Revision: 1003886 $ $Date: 2010-10-02 23:04:44 +0200 (sam. 02 oct. 2010) $
+ */
+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 <tt>int</tt> 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 <tt>int</tt> 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 <tt>int</tt>
+     * 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 <tt>long</tt> 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/math/random/RandomData.java b/src/main/java/org/apache/commons/math/random/RandomData.java
new file mode 100644
index 0000000..0fc5136
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/random/RandomData.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.math.random;
+import java.util.Collection;
+
+/**
+ * Random data generation utilities.
+ * @version $Revision: 780975 $ $Date: 2009-06-02 11:05:37 +0200 (mar. 02 juin 2009) $
+ */
+public interface RandomData {
+    /**
+     * Generates a random string of hex characters of length
+     * <code>len</code>.
+     * <p>
+     * The generated string will be random, but not cryptographically
+     * secure. To generate cryptographically secure strings, use
+     * <code>nextSecureHexString</code></p>
+     * <p>
+     * <strong>Preconditions</strong>:<ul>
+     * <li><code>len > 0</code> (otherwise an IllegalArgumentException
+     *     is thrown.)</li>
+     * </ul></p>
+     *
+     * @param len the length of the string to be generated
+     * @return random string of hex characters of length <code>len</code>
+     */
+    String nextHexString(int len);
+
+    /**
+     * Generates a uniformly distributed random integer between
+     * <code>lower</code> and <code>upper</code> (endpoints included).
+     * <p>
+     * The generated integer will be random, but not cryptographically secure.
+     * To generate cryptographically secure integer sequences, use
+     * <code>nextSecureInt</code>.</p>
+     * <p>
+     * <strong>Preconditions</strong>:<ul>
+     * <li><code>lower < upper</code> (otherwise an IllegalArgumentException
+     *     is thrown.)</li>
+     * </ul></p>
+     *
+     * @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</code>
+     * and less than or equal to <code>upper</code>.
+     */
+    int nextInt(int lower, int upper);
+
+    /**
+     * Generates a uniformly distributed random long integer between
+     * <code>lower</code> and <code>upper</code> (endpoints included).
+     * <p>
+     * The generated long integer values will be random, but not
+     * cryptographically secure.
+     * To generate cryptographically secure sequences of longs, use
+     * <code>nextSecureLong</code></p>
+     * <p>
+     * <strong>Preconditions</strong>:<ul>
+     * <li><code>lower < upper</code> (otherwise an IllegalArgumentException
+     *     is thrown.)</li>
+     * </ul></p>
+     *
+     * @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</code>
+     * and less than or equal to <code>upper</code>.
+     */
+    long nextLong(long lower, long upper);
+
+    /**
+     * Generates a random string of hex characters from a secure random
+     * sequence.
+     * <p>
+     * If cryptographic security is not required,
+     * use <code>nextHexString()</code>.</p>
+     * <p>
+     * <strong>Preconditions</strong>:<ul>
+     * <li><code>len > 0</code> (otherwise an IllegalArgumentException
+     *     is thrown.)</li>
+     * </ul></p>
+     * @param len length of return string
+     * @return the random hex string
+     */
+    String nextSecureHexString(int len);
+
+    /**
+     * Generates a uniformly distributed random integer between
+     * <code>lower</code> and <code>upper</code> (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,
+     * <code>nextInt</code> should be used instead of this method.</p>
+     * <p>
+     * <strong>Definition</strong>:
+     * <a href="http://en.wikipedia.org/wiki/Cryptographically_secure_pseudo-random_number_generator">
+     * Secure Random Sequence</a></p>
+     * <p>
+     * <strong>Preconditions</strong>:<ul>
+     * <li><code>lower < upper</code> (otherwise an IllegalArgumentException
+     *     is thrown.)</li>
+     * </ul></p>
+     *
+     * @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</code>
+     * and less than or equal to <code>upper</code>.
+     */
+    int nextSecureInt(int lower, int upper);
+
+    /**
+     * Generates a random long integer between <code>lower</code>
+     * and <code>upper</code> (endpoints included).
+     * <p>
+     * Sequences of long values generated using this method will be
+     * cryptographically secure. If cryptographic security is not required,
+     * <code>nextLong</code> should be used instead of this method.</p>
+     * <p>
+     * <strong>Definition</strong>:
+     * <a href="http://en.wikipedia.org/wiki/Cryptographically_secure_pseudo-random_number_generator">
+     * Secure Random Sequence</a></p>
+     * <p>
+     * <strong>Preconditions</strong>:<ul>
+     * <li><code>lower < upper</code> (otherwise an IllegalArgumentException
+     *     is thrown.)</li>
+     * </ul></p>
+     *
+     * @param lower lower bound for generated integer
+     * @param upper upper bound for generated integer
+     * @return a long integer greater than or equal to <code>lower</code>
+     * and less than or equal to <code>upper</code>.
+     */
+    long nextSecureLong(long lower, long upper);
+
+    /**
+     * 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></p>
+     * <p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li>The specified mean <i>must</i> be positive (otherwise an
+     *     IllegalArgumentException is thrown.)</li>
+     * </ul></p>
+     * @param mean Mean of the distribution
+     * @return poisson deviate with the specified mean
+     */
+    long nextPoisson(double mean);
+
+    /**
+     * Generates a random value from the
+     * Normal (or Gaussian) distribution with the given mean
+     * and standard deviation.
+     * <p>
+     * <strong>Definition</strong>:
+     * <a href="http://www.itl.nist.gov/div898/handbook/eda/section3/eda3661.htm">
+     * Normal Distribution</a></p>
+     * <p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li><code>sigma > 0</code> (otherwise an IllegalArgumentException
+     *     is thrown.)</li>
+     * </ul></p>
+     * @param mu Mean of the distribution
+     * @param sigma Standard deviation of the distribution
+     * @return random value from Gaussian distribution with mean = mu,
+     * standard deviation = sigma
+     */
+    double nextGaussian(double mu, double sigma);
+
+    /**
+     * Generates a random value from the exponential distribution
+     * with expected value = <code>mean</code>.
+     * <p>
+     * <strong>Definition</strong>:
+     * <a href="http://www.itl.nist.gov/div898/handbook/eda/section3/eda3667.htm">
+     * Exponential Distribution</a></p>
+     * <p>
+     * <strong>Preconditions</strong>: <ul>
+     * <li><code>mu >= 0</code> (otherwise an IllegalArgumentException
+     *     is thrown.)</li>
+     * </ul></p>
+     * @param mean Mean of the distribution
+     * @return random value from exponential distribution
+     */
+    double nextExponential(double mean);
+
+    /**
+     * Generates a uniformly distributed random value from the open interval
+     * (<code>lower</code>,<code>upper</code>) (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</code> and
+     * <code>upper - lower</code> are the
+     * <a href = "http://www.itl.nist.gov/div898/handbook/eda/section3/eda364.htm">
+     * location and scale parameters</a>, respectively.</p>
+     * <p>
+     * <strong>Preconditions</strong>:<ul>
+     * <li><code>lower < upper</code> (otherwise an IllegalArgumentException
+     *     is thrown.)</li>
+     * </ul></p>
+     *
+     * @param lower lower endpoint of the interval of support
+     * @param upper upper endpoint of the interval of support
+     * @return uniformly distributed random value between lower
+     * and upper (exclusive)
+     */
+    double nextUniform(double lower, double upper);
+
+    /**
+     * Generates an integer array of length <code>k</code> whose entries
+     * are selected randomly, without repetition, from the integers <code>
+     * 0 through n-1</code> (inclusive).
+     * <p>
+     * Generated arrays represent permutations
+     * of <code>n</code> taken <code>k</code> at a time.</p>
+     * <p>
+     * <strong>Preconditions:</strong><ul>
+     * <li> <code>k <= n</code></li>
+     * <li> <code>n > 0</code> </li>
+     * </ul>
+     * If the preconditions are not met, an IllegalArgumentException is
+     * thrown.</p>
+     *
+     * @param n domain of the permutation
+     * @param k size of the permutation
+     * @return random k-permutation of n
+     */
+    int[] nextPermutation(int n, int k);
+
+    /**
+     * Returns an array of <code>k</code> objects selected randomly
+     * from the Collection <code>c</code>.
+     * <p>
+     * Sampling from <code>c</code>
+     * is without replacement; but if <code>c</code> contains identical
+     * objects, the sample may include repeats.  If all elements of <code>
+     * c</code> 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</code> from the elements of <code>c</code>.</p>
+     * <p>
+     * <strong>Preconditions:</strong><ul>
+     * <li> k must be less than or equal to the size of c </li>
+     * <li> c must not be empty </li>
+     * </ul>
+     * If the preconditions are not met, an IllegalArgumentException is
+     * thrown.</p>
+     *
+     * @param c collection to be sampled
+     * @param k size of the sample
+     * @return random sample of k elements from c
+     */
+    Object[] nextSample(Collection<?> c, int k);
+}
diff --git a/src/main/java/org/apache/commons/math/random/RandomDataImpl.java b/src/main/java/org/apache/commons/math/random/RandomDataImpl.java
new file mode 100644
index 0000000..e9ccab7
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/random/RandomDataImpl.java
@@ -0,0 +1,966 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.random;
+
+import java.io.Serializable;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.SecureRandom;
+import java.util.Collection;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.distribution.BetaDistributionImpl;
+import org.apache.commons.math.distribution.BinomialDistributionImpl;
+import org.apache.commons.math.distribution.CauchyDistributionImpl;
+import org.apache.commons.math.distribution.ChiSquaredDistributionImpl;
+import org.apache.commons.math.distribution.ContinuousDistribution;
+import org.apache.commons.math.distribution.FDistributionImpl;
+import org.apache.commons.math.distribution.GammaDistributionImpl;
+import org.apache.commons.math.distribution.HypergeometricDistributionImpl;
+import org.apache.commons.math.distribution.IntegerDistribution;
+import org.apache.commons.math.distribution.PascalDistributionImpl;
+import org.apache.commons.math.distribution.TDistributionImpl;
+import org.apache.commons.math.distribution.WeibullDistributionImpl;
+import org.apache.commons.math.distribution.ZipfDistributionImpl;
+import org.apache.commons.math.exception.MathInternalError;
+import org.apache.commons.math.exception.NotStrictlyPositiveException;
+import org.apache.commons.math.exception.NumberIsTooLargeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.util.FastMath;
+import org.apache.commons.math.util.MathUtils;
+
+/**
+ * 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 generator based on {@link java.util.Random}. 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>
+ * <p>
+ * For details on the default PRNGs, see {@link java.util.Random} and
+ * {@link java.security.SecureRandom}.
+ * </p>
+ * <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>
+ * <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>
+ * <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 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>
+ * <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>
+ * <li>
+ * This implementation is not synchronized.
+ * </ul>
+ * </p>
+ *
+ * @version $Revision: 1061496 $ $Date: 2011-01-20 21:32:16 +0100 (jeu. 20 janv. 2011) $
+ */
+public class RandomDataImpl 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 SecureRandom secRand = null;
+
+    /**
+     * Construct a RandomDataImpl.
+     */
+    public RandomDataImpl() {
+    }
+
+    /**
+     * 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
+     * @since 1.1
+     */
+    public RandomDataImpl(RandomGenerator rand) {
+        super();
+        this.rand = rand;
+    }
+
+    /**
+     * {@inheritDoc}
+     * <p>
+     * <strong>Algorithm Description:</strong> hex strings are generated using a
+     * 2-step process.
+     * <ol>
+     * <li>
+     * len/2+1 binary bytes are generated using the underlying Random</li>
+     * <li>
+     * Each binary byte is translated into 2 hex digits</li>
+     * </ol>
+     * </p>
+     *
+     * @param len
+     *            the desired string length.
+     * @return the random string.
+     * @throws NotStrictlyPositiveException if {@code len <= 0}.
+     */
+    public String nextHexString(int len) {
+        if (len <= 0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.LENGTH, len);
+        }
+
+        // Get a random number generator
+        RandomGenerator ran = getRan();
+
+        // 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);
+    }
+
+    /**
+     * Generate a random int value uniformly distributed between
+     * <code>lower</code> and <code>upper</code>, inclusive.
+     *
+     * @param lower
+     *            the lower bound.
+     * @param upper
+     *            the upper bound.
+     * @return the random integer.
+     * @throws NumberIsTooLargeException if {@code lower >= upper}.
+     */
+    public int nextInt(int lower, int upper) {
+        if (lower >= upper) {
+            throw new NumberIsTooLargeException(LocalizedFormats.LOWER_BOUND_NOT_BELOW_UPPER_BOUND,
+                                                lower, upper, false);
+        }
+        double r = getRan().nextDouble();
+        return (int) ((r * upper) + ((1.0 - r) * lower) + r);
+    }
+
+    /**
+     * Generate a random long value uniformly distributed between
+     * <code>lower</code> and <code>upper</code>, inclusive.
+     *
+     * @param lower
+     *            the lower bound.
+     * @param upper
+     *            the upper bound.
+     * @return the random integer.
+     * @throws NumberIsTooLargeException if {@code lower >= upper}.
+     */
+    public long nextLong(long lower, long upper) {
+        if (lower >= upper) {
+            throw new NumberIsTooLargeException(LocalizedFormats.LOWER_BOUND_NOT_BELOW_UPPER_BOUND,
+                                                lower, upper, false);
+        }
+        double r = getRan().nextDouble();
+        return (long) ((r * upper) + ((1.0 - r) * lower) + r);
+    }
+
+    /**
+     * {@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>
+     * <li>
+     * SHA-1 hash is applied to yield a 20-byte binary digest.</li>
+     * <li>
+     * Each byte of the binary digest is converted to 2 hex digits.</li>
+     * </ol>
+     * </p>
+     *
+     * @param len
+     *            the length of the generated string
+     * @return the random string
+     * @throws NotStrictlyPositiveException if {@code len <= 0}.
+     */
+    public String nextSecureHexString(int len) {
+        if (len <= 0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.LENGTH, len);
+        }
+
+        // Get SecureRandom and setup Digest provider
+        SecureRandom 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);
+    }
+
+    /**
+     * Generate a random int value uniformly distributed between
+     * <code>lower</code> and <code>upper</code>, inclusive. This algorithm uses
+     * a secure random number generator.
+     *
+     * @param lower
+     *            the lower bound.
+     * @param upper
+     *            the upper bound.
+     * @return the random integer.
+     * @throws NumberIsTooLargeException if {@code lower >= upper}.
+     */
+    public int nextSecureInt(int lower, int upper) {
+        if (lower >= upper) {
+            throw new NumberIsTooLargeException(LocalizedFormats.LOWER_BOUND_NOT_BELOW_UPPER_BOUND,
+                                                lower, upper, false);
+        }
+        SecureRandom sec = getSecRan();
+        return lower + (int) (sec.nextDouble() * (upper - lower + 1));
+    }
+
+    /**
+     * Generate a random long value uniformly distributed between
+     * <code>lower</code> and <code>upper</code>, inclusive. This algorithm uses
+     * a secure random number generator.
+     *
+     * @param lower
+     *            the lower bound.
+     * @param upper
+     *            the upper bound.
+     * @return the random integer.
+     * @throws NumberIsTooLargeException if {@code lower >= upper}.
+     */
+    public long nextSecureLong(long lower, long upper) {
+        if (lower >= upper) {
+            throw new NumberIsTooLargeException(LocalizedFormats.LOWER_BOUND_NOT_BELOW_UPPER_BOUND,
+                                                lower, upper, false);
+        }
+        SecureRandom sec = getSecRan();
+        return lower + (long) (sec.nextDouble() * (upper - lower + 1));
+    }
+
+    /**
+     * {@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>
+     *
+     * <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.</li></ul></p>
+     *
+     * @param mean mean of the Poisson distribution.
+     * @return the random Poisson value.
+     * @throws NotStrictlyPositiveException if {@code mean <= 0}.
+     */
+    public long nextPoisson(double mean) {
+        if (mean <= 0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.MEAN, mean);
+        }
+
+        final RandomGenerator generator = getRan();
+
+        final double pivot = 40.0d;
+        if (mean < pivot) {
+            double p = FastMath.exp(-mean);
+            long n = 0;
+            double r = 1.0d;
+            double rnd = 1.0d;
+
+            while (n < 1000 * mean) {
+                rnd = generator.nextDouble();
+                r = r * rnd;
+                if (r >= p) {
+                    n++;
+                } else {
+                    return n;
+                }
+            }
+            return n;
+        } else {
+            final double lambda = FastMath.floor(mean);
+            final double lambdaFractional = mean - lambda;
+            final double logLambda = FastMath.log(lambda);
+            final double logLambdaFactorial = MathUtils.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 = nextUniform(0.0, 1);
+                if (u <= p1) {
+                    final double n = nextGaussian(0d, 1d);
+                    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 = nextExponential(1d);
+                    v = -e - (n * n / 2) + c1;
+                } else {
+                    if (u > p1 + p2) {
+                        y = lambda;
+                        break;
+                    } else {
+                        x = delta + (twolpd / delta) * nextExponential(1d);
+                        y = FastMath.ceil(x);
+                        v = -nextExponential(1d) - 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 - MathUtils.factorialLog((int) (y + lambda)) + logLambdaFactorial) {
+                    y = lambda + y;
+                    break;
+                }
+            }
+            return y2 + (long) y;
+        }
+    }
+
+    /**
+     * Generate a random value from a Normal (a.k.a. Gaussian) distribution with
+     * the given mean, <code>mu</code> and the given standard deviation,
+     * <code>sigma</code>.
+     *
+     * @param mu
+     *            the mean of the distribution
+     * @param sigma
+     *            the standard deviation of the distribution
+     * @return the random Normal value
+     * @throws NotStrictlyPositiveException if {@code sigma <= 0}.
+     */
+    public double nextGaussian(double mu, double sigma) {
+        if (sigma <= 0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.STANDARD_DEVIATION, sigma);
+        }
+        return sigma * getRan().nextGaussian() + mu;
+    }
+
+    /**
+     * Returns a random value from an Exponential distribution with the given
+     * mean.
+     * <p>
+     * <strong>Algorithm Description</strong>: 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.
+     * </p>
+     *
+     * @param mean the mean of the distribution
+     * @return the random Exponential value
+     * @throws NotStrictlyPositiveException if {@code mean <= 0}.
+     */
+    public double nextExponential(double mean) {
+        if (mean <= 0.0) {
+            throw new NotStrictlyPositiveException(LocalizedFormats.MEAN, mean);
+        }
+        final RandomGenerator generator = getRan();
+        double unif = generator.nextDouble();
+        while (unif == 0.0d) {
+            unif = generator.nextDouble();
+        }
+        return -mean * FastMath.log(unif);
+    }
+
+    /**
+     * {@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).
+     * </p>
+     *
+     * @param lower
+     *            the lower bound.
+     * @param upper
+     *            the upper bound.
+     * @return a uniformly distributed random value from the interval (lower,
+     *         upper)
+     * @throws NumberIsTooLargeException if {@code lower >= upper}.
+     */
+    public double nextUniform(double lower, double upper) {
+        if (lower >= upper) {
+            throw new NumberIsTooLargeException(LocalizedFormats.LOWER_BOUND_NOT_BELOW_UPPER_BOUND,
+                                                lower, upper, false);
+        }
+        final RandomGenerator generator = getRan();
+
+        // ensure nextDouble() isn't 0.0
+        double u = generator.nextDouble();
+        while (u <= 0.0) {
+            u = generator.nextDouble();
+        }
+
+        return lower + u * (upper - lower);
+    }
+
+    /**
+     * Generates a random value from the {@link BetaDistributionImpl Beta Distribution}.
+     * This implementation uses {@link #nextInversionDeviate(ContinuousDistribution) 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
+     * @throws MathException if an error occurs generating the random value
+     * @since 2.2
+     */
+    public double nextBeta(double alpha, double beta) throws MathException {
+        return nextInversionDeviate(new BetaDistributionImpl(alpha, beta));
+    }
+
+    /**
+     * Generates a random value from the {@link BinomialDistributionImpl Binomial Distribution}.
+     * This implementation uses {@link #nextInversionDeviate(ContinuousDistribution) 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
+     * @throws MathException if an error occurs generating the random value
+     * @since 2.2
+     */
+    public int nextBinomial(int numberOfTrials, double probabilityOfSuccess) throws MathException {
+        return nextInversionDeviate(new BinomialDistributionImpl(numberOfTrials, probabilityOfSuccess));
+    }
+
+    /**
+     * Generates a random value from the {@link CauchyDistributionImpl Cauchy Distribution}.
+     * This implementation uses {@link #nextInversionDeviate(ContinuousDistribution) 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
+     * @throws MathException if an error occurs generating the random value
+     * @since 2.2
+     */
+    public double nextCauchy(double median, double scale) throws MathException {
+        return nextInversionDeviate(new CauchyDistributionImpl(median, scale));
+    }
+
+    /**
+     * Generates a random value from the {@link ChiSquaredDistributionImpl ChiSquare Distribution}.
+     * This implementation uses {@link #nextInversionDeviate(ContinuousDistribution) inversion}
+     * to generate random values.
+     *
+     * @param df the degrees of freedom of the ChiSquare distribution
+     * @return random value sampled from the ChiSquare(df) distribution
+     * @throws MathException if an error occurs generating the random value
+     * @since 2.2
+     */
+    public double nextChiSquare(double df) throws MathException {
+        return nextInversionDeviate(new ChiSquaredDistributionImpl(df));
+    }
+
+    /**
+     * Generates a random value from the {@link FDistributionImpl F Distribution}.
+     * This implementation uses {@link #nextInversionDeviate(ContinuousDistribution) 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 MathException if an error occurs generating the random value
+     * @since 2.2
+     */
+    public double nextF(double numeratorDf, double denominatorDf) throws MathException {
+        return nextInversionDeviate(new FDistributionImpl(numeratorDf, denominatorDf));
+    }
+
+    /**
+     * Generates a random value from the {@link GammaDistributionImpl Gamma Distribution}.
+     * This implementation uses {@link #nextInversionDeviate(ContinuousDistribution) inversion}
+     * to generate random values.
+     *
+     * @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 MathException if an error occurs generating the random value
+     * @since 2.2
+     */
+    public double nextGamma(double shape, double scale) throws MathException {
+        return nextInversionDeviate(new GammaDistributionImpl(shape, scale));
+    }
+
+    /**
+     * Generates a random value from the {@link HypergeometricDistributionImpl 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 MathException if an error occurs generating the random value
+     * @since 2.2
+     */
+    public int nextHypergeometric(int populationSize, int numberOfSuccesses, int sampleSize) throws MathException {
+        return nextInversionDeviate(new HypergeometricDistributionImpl(populationSize, numberOfSuccesses, sampleSize));
+    }
+
+    /**
+     * Generates a random value from the {@link PascalDistributionImpl 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
+     * @throws MathException if an error occurs generating the random value
+     * @since 2.2
+     */
+    public int nextPascal(int r, double p) throws MathException {
+        return nextInversionDeviate(new PascalDistributionImpl(r, p));
+    }
+
+    /**
+     * Generates a random value from the {@link TDistributionImpl T Distribution}.
+     * This implementation uses {@link #nextInversionDeviate(ContinuousDistribution) inversion}
+     * to generate random values.
+     *
+     * @param df the degrees of freedom of the T distribution
+     * @return random value from the T(df) distribution
+     * @throws MathException if an error occurs generating the random value
+     * @since 2.2
+     */
+    public double nextT(double df) throws MathException {
+        return nextInversionDeviate(new TDistributionImpl(df));
+    }
+
+    /**
+     * Generates a random value from the {@link WeibullDistributionImpl Weibull Distribution}.
+     * This implementation uses {@link #nextInversionDeviate(ContinuousDistribution) 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
+     * @throws MathException if an error occurs generating the random value
+     * @since 2.2
+     */
+    public double nextWeibull(double shape, double scale) throws MathException {
+        return nextInversionDeviate(new WeibullDistributionImpl(shape, scale));
+    }
+
+    /**
+     * Generates a random value from the {@link ZipfDistributionImpl 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
+     * @throws MathException if an error occurs generating the random value
+     * @since 2.2
+     */
+    public int nextZipf(int numberOfElements, double exponent) throws MathException {
+        return nextInversionDeviate(new ZipfDistributionImpl(numberOfElements, exponent));
+    }
+
+    /**
+     * Returns the RandomGenerator used to generate non-secure random data.
+     * <p>
+     * Creates and initializes a default generator if null.
+     * </p>
+     *
+     * @return the Random used to generate random data
+     * @since 1.1
+     */
+    private RandomGenerator getRan() {
+        if (rand == null) {
+            rand = new JDKRandomGenerator();
+            rand.setSeed(System.currentTimeMillis());
+        }
+        return rand;
+    }
+
+    /**
+     * Returns the SecureRandom used to generate secure random data.
+     * <p>
+     * Creates and initializes if null.
+     * </p>
+     *
+     * @return the SecureRandom used to generate secure random data
+     */
+    private SecureRandom getSecRan() {
+        if (secRand == null) {
+            secRand = new SecureRandom();
+            secRand.setSeed(System.currentTimeMillis());
+        }
+        return secRand;
+    }
+
+    /**
+     * Reseeds the random number generator with the supplied seed.
+     * <p>
+     * Will create and initialize if null.
+     * </p>
+     *
+     * @param seed
+     *            the seed value to use
+     */
+    public void reSeed(long seed) {
+        if (rand == null) {
+            rand = new JDKRandomGenerator();
+        }
+        rand.setSeed(seed);
+    }
+
+    /**
+     * Reseeds the secure random number generator with the current time in
+     * milliseconds.
+     * <p>
+     * Will create and initialize if null.
+     * </p>
+     */
+    public void reSeedSecure() {
+        if (secRand == null) {
+            secRand = new SecureRandom();
+        }
+        secRand.setSeed(System.currentTimeMillis());
+    }
+
+    /**
+     * Reseeds the secure random number generator with the supplied seed.
+     * <p>
+     * Will create and initialize if null.
+     * </p>
+     *
+     * @param seed
+     *            the seed value to use
+     */
+    public void reSeedSecure(long seed) {
+        if (secRand == null) {
+            secRand = new SecureRandom();
+        }
+        secRand.setSeed(seed);
+    }
+
+    /**
+     * Reseeds the random number generator with the current time in
+     * milliseconds.
+     */
+    public void reSeed() {
+        if (rand == null) {
+            rand = new JDKRandomGenerator();
+        }
+        rand.setSeed(System.currentTimeMillis());
+    }
+
+    /**
+     * 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.
+     * </p>
+     *
+     * @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 = SecureRandom.getInstance(algorithm, provider);
+    }
+
+    /**
+     * Generates an integer array of length <code>k</code> whose entries are
+     * selected randomly, without repetition, from the integers
+     * <code>0 through n-1</code> (inclusive).
+     * <p>
+     * Generated arrays represent permutations of <code>n</code> taken
+     * <code>k</code> at a time.
+     * </p>
+     * <p>
+     * <strong>Preconditions:</strong>
+     * <ul>
+     * <li> <code>k <= n</code></li>
+     * <li> <code>n > 0</code></li>
+     * </ul>
+     * If the preconditions are not met, an IllegalArgumentException is thrown.
+     * </p>
+     * <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>.
+     * </p>
+     *
+     * @param n
+     *            domain of the permutation (must be positive)
+     * @param k
+     *            size of the permutation (must satisfy 0 < k <= n).
+     * @return the random permutation as an int array
+     * @throws NumberIsTooLargeException if {@code k > n}.
+     * @throws NotStrictlyPositiveException if {@code k <= 0}.
+     */
+    public int[] nextPermutation(int n, int k) {
+        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 = getNatural(n);
+        shuffle(index, n - k);
+        int[] result = new int[k];
+        for (int i = 0; i < k; i++) {
+            result[i] = index[n - i - 1];
+        }
+
+        return result;
+    }
+
+    /**
+     * Uses a 2-cycle permutation shuffle to generate a random permutation.
+     * <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>
+     *
+     * @param c
+     *            Collection to sample from.
+     * @param k
+     *            sample size.
+     * @return the random sample.
+     * @throws NumberIsTooLargeException if {@code k > c.size()}.
+     * @throws NotStrictlyPositiveException if {@code k <= 0}.
+     */
+    public Object[] nextSample(Collection<?> c, int k) {
+        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;
+    }
+
+    /**
+     * 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 MathException if an error occurs computing the inverse cumulative distribution function
+     * @since 2.2
+     */
+    public double nextInversionDeviate(ContinuousDistribution distribution) throws MathException {
+        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 MathException if an error occurs computing the inverse cumulative distribution function
+     * @since 2.2
+     */
+    public int nextInversionDeviate(IntegerDistribution distribution) throws MathException {
+        final double target = nextUniform(0, 1);
+        final int glb = distribution.inverseCumulativeProbability(target);
+        if (distribution.cumulativeProbability(glb) == 1.0d) { // No mass above
+            return glb;
+        } else {
+            return glb + 1;
+        }
+    }
+
+    // ------------------------Private methods----------------------------------
+
+    /**
+     * Uses a 2-cycle permutation shuffle to randomly re-order the last elements
+     * of list.
+     *
+     * @param list
+     *            list to be shuffled
+     * @param end
+     *            element past which shuffling begins
+     */
+    private void shuffle(int[] list, int end) {
+        int target = 0;
+        for (int i = list.length - 1; i >= end; i--) {
+            if (i == 0) {
+                target = 0;
+            } else {
+                target = nextInt(0, i);
+            }
+            int temp = list[target];
+            list[target] = list[i];
+            list[i] = temp;
+        }
+    }
+
+    /**
+     * Returns an array representing n.
+     *
+     * @param n
+     *            the natural number to represent
+     * @return array with entries = elements of n
+     */
+    private int[] getNatural(int n) {
+        int[] natural = new int[n];
+        for (int i = 0; i < n; i++) {
+            natural[i] = i;
+        }
+        return natural;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/random/RandomGenerator.java b/src/main/java/org/apache/commons/math/random/RandomGenerator.java
new file mode 100644
index 0000000..0b86c2a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/random/RandomGenerator.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.math.random;
+
+
+/**
+ * Interface extracted from <code>java.util.Random</code>.  This interface is
+ * implemented by {@link AbstractRandomGenerator}.
+ *
+ * @since 1.1
+ * @version $Revision: 949750 $ $Date: 2010-05-31 16:06:04 +0200 (lun. 31 mai 2010) $
+ */
+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.
+     * </p>
+     * @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.
+     * </p>
+     * @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.
+     * </p>
+     * @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 <tt>int</tt> 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 <tt>int</tt> 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 <tt>int</tt>
+     * 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 <tt>long</tt> 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/math/random/RandomVectorGenerator.java b/src/main/java/org/apache/commons/math/random/RandomVectorGenerator.java
new file mode 100644
index 0000000..15abbd7
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/random/RandomVectorGenerator.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.math.random;
+
+
+/** This interface represents a random generator for whole vectors.
+ *
+ * @since 1.2
+ * @version $Revision: 811786 $ $Date: 2009-09-06 11:36:08 +0200 (dim. 06 sept. 2009) $
+ *
+ */
+
+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/math/random/UncorrelatedRandomVectorGenerator.java b/src/main/java/org/apache/commons/math/random/UncorrelatedRandomVectorGenerator.java
new file mode 100644
index 0000000..d365f9b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/random/UncorrelatedRandomVectorGenerator.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.math.random;
+
+import java.util.Arrays;
+
+import org.apache.commons.math.exception.DimensionMismatchException;
+
+/**
+ * A {@link RandomVectorGenerator} that generates vectors with uncorrelated
+ * components. Components of generated vectors follow (independent) Gaussian
+ * distributions, with parameters supplied in the constructor.
+ *
+ * @version $Revision: 962515 $ $Date: 2010-07-09 15:15:28 +0200 (ven. 09 juil. 2010) $
+ * @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.</p>
+   * @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</p>
+   * @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/math/random/UniformRandomGenerator.java b/src/main/java/org/apache/commons/math/random/UniformRandomGenerator.java
new file mode 100644
index 0000000..54492d0
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/random/UniformRandomGenerator.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.math.random;
+
+import org.apache.commons.math.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].</p>
+ *
+ * @since 1.2
+ *
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ */
+
+public class UniformRandomGenerator implements NormalizedRandomGenerator {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 1569292426375546027L;
+
+    /** 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).</p>
+     * @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/math/random/UnitSphereRandomVectorGenerator.java b/src/main/java/org/apache/commons/math/random/UnitSphereRandomVectorGenerator.java
new file mode 100644
index 0000000..cac8f18
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/random/UnitSphereRandomVectorGenerator.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.math.random;
+
+import org.apache.commons.math.util.FastMath;
+
+
+/**
+ * Generate random vectors isotropically located on the surface of a sphere.
+ *
+ * @since 2.1
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ */
+
+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];
+
+        double normSq;
+        do {
+            normSq = 0;
+            for (int i = 0; i < dimension; i++) {
+                final double comp = 2 * rand.nextDouble() - 1;
+                v[i] = comp;
+                normSq += comp * comp;
+            }
+        } while (normSq > 1);
+
+        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/math/random/ValueServer.java b/src/main/java/org/apache/commons/math/random/ValueServer.java
new file mode 100644
index 0000000..9146e69
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/random/ValueServer.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.math.random;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * Generates values for use in simulation applications.
+ * <p>
+ * How values are generated is determined by the <code>mode</code>
+ * property.</p>
+ * <p>
+ * Supported <code>mode</code> values are: <ul>
+ * <li> DIGEST_MODE -- uses an empirical distribution </li>
+ * <li> REPLAY_MODE -- replays data from <code>valuesFileURL</code></li>
+ * <li> UNIFORM_MODE -- generates uniformly distributed random values with
+ *                      mean = <code>mu</code> </li>
+ * <li> EXPONENTIAL_MODE -- generates exponentially distributed random values
+ *                         with mean = <code>mu</code></li>
+ * <li> GAUSSIAN_MODE -- generates Gaussian distributed random values with
+ *                       mean = <code>mu</code> and
+ *                       standard deviation = <code>sigma</code></li>
+ * <li> CONSTANT_MODE -- returns <code>mu</code> every time.</li></ul></p>
+ *
+ * @version $Revision: 1003886 $ $Date: 2010-10-02 23:04:44 +0200 (sam. 02 oct. 2010) $
+ *
+ */
+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 RandomData randomData;
+
+    // Data generation modes ======================================
+
+    /** Creates new ValueServer */
+    public ValueServer() {
+        randomData = new RandomDataImpl();
+    }
+
+    /**
+     * Construct a ValueServer instance using a RandomData as its source
+     * of random data.
+     *
+     * @param randomData the RandomData instance used to source random data
+     * @since 1.1
+     */
+    public ValueServer(RandomData randomData) {
+        this.randomData = randomData;
+    }
+
+    /**
+     * 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
+     */
+    public double getNext() throws IOException {
+        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 MathRuntimeException.createIllegalStateException(
+                    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
+     */
+    public void fill(double[] values) throws IOException {
+        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
+     */
+    public double[] fill(int length) throws IOException {
+        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>
+     * <p>
+     * This method must be called before using <code>getNext()</code>
+     * with <code>mode = DIGEST_MODE</code></p>
+     *
+     * @throws IOException if an I/O error occurs reading the input file
+     */
+    public void computeDistribution() throws IOException {
+        empiricalDistribution = new EmpiricalDistributionImpl();
+        empiricalDistribution.load(valuesFileURL);
+    }
+
+    /**
+     * 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>
+     * <p>
+     * This method must be called before using <code>getNext()</code>
+     * with <code>mode = DIGEST_MODE</code></p>
+     *
+     * @param binCount the number of bins used in computing the empirical
+     * distribution
+     * @throws IOException if an error occurs reading the input file
+     */
+    public void computeDistribution(int binCount)
+            throws IOException {
+        empiricalDistribution = new EmpiricalDistributionImpl(binCount);
+        empiricalDistribution.load(valuesFileURL);
+        mu = empiricalDistribution.getSampleStats().getMean();
+        sigma = empiricalDistribution.getSampleStats().getStandardDeviation();
+    }
+
+    /** Getter for property mode.
+     * @return Value of property mode.
+     */
+    public int getMode() {
+        return mode;
+    }
+
+    /** Setter for property mode.
+     * @param mode New value of property mode.
+     */
+    public void setMode(int mode) {
+        this.mode = mode;
+    }
+
+    /**
+     * Getter for <code>valuesFileURL<code>
+     * @return Value of property valuesFileURL.
+     */
+    public URL getValuesFileURL() {
+        return valuesFileURL;
+    }
+
+    /**
+     * Sets the <code>valuesFileURL</code> 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 <code>valuesFileURL</code>
+     * @param url New value of property valuesFileURL.
+     */
+    public void setValuesFileURL(URL url) {
+        this.valuesFileURL = url;
+    }
+
+    /** Getter for property empiricalDistribution.
+     * @return Value of property empiricalDistribution.
+     */
+    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
+     */
+    public void resetReplayFile() throws IOException {
+        if (filePointer != null) {
+            try {
+                filePointer.close();
+                filePointer = null;
+            } catch (IOException ex) {
+                // ignore
+            }
+        }
+        filePointer = new BufferedReader(new InputStreamReader(valuesFileURL.openStream()));
+    }
+
+    /**
+     * Closes <code>valuesFileURL</code> 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;
+        }
+    }
+
+    /** Getter for property mu.
+     * @return Value of property mu.
+     */
+    public double getMu() {
+        return mu;
+    }
+
+    /** Setter for property mu.
+     * @param mu New value of property mu.
+     */
+    public void setMu(double mu) {
+        this.mu = mu;
+    }
+
+    /** Getter for property sigma.
+     * @return Value of property sigma.
+     */
+    public double getSigma() {
+        return sigma;
+    }
+
+    /** Setter for property sigma.
+     * @param sigma New value of property sigma.
+     */
+    public void setSigma(double sigma) {
+        this.sigma = sigma;
+    }
+
+    //------------- 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</li></ul></p>
+     *
+     * @return next random value from the empirical distribution digest
+     */
+    private double getNextDigest() {
+        if ((empiricalDistribution == null) ||
+            (empiricalDistribution.getBinStats().size() == 0)) {
+            throw MathRuntimeException.createIllegalStateException(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>
+     * <p>
+     * This method will open the <code>valuesFileURL</code> if there is no
+     * replay file open.</p>
+     * <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.</p>
+     *
+     * @return next value from the replay file
+     * @throws IOException if there is a problem reading from the file
+     * @throws NumberFormatException if an invalid numeric string is
+     *   encountered in the file
+     */
+    private double getNextReplay() throws IOException {
+        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 MathRuntimeException.createEOFException(LocalizedFormats.URL_CONTAINS_NO_DATA,
+                                                              valuesFileURL);
+            }
+        }
+        return Double.valueOf(str).doubleValue();
+    }
+
+    /**
+     * Gets a uniformly distributed random value with mean = mu.
+     *
+     * @return random uniform value
+     */
+    private double getNextUniform() {
+        return randomData.nextUniform(0, 2 * mu);
+    }
+
+    /**
+     * Gets an exponentially distributed random value with mean = mu.
+     *
+     * @return random exponential value
+     */
+    private double getNextExponential() {
+        return randomData.nextExponential(mu);
+    }
+
+    /**
+     * Gets a Gaussian distributed random value with mean = mu
+     * and standard deviation = sigma.
+     *
+     * @return random Gaussian value
+     */
+    private double getNextGaussian() {
+        return randomData.nextGaussian(mu, sigma);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/random/Well1024a.java b/src/main/java/org/apache/commons/math/random/Well1024a.java
new file mode 100644
index 0000000..8406ed5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/random/Well1024a.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.math.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>.</p>
+
+ * @see <a href="http://www.iro.umontreal.ca/~panneton/WELLRNG.html">WELL Random number generator</a>
+ * @version $Revision: 1003892 $ $Date: 2010-10-02 23:28:56 +0200 (sam. 02 oct. 2010) $
+ * @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.</p>
+     */
+    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/math/random/Well19937a.java b/src/main/java/org/apache/commons/math/random/Well19937a.java
new file mode 100644
index 0000000..97f9bfd
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/random/Well19937a.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.math.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>.</p>
+
+ * @see <a href="http://www.iro.umontreal.ca/~panneton/WELLRNG.html">WELL Random number generator</a>
+ * @version $Revision: 1003892 $ $Date: 2010-10-02 23:28:56 +0200 (sam. 02 oct. 2010) $
+ * @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.</p>
+     */
+    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/math/random/Well19937c.java b/src/main/java/org/apache/commons/math/random/Well19937c.java
new file mode 100644
index 0000000..53029c1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/random/Well19937c.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.math.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>.</p>
+
+ * @see <a href="http://www.iro.umontreal.ca/~panneton/WELLRNG.html">WELL Random number generator</a>
+ * @version $Revision: 1003892 $ $Date: 2010-10-02 23:28:56 +0200 (sam. 02 oct. 2010) $
+ * @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.</p>
+     */
+    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 ^ ((z4 <<  7) & 0xe46e1700);
+        z4 = z4 ^ ((z4 << 15) & 0x9b868000);
+
+        return z4 >>> (32 - bits);
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/random/Well44497a.java b/src/main/java/org/apache/commons/math/random/Well44497a.java
new file mode 100644
index 0000000..70d672c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/random/Well44497a.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.math.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>.</p>
+
+ * @see <a href="http://www.iro.umontreal.ca/~panneton/WELLRNG.html">WELL Random number generator</a>
+ * @version $Revision: 1003892 $ $Date: 2010-10-02 23:28:56 +0200 (sam. 02 oct. 2010) $
+ * @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.</p>
+     */
+    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/math/random/Well44497b.java b/src/main/java/org/apache/commons/math/random/Well44497b.java
new file mode 100644
index 0000000..28b5dbb
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/random/Well44497b.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.math.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>.</p>
+
+ * @see <a href="http://www.iro.umontreal.ca/~panneton/WELLRNG.html">WELL Random number generator</a>
+ * @version $Revision: 1003892 $ $Date: 2010-10-02 23:28:56 +0200 (sam. 02 oct. 2010) $
+ * @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.</p>
+     */
+    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 ^ ((z4 <<  7) & 0x93dd1400);
+        z4 = z4 ^ ((z4 << 15) & 0xfa118000);
+
+        return z4 >>> (32 - bits);
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/random/Well512a.java b/src/main/java/org/apache/commons/math/random/Well512a.java
new file mode 100644
index 0000000..c822c9d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/random/Well512a.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.math.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>.</p>
+
+ * @see <a href="http://www.iro.umontreal.ca/~panneton/WELLRNG.html">WELL Random number generator</a>
+ * @version $Revision: 1003892 $ $Date: 2010-10-02 23:28:56 +0200 (sam. 02 oct. 2010) $
+ * @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.</p>
+     */
+    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/math/random/package.html b/src/main/java/org/apache/commons/math/random/package.html
new file mode 100644
index 0000000..9978ca0
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/random/package.html
@@ -0,0 +1,132 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 1054186 $ $Date: 2011-01-01 03:28:46 +0100 (sam. 01 janv. 2011) $ -->
+    <body>
+      <p>Random number and random data generators.</p>
+      <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.math.random.JDKRandomGenerator JDKRandomGenerator}
+            that extends the JDK provided generator</li>
+        <li>AbstractRandomGenerator as a helper for users generators</li>
+        <li>BitStreamGenerator which is an abstract class for several generators and
+            which in turn is extended by:
+            <ul>
+              <li>{@link org.apache.commons.math.random.MersenneTwister MersenneTwister}</li>
+              <li>{@link org.apache.commons.math.random.Well512a Well512a}</li>
+              <li>{@link org.apache.commons.math.random.Well1024a Well1024a}</li>
+              <li>{@link org.apache.commons.math.random.Well19937a Well19937a}</li>
+              <li>{@link org.apache.commons.math.random.Well19937c Well19937c}</li>
+              <li>{@link org.apache.commons.math.random.Well44497a Well44497a}</li>
+              <li>{@link org.apache.commons.math.random.Well44497b Well44497b}</li>
+            </ul>
+          </li>
+        </ul>
+      </p>
+
+      <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>
+
+      <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>
+
+      <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>
+
+      <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>
+
+      <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>
+
+      <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.math.random.MersenneTwister MersenneTwister}</td><td>1</td></tr>
+          <tr><td>{@link org.apache.commons.math.random.JDKRandomGenerator JDKRandomGenerator}</td><td>between 0.96 and 1.16</td></tr>
+          <tr><td>{@link org.apache.commons.math.random.Well512a Well512a}</td><td>between 0.85 and 0.88</td></tr>
+          <tr><td>{@link org.apache.commons.math.random.Well1024a Well1024a}</td><td>between 0.63 and 0.73</td></tr>
+          <tr><td>{@link org.apache.commons.math.random.Well19937a Well19937a}</td><td>between 0.70 and 0.71</td></tr>
+          <tr><td>{@link org.apache.commons.math.random.Well19937c Well19937c}</td><td>between 0.57 and 0.71</td></tr>
+          <tr><td>{@link org.apache.commons.math.random.Well44497a Well44497a}</td><td>between 0.69 and 0.71</td></tr>
+          <tr><td>{@link org.apache.commons.math.random.Well44497b Well44497b}</td><td>between 0.65 and 0.71</td></tr>
+        </table>
+      </p>
+
+      <p>
+      So for most simulation problems, the better generators like {@link
+      org.apache.commons.math.random.Well19937c Well19937c} and {@link
+      org.apache.commons.math.random.Well44497b Well44497b} are probably very good choices.
+      </p>
+
+      <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.
+      </p>
+
+    </body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/special/Beta.java b/src/main/java/org/apache/commons/math/special/Beta.java
new file mode 100644
index 0000000..6ab284f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/special/Beta.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.math.special;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.util.ContinuedFraction;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * This is a utility class that provides computation methods related to the
+ * Beta family of functions.
+ *
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ */
+public class Beta {
+
+    /** Maximum allowed numerical error. */
+    private static final double DEFAULT_EPSILON = 10e-15;
+
+    /**
+     * Default constructor.  Prohibit instantiation.
+     */
+    private Beta() {
+        super();
+    }
+
+    /**
+     * Returns the
+     * <a href="http://mathworld.wolfram.com/RegularizedBetaFunction.html">
+     * regularized beta function</a> I(x, a, b).
+     *
+     * @param x the value.
+     * @param a the a parameter.
+     * @param b the b parameter.
+     * @return the regularized beta function I(x, a, b)
+     * @throws MathException if the algorithm fails to converge.
+     */
+    public static double regularizedBeta(double x, double a, double b)
+        throws MathException
+    {
+        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 the value.
+     * @param a the a parameter.
+     * @param b the b parameter.
+     * @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 MathException if the algorithm fails to converge.
+     */
+    public static double regularizedBeta(double x, double a, double b,
+        double epsilon) throws MathException
+    {
+        return regularizedBeta(x, a, b, epsilon, Integer.MAX_VALUE);
+    }
+
+    /**
+     * Returns the regularized beta function I(x, a, b).
+     *
+     * @param x the value.
+     * @param a the a parameter.
+     * @param b the b parameter.
+     * @param maxIterations Maximum number of "iterations" to complete.
+     * @return the regularized beta function I(x, a, b)
+     * @throws MathException if the algorithm fails to converge.
+     */
+    public static double regularizedBeta(double x, double a, double b,
+        int maxIterations) throws MathException
+    {
+        return regularizedBeta(x, a, b, DEFAULT_EPSILON, maxIterations);
+    }
+
+    /**
+     * Returns the regularized beta function I(x, a, b).
+     *
+     * The implementation of this method is based on:
+     * <ul>
+     * <li>
+     * <a href="http://mathworld.wolfram.com/RegularizedBetaFunction.html">
+     * Regularized Beta Function</a>.</li>
+     * <li>
+     * <a href="http://functions.wolfram.com/06.21.10.0001.01">
+     * Regularized Beta Function</a>.</li>
+     * </ul>
+     *
+     * @param x the value.
+     * @param a the a parameter.
+     * @param b the b parameter.
+     * @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 MathException if the algorithm fails to converge.
+     */
+    public static double regularizedBeta(double x, final double a,
+        final double b, double epsilon, int maxIterations) throws MathException
+    {
+        double ret;
+
+        if (Double.isNaN(x) || Double.isNaN(a) || Double.isNaN(b) || (x < 0) ||
+            (x > 1) || (a <= 0.0) || (b <= 0.0))
+        {
+            ret = Double.NaN;
+        } else if (x > (a + 1.0) / (a + b + 2.0)) {
+            ret = 1.0 - regularizedBeta(1.0 - x, b, a, epsilon, maxIterations);
+        } else {
+            ContinuedFraction fraction = new ContinuedFraction() {
+
+                @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;
+                }
+
+                @Override
+                protected double getA(int n, double x) {
+                    return 1.0;
+                }
+            };
+            ret = FastMath.exp((a * FastMath.log(x)) + (b * FastMath.log(1.0 - x)) -
+                FastMath.log(a) - logBeta(a, b, epsilon, maxIterations)) *
+                1.0 / fraction.evaluate(x, epsilon, maxIterations);
+        }
+
+        return ret;
+    }
+
+    /**
+     * Returns the natural logarithm of the beta function B(a, b).
+     *
+     * @param a the a parameter.
+     * @param b the b parameter.
+     * @return log(B(a, b))
+     */
+    public static double logBeta(double a, double b) {
+        return logBeta(a, b, DEFAULT_EPSILON, Integer.MAX_VALUE);
+    }
+
+    /**
+     * Returns the natural logarithm of the beta function B(a, b).
+     *
+     * The implementation of this method is based on:
+     * <ul>
+     * <li><a href="http://mathworld.wolfram.com/BetaFunction.html">
+     * Beta Function</a>, equation (1).</li>
+     * </ul>
+     *
+     * @param a the a parameter.
+     * @param b the b parameter.
+     * @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 log(B(a, b))
+     */
+    public static double logBeta(double a, double b, double epsilon,
+        int maxIterations) {
+
+        double ret;
+
+        if (Double.isNaN(a) || Double.isNaN(b) || (a <= 0.0) || (b <= 0.0)) {
+            ret = Double.NaN;
+        } else {
+            ret = Gamma.logGamma(a) + Gamma.logGamma(b) -
+                Gamma.logGamma(a + b);
+        }
+
+        return ret;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/special/Erf.java b/src/main/java/org/apache/commons/math/special/Erf.java
new file mode 100644
index 0000000..5abe18b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/special/Erf.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.math.special;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * This is a utility class that provides computation methods related to the
+ * error functions.
+ *
+ * @version $Revision: 1054186 $ $Date: 2011-01-01 03:28:46 +0100 (sam. 01 janv. 2011) $
+ */
+public class Erf {
+
+    /**
+     * Default constructor.  Prohibit instantiation.
+     */
+    private Erf() {
+        super();
+    }
+
+    /**
+     * <p>Returns the error function</p>
+     * <p>erf(x) = 2/&radic;&pi; <sub>0</sub>&int;<sup>x</sup> e<sup>-t<sup>2</sup></sup>dt </p>
+     *
+     * <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>
+     *
+     * <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.</p>
+     *
+     * @param x the value.
+     * @return the error function erf(x)
+     * @throws MathException if the algorithm fails to converge.
+     * @see Gamma#regularizedGammaP(double, double, double, int)
+     */
+    public static double erf(double x) throws MathException {
+        if (FastMath.abs(x) > 40) {
+            return x > 0 ? 1 : -1;
+        }
+        double ret = Gamma.regularizedGammaP(0.5, x * x, 1.0e-15, 10000);
+        if (x < 0) {
+            ret = -ret;
+        }
+        return ret;
+    }
+
+    /**
+     * <p>Returns the complementary error function</p>
+     * <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>
+     *
+     * <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>
+     *
+     * <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.</p>
+     *
+     * @param x the value
+     * @return the complementary error function erfc(x)
+     * @throws MathException if the algorithm fails to converge
+     * @see Gamma#regularizedGammaQ(double, double, double, int)
+     * @since 2.2
+     */
+    public static double erfc(double x) throws MathException {
+        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;
+    }
+}
+
diff --git a/src/main/java/org/apache/commons/math/special/Gamma.java b/src/main/java/org/apache/commons/math/special/Gamma.java
new file mode 100644
index 0000000..327aa3a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/special/Gamma.java
@@ -0,0 +1,339 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.special;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.MaxIterationsExceededException;
+import org.apache.commons.math.util.ContinuedFraction;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * This is a utility class that provides computation methods related to the
+ * Gamma family of functions.
+ *
+ * @version $Revision: 1042510 $ $Date: 2010-12-06 02:54:18 +0100 (lun. 06 déc. 2010) $
+ */
+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;
+
+    /** 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);
+
+    // 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;
+
+    /**
+     * Default constructor.  Prohibit instantiation.
+     */
+    private Gamma() {
+        super();
+    }
+
+    /**
+     * Returns the natural logarithm of the gamma function &#915;(x).
+     *
+     * The implementation of this method is based on:
+     * <ul>
+     * <li><a href="http://mathworld.wolfram.com/GammaFunction.html">
+     * Gamma Function</a>, equation (28).</li>
+     * <li><a href="http://mathworld.wolfram.com/LanczosApproximation.html">
+     * Lanczos Approximation</a>, equations (1) through (5).</li>
+     * <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></li>
+     * </ul>
+     *
+     * @param x the value.
+     * @return log(&#915;(x))
+     */
+    public static double logGamma(double x) {
+        double ret;
+
+        if (Double.isNaN(x) || (x <= 0.0)) {
+            ret = Double.NaN;
+        } else {
+            double g = 607.0 / 128.0;
+
+            double sum = 0.0;
+            for (int i = LANCZOS.length - 1; i > 0; --i) {
+                sum = sum + (LANCZOS[i] / (x + i));
+            }
+            sum = sum + LANCZOS[0];
+
+            double tmp = x + 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 the a parameter.
+     * @param x the value.
+     * @return the regularized gamma function P(a, x)
+     * @throws MathException if the algorithm fails to converge.
+     */
+    public static double regularizedGammaP(double a, double x)
+        throws MathException
+    {
+        return regularizedGammaP(a, x, DEFAULT_EPSILON, Integer.MAX_VALUE);
+    }
+
+
+    /**
+     * Returns the regularized gamma function P(a, x).
+     *
+     * 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>
+     * <li>
+     * <a href="http://mathworld.wolfram.com/IncompleteGammaFunction.html">
+     * Incomplete Gamma Function</a>, equation (4).</li>
+     * <li>
+     * <a href="http://mathworld.wolfram.com/ConfluentHypergeometricFunctionoftheFirstKind.html">
+     * Confluent Hypergeometric Function of the First Kind</a>, equation (1).
+     * </li>
+     * </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 MathException if the algorithm fails to converge.
+     */
+    public static double regularizedGammaP(double a,
+                                           double x,
+                                           double epsilon,
+                                           int maxIterations)
+        throws MathException
+    {
+        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 = n + 1.0;
+                an = an * (x / (a + n));
+
+                // update partial sum
+                sum = sum + an;
+            }
+            if (n >= maxIterations) {
+                throw new MaxIterationsExceededException(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 MathException if the algorithm fails to converge.
+     */
+    public static double regularizedGammaQ(double a, double x)
+        throws MathException
+    {
+        return regularizedGammaQ(a, x, DEFAULT_EPSILON, Integer.MAX_VALUE);
+    }
+
+    /**
+     * Returns the regularized gamma function Q(a, x) = 1 - P(a, x).
+     *
+     * 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>
+     * <li>
+     * <a href="http://functions.wolfram.com/GammaBetaErf/GammaRegularized/10/0003/">
+     * Regularized incomplete gamma function: Continued fraction representations  (formula 06.08.10.0003)</a></li>
+     * </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 MathException if the algorithm fails to converge.
+     */
+    public static double regularizedGammaQ(final double a,
+                                           double x,
+                                           double epsilon,
+                                           int maxIterations)
+        throws MathException
+    {
+        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() {
+
+                @Override
+                protected double getA(int n, double x) {
+                    return ((2.0 * n) + 1.0) - a + x;
+                }
+
+                @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;
+    }
+
+
+    /**
+     * <p>Computes the digamma function of x.</p>
+     *
+     * <p>This is an independently written implementation of the algorithm described in
+     * Jose Bernardo, Algorithm AS 103: Psi (Digamma) Function, Applied Statistics, 1976.</p>
+     *
+     * <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>
+     *
+     * <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.</p>
+     *
+     * @param x  the 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 at wikipedia </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 (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;
+    }
+
+    /**
+     * <p>Computes the trigamma function of x.  This function is derived by taking the derivative of
+     * the implementation of digamma.</p>
+     *
+     * @param x  the 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 at wikipedia </a>
+     * @see Gamma#digamma(double)
+     * @since 2.0
+     */
+    public static double trigamma(double 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);
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/special/package.html b/src/main/java/org/apache/commons/math/special/package.html
new file mode 100644
index 0000000..0676334
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/special/package.html
@@ -0,0 +1,20 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 480440 $ $Date: 2006-11-29 08:14:12 +0100 (mer. 29 nov. 2006) $ -->
+    <body>Implementations of special functions such as Beta and Gamma.</body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/stat/Frequency.java b/src/main/java/org/apache/commons/math/stat/Frequency.java
new file mode 100644
index 0000000..434819e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/Frequency.java
@@ -0,0 +1,603 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.stat;
+
+import java.io.Serializable;
+import java.text.NumberFormat;
+import java.util.Iterator;
+import java.util.Comparator;
+import java.util.TreeMap;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * 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>
+ * <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>
+ * <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>
+ * <p>
+ * The values are ordered using the default (natural order), unless a
+ * <code>Comparator</code> is supplied in the constructor.</p>
+ *
+ * @version $Revision: 1054186 $ $Date: 2011-01-01 03:28:46 +0100 (sam. 01 janv. 2011) $
+ */
+public class Frequency implements Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -3845586908418844111L;
+
+    /** underlying collection */
+    private final TreeMap<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.
+     * </p>
+     *
+     * @param v the value to add.
+     * @throws IllegalArgumentException if <code>v</code> is not Comparable,
+     *         or is not comparable with previous entries
+     * @deprecated use {@link #addValue(Comparable)} instead
+     */
+    @Deprecated
+    public void addValue(Object v) {
+        if (v instanceof Comparable<?>){
+            addValue((Comparable<?>) v);
+        } else {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.CLASS_DOESNT_IMPLEMENT_COMPARABLE,
+                  v.getClass().getName());
+        }
+    }
+
+    /**
+     * 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.
+     * </p>
+     *
+     * @param v the value to add.
+     * @throws IllegalArgumentException if <code>v</code> is not comparable with previous entries
+     */
+    public void addValue(Comparable<?> v){
+        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(1));
+            } else {
+                freqTable.put(obj, Long.valueOf(count.longValue() + 1));
+            }
+        } catch (ClassCastException ex) {
+            //TreeMap will throw ClassCastException if v is not comparable
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.INSTANCES_NOT_COMPARABLE_TO_EXISTING_VALUES,
+                  v.getClass().getName());
+        }
+    }
+
+    /**
+     * Adds 1 to the frequency count for v.
+     *
+     * @param v the value to add.
+     */
+    public void addValue(int v) {
+        addValue(Long.valueOf(v));
+    }
+
+    /**
+     * Adds 1 to the frequency count for v.
+     *
+     * @param v the value to add.
+     * @deprecated to be removed in math 3.0
+     */
+    @Deprecated
+    public void addValue(Integer v) {
+        addValue(Long.valueOf(v.longValue()));
+    }
+
+    /**
+     * Adds 1 to the frequency count for v.
+     *
+     * @param v the value to add.
+     */
+    public void addValue(long v) {
+        addValue(Long.valueOf(v));
+    }
+
+    /**
+     * Adds 1 to the frequency count for v.
+     *
+     * @param v the value to add.
+     */
+    public void addValue(char v) {
+        addValue(Character.valueOf(v));
+    }
+
+    /** 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.</p>
+     *
+     * @return values Iterator
+     */
+    public Iterator<Comparable<?>> valuesIterator() {
+        return freqTable.keySet().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 = v.
+     * Returns 0 if the value is not comparable.
+     *
+     * @param v the value to lookup.
+     * @return the frequency of v.
+     * @deprecated replaced by {@link #getCount(Comparable)} as of 2.0
+     */
+    @Deprecated
+    public long getCount(Object v) {
+        return getCount((Comparable<?>) v);
+    }
+
+    /**
+     * Returns the number of values = 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) {
+            // ignore and return 0 -- ClassCastException will be thrown if value is not comparable
+        }
+        return result;
+    }
+
+    /**
+     * Returns the number of values = 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 = 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 = 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.</p>
+     *
+     * @param v the value to lookup
+     * @return the proportion of values equal to v
+     * @deprecated replaced by {@link #getPct(Comparable)} as of 2.0
+     */
+    @Deprecated
+    public double getPct(Object v) {
+        return getPct((Comparable<?>) v);
+    }
+
+    /**
+     * 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.</p>
+     *
+     * @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.</p>
+     *
+     * @param v the value to lookup.
+     * @return the proportion of values equal to v
+     * @deprecated replaced by {@link #getCumFreq(Comparable)} as of 2.0
+     */
+    @Deprecated
+    public long getCumFreq(Object v) {
+        return getCumFreq((Comparable<?>) 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.</p>
+     *
+     * @param v the value to lookup.
+     * @return the proportion of values equal to v
+     */
+    public long getCumFreq(Comparable<?> v) {
+        if (getSumFreq() == 0) {
+            return 0;
+        }
+        if (v instanceof Integer) {
+            return getCumFreq(((Integer) v).longValue());
+        }
+        @SuppressWarnings("unchecked") // OK, freqTable is Comparable<?>
+        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.</p>
+     *
+     * @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.</p>
+     *
+     * @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.</p>
+     *
+     * @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.</p>
+     *
+     * @param v the value to lookup
+     * @return the proportion of values less than or equal to v
+     * @deprecated replaced by {@link #getCumPct(Comparable)} as of 2.0
+     */
+    @Deprecated
+    public double getCumPct(Object v) {
+        return getCumPct((Comparable<?>) 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.</p>
+     *
+     * @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.</p>
+     *
+     * @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.</p>
+     *
+     * @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.</p>
+     *
+     * @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));
+    }
+
+    /**
+     * A Comparator that compares comparable objects using the
+     * natural order.  Copied from Commons Collections ComparableComparator.
+     */
+    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/math/stat/StatUtils.java b/src/main/java/org/apache/commons/math/stat/StatUtils.java
new file mode 100644
index 0000000..7ae1e17
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/StatUtils.java
@@ -0,0 +1,663 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.stat;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.stat.descriptive.DescriptiveStatistics;
+import org.apache.commons.math.stat.descriptive.UnivariateStatistic;
+import org.apache.commons.math.stat.descriptive.moment.GeometricMean;
+import org.apache.commons.math.stat.descriptive.moment.Mean;
+import org.apache.commons.math.stat.descriptive.moment.Variance;
+import org.apache.commons.math.stat.descriptive.rank.Max;
+import org.apache.commons.math.stat.descriptive.rank.Min;
+import org.apache.commons.math.stat.descriptive.rank.Percentile;
+import org.apache.commons.math.stat.descriptive.summary.Product;
+import org.apache.commons.math.stat.descriptive.summary.Sum;
+import org.apache.commons.math.stat.descriptive.summary.SumOfLogs;
+import org.apache.commons.math.stat.descriptive.summary.SumOfSquares;
+
+/**
+ * StatUtils provides static methods for computing statistics based on data
+ * stored in double[] arrays.
+ *
+ * @version $Revision: 1073276 $ $Date: 2011-02-22 10:34:52 +0100 (mar. 22 févr. 2011) $
+ */
+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.</p>
+     *
+     * @param values  array of values to sum
+     * @return the sum of the values or <code>Double.NaN</code> if the array
+     * is empty
+     * @throws IllegalArgumentException if the array is null
+     */
+    public static double sum(final double[] values) {
+        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.</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 Double.NaN if length = 0
+     * @throws IllegalArgumentException 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) {
+        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.</p>
+     *
+     * @param values  input array
+     * @return the sum of the squared values or <code>Double.NaN</code> if the
+     * array is empty
+     * @throws IllegalArgumentException if the array is null
+     */
+    public static double sumSq(final double[] values) {
+        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.</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 Double.NaN if length = 0
+     * @throws IllegalArgumentException 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) {
+        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.</p>
+     *
+     * @param values the input array
+     * @return the product of the values or Double.NaN if the array is empty
+     * @throws IllegalArgumentException if the array is null
+     */
+    public static double product(final double[] values) {
+        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.</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 Double.NaN if length = 0
+     * @throws IllegalArgumentException 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) {
+        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>
+     * <p>
+     * See {@link org.apache.commons.math.stat.descriptive.summary.SumOfLogs}.
+     * </p>
+     *
+     * @param values the input array
+     * @return the sum of the natural logs of the values or Double.NaN if
+     * the array is empty
+     * @throws IllegalArgumentException if the array is null
+     */
+    public static double sumLog(final double[] values) {
+        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>
+     * <p>
+     * See {@link org.apache.commons.math.stat.descriptive.summary.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 Double.NaN if
+     * length = 0
+     * @throws IllegalArgumentException 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) {
+        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>
+     * <p>
+     * See {@link org.apache.commons.math.stat.descriptive.moment.Mean} for
+     * details on the computing algorithm.</p>
+     *
+     * @param values the input array
+     * @return the mean of the values or Double.NaN if the array is empty
+     * @throws IllegalArgumentException if the array is null
+     */
+    public static double mean(final double[] values) {
+        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>
+     * <p>
+     * See {@link org.apache.commons.math.stat.descriptive.moment.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 IllegalArgumentException 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) {
+        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>
+     * <p>
+     * See {@link org.apache.commons.math.stat.descriptive.moment.GeometricMean}
+     * for details on the computing algorithm.</p>
+     *
+     * @param values the input array
+     * @return the geometric mean of the values or Double.NaN if the array is empty
+     * @throws IllegalArgumentException if the array is null
+     */
+    public static double geometricMean(final double[] values) {
+        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>
+     * <p>
+     * See {@link org.apache.commons.math.stat.descriptive.moment.GeometricMean}
+     * 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 geometric mean of the values or Double.NaN if length = 0
+     * @throws IllegalArgumentException 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) {
+        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>
+     * See {@link org.apache.commons.math.stat.descriptive.moment.Variance} for
+     * details on the computing algorithm.</p>
+     * <p>
+     * Returns 0 for a single-value (i.e. length = 1) sample.</p>
+     * <p>
+     * Throws <code>IllegalArgumentException</code> if the array is null.</p>
+     *
+     * @param values the input array
+     * @return the variance of the values or Double.NaN if the array is empty
+     * @throws IllegalArgumentException if the array is null
+     */
+    public static double variance(final double[] values) {
+        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>
+     * See {@link org.apache.commons.math.stat.descriptive.moment.Variance} for
+     * details on the computing algorithm.</p>
+     * <p>
+     * Returns 0 for a single-value (i.e. length = 1) sample.</p>
+     * <p>
+     * Throws <code>IllegalArgumentException</code> if the array is null or the
+     * array index parameters are not valid.</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 IllegalArgumentException 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) {
+        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>
+     * See {@link org.apache.commons.math.stat.descriptive.moment.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>IllegalArgumentException</code> if the array is null or the
+     * array index parameters are not valid.</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 IllegalArgumentException 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) {
+        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>
+     * See {@link org.apache.commons.math.stat.descriptive.moment.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>IllegalArgumentException</code> if the array is null.</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 IllegalArgumentException if the array is null
+     */
+    public static double variance(final double[] values, final double mean) {
+        return VARIANCE.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>IllegalArgumentException</code> if the array is null.</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
+     * @return the maximum of the values or Double.NaN if the array is empty
+     * @throws IllegalArgumentException if the array is null
+     */
+    public static double max(final double[] values) {
+        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>IllegalArgumentException</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 IllegalArgumentException 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) {
+        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>IllegalArgumentException</code> if the array is null.</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
+     * @return the minimum of the values or Double.NaN if the array is empty
+     * @throws IllegalArgumentException if the array is null
+     */
+    public static double min(final double[] values) {
+        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>IllegalArgumentException</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 IllegalArgumentException 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) {
+        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></p>
+     * <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>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)</li>
+     * </ul></p>
+     * <p>
+     * See {@link org.apache.commons.math.stat.descriptive.rank.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 IllegalArgumentException if <code>values</code> is null
+     * or p is invalid
+     */
+    public static double percentile(final double[] values, final double p) {
+            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>
+     * <li>Returns (for any value of <code>p</code>) <code>values[begin]</code>
+     *  if <code>length = 1 </code></li>
+     * <li>Throws <code>IllegalArgumentException</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 org.apache.commons.math.stat.descriptive.rank.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 IllegalArgumentException 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) {
+        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 IllegalArgumentException if the arrays do not have the same
+     * (positive) length
+     */
+    public static double sumDifference(final double[] sample1, final double[] sample2)
+        throws IllegalArgumentException {
+        int n = sample1.length;
+        if (n  != sample2.length) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, n, sample2.length);
+        }
+        if (n < 1) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.INSUFFICIENT_DIMENSION, sample2.length, 1);
+        }
+        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 IllegalArgumentException if the arrays do not have the same
+     * (positive) length
+     */
+    public static double meanDifference(final double[] sample1, final double[] sample2)
+    throws IllegalArgumentException {
+        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 IllegalArgumentException if the arrays do not have the same
+     * length or their common length is less than 2.
+     */
+    public static double varianceDifference(final double[] sample1, final double[] sample2,
+            double meanDifference)  throws IllegalArgumentException {
+        double sum1 = 0d;
+        double sum2 = 0d;
+        double diff = 0d;
+        int n = sample1.length;
+        if (n != sample2.length) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, n, sample2.length);
+        }
+        if (n < 2) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.INSUFFICIENT_DIMENSION, n, 2);
+        }
+        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 series, so in the end it is having 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;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/stat/clustering/Cluster.java b/src/main/java/org/apache/commons/math/stat/clustering/Cluster.java
new file mode 100644
index 0000000..f4913d3
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/clustering/Cluster.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.math.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
+ * @version $Revision: 771076 $ $Date: 2009-05-03 18:28:48 +0200 (dim. 03 mai 2009) $
+ * @since 2.0
+ */
+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/math/stat/clustering/Clusterable.java b/src/main/java/org/apache/commons/math/stat/clustering/Clusterable.java
new file mode 100644
index 0000000..65132e6
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/clustering/Clusterable.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.math.stat.clustering;
+
+import java.util.Collection;
+
+/**
+ * Interface for points that can be clustered together.
+ * @param <T> the type of point that can be clustered
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ * @since 2.0
+ */
+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/math/stat/clustering/EuclideanIntegerPoint.java b/src/main/java/org/apache/commons/math/stat/clustering/EuclideanIntegerPoint.java
new file mode 100644
index 0000000..7fec0ff
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/clustering/EuclideanIntegerPoint.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.math.stat.clustering;
+
+import java.io.Serializable;
+import java.util.Collection;
+
+import org.apache.commons.math.util.MathUtils;
+
+/**
+ * A simple implementation of {@link Clusterable} for points with integer coordinates.
+ * @version $Revision: 1042376 $ $Date: 2010-12-05 16:54:55 +0100 (dim. 05 déc. 2010) $
+ * @since 2.0
+ */
+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 MathUtils.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;
+        }
+        final int[] otherPoint = ((EuclideanIntegerPoint) other).getPoint();
+        if (point.length != otherPoint.length) {
+            return false;
+        }
+        for (int i = 0; i < point.length; i++) {
+            if (point[i] != otherPoint[i]) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        int hashCode = 0;
+        for (Integer i : point) {
+            hashCode += i.hashCode() * 13 + 7;
+        }
+        return hashCode;
+    }
+
+    /**
+     * {@inheritDoc}
+     * @since 2.1
+     */
+    @Override
+    public String toString() {
+        final StringBuilder buff = new StringBuilder("(");
+        final int[] coordinates = getPoint();
+        for (int i = 0; i < coordinates.length; i++) {
+            buff.append(coordinates[i]);
+            if (i < coordinates.length - 1) {
+                buff.append(",");
+            }
+        }
+        buff.append(")");
+        return buff.toString();
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/stat/clustering/KMeansPlusPlusClusterer.java b/src/main/java/org/apache/commons/math/stat/clustering/KMeansPlusPlusClusterer.java
new file mode 100644
index 0000000..eb61866
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/clustering/KMeansPlusPlusClusterer.java
@@ -0,0 +1,333 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.stat.clustering;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Random;
+
+import org.apache.commons.math.exception.ConvergenceException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.stat.descriptive.moment.Variance;
+
+/**
+ * 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>
+ * @version $Revision: 1054333 $ $Date: 2011-01-02 01:34:58 +0100 (dim. 02 janv. 2011) $
+ * @since 2.0
+ */
+public class KMeansPlusPlusClusterer<T extends Clusterable<T>> {
+
+    /** Strategies to use for replacing an empty cluster. */
+    public static 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 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
+     */
+    public List<Cluster<T>> cluster(final Collection<T> points,
+                                    final int k, final int maxIterations) {
+        // create the initial clusters
+        List<Cluster<T>> clusters = chooseInitialCenters(points, k, random);
+        assignPointsToClusters(clusters, points);
+
+        // 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 clusteringChanged = 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);
+                    }
+                    clusteringChanged = true;
+                } else {
+                    newCenter = cluster.getCenter().centroidOf(cluster.getPoints());
+                    if (!newCenter.equals(cluster.getCenter())) {
+                        clusteringChanged = true;
+                    }
+                }
+                newClusters.add(new Cluster<T>(newCenter));
+            }
+            if (!clusteringChanged) {
+                return clusters;
+            }
+            assignPointsToClusters(newClusters, points);
+            clusters = newClusters;
+        }
+        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
+     */
+    private static <T extends Clusterable<T>> void
+        assignPointsToClusters(final Collection<Cluster<T>> clusters, final Collection<T> points) {
+        for (final T p : points) {
+            Cluster<T> cluster = getNearestCluster(clusters, p);
+            cluster.addPoint(p);
+        }
+    }
+
+    /**
+     * 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) {
+
+        final List<T> pointSet = new ArrayList<T>(points);
+        final List<Cluster<T>> resultSet = new ArrayList<Cluster<T>>();
+
+        // Choose one center uniformly at random from among the data points.
+        final T firstPoint = pointSet.remove(random.nextInt(pointSet.size()));
+        resultSet.add(new Cluster<T>(firstPoint));
+
+        final double[] dx2 = new double[pointSet.size()];
+        while (resultSet.size() < k) {
+            // For each data point x, compute D(x), the distance between x and
+            // the nearest center that has already been chosen.
+            int sum = 0;
+            for (int i = 0; i < pointSet.size(); i++) {
+                final T p = pointSet.get(i);
+                final Cluster<T> nearest = getNearestCluster(resultSet, p);
+                final double d = p.distanceFrom(nearest.getCenter());
+                sum += d * d;
+                dx2[i] = sum;
+            }
+
+            // 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() * sum;
+            for (int i = 0 ; i < dx2.length; i++) {
+                if (dx2[i] >= r) {
+                    final T p = pointSet.remove(i);
+                    resultSet.add(new Cluster<T>(p));
+                    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
+     */
+    private T getPointFromLargestVarianceCluster(final Collection<Cluster<T>> clusters) {
+
+        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
+     */
+    private T getPointFromLargestNumberCluster(final Collection<Cluster<T>> clusters) {
+
+        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
+     */
+    private T getFarthestPoint(final Collection<Cluster<T>> clusters) {
+
+        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 nearest {@link Cluster} to the given point
+     */
+    private static <T extends Clusterable<T>> Cluster<T>
+        getNearestCluster(final Collection<Cluster<T>> clusters, final T point) {
+        double minDistance = Double.MAX_VALUE;
+        Cluster<T> minCluster = null;
+        for (final Cluster<T> c : clusters) {
+            final double distance = point.distanceFrom(c.getCenter());
+            if (distance < minDistance) {
+                minDistance = distance;
+                minCluster = c;
+            }
+        }
+        return minCluster;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/stat/clustering/package.html b/src/main/java/org/apache/commons/math/stat/clustering/package.html
new file mode 100644
index 0000000..21e9079
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/clustering/package.html
@@ -0,0 +1,20 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 770979 $ $Date: 2009-05-02 21:34:51 +0200 (sam. 02 mai 2009) $ -->
+    <body>Clustering algorithms</body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/stat/correlation/Covariance.java b/src/main/java/org/apache/commons/math/stat/correlation/Covariance.java
new file mode 100644
index 0000000..393a02d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/correlation/Covariance.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.math.stat.correlation;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.linear.RealMatrix;
+import org.apache.commons.math.linear.BlockRealMatrix;
+import org.apache.commons.math.stat.descriptive.moment.Mean;
+import org.apache.commons.math.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>
+ *
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ * @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 two columns
+     * and two rows.</p>
+     *
+     * @param data rectangular array with columns representing covariates
+     * @param biasCorrected true means covariances are bias-corrected
+     * @throws IllegalArgumentException if the input data array is not
+     * rectangular with at least two rows and two columns.
+     */
+    public Covariance(double[][] data, boolean biasCorrected) {
+        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 two columns
+     * and two rows</p>
+     *
+     * @param data rectangular array with columns representing covariates
+     * @throws IllegalArgumentException if the input data array is not
+     * rectangular with at least two rows and two columns.
+     */
+    public Covariance(double[][] data) {
+        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 two columns and two rows</p>
+     *
+     * @param matrix matrix with columns representing covariates
+     * @param biasCorrected true means covariances are bias-corrected
+     * @throws IllegalArgumentException if the input matrix does not have
+     * at least two rows and two columns
+     */
+    public Covariance(RealMatrix matrix, boolean biasCorrected) {
+       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 two columns and two rows</p>
+     *
+     * @param matrix matrix with columns representing covariates
+     * @throws IllegalArgumentException if the input matrix does not have
+     * at least two rows and two columns
+     */
+    public Covariance(RealMatrix matrix) {
+        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 two columns and two rows)
+     * @param biasCorrected determines whether or not covariance estimates are bias-corrected
+     * @return covariance matrix
+     */
+    protected RealMatrix computeCovarianceMatrix(RealMatrix matrix, boolean biasCorrected) {
+        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 two columns and two rows)
+     * @return covariance matrix
+     * @see #Covariance
+     */
+    protected RealMatrix computeCovarianceMatrix(RealMatrix matrix) {
+        return computeCovarianceMatrix(matrix, true);
+    }
+
+    /**
+     * Compute a covariance matrix from a rectangular array whose columns represent
+     * covariates.
+     * @param data input array (must have at least two columns and two rows)
+     * @param biasCorrected determines whether or not covariance estimates are bias-corrected
+     * @return covariance matrix
+     */
+    protected RealMatrix computeCovarianceMatrix(double[][] data, boolean biasCorrected) {
+        return computeCovarianceMatrix(new BlockRealMatrix(data), biasCorrected);
+    }
+
+    /**
+     * Create a covariance matrix from a rectangual array whose columns represent
+     * covariates. Covariances are computed using the bias-corrected formula.
+     * @param data input array (must have at least two columns and two rows)
+     * @return covariance matrix
+     * @see #Covariance
+     */
+    protected RealMatrix computeCovarianceMatrix(double[][] data) {
+        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  IllegalArgumentException if the arrays lengths do not match or
+     * there is insufficient data
+     */
+    public double covariance(final double[] xArray, final double[] yArray, boolean biasCorrected)
+        throws IllegalArgumentException {
+        Mean mean = new Mean();
+        double result = 0d;
+        int length = xArray.length;
+        if (length != yArray.length) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, length, yArray.length);
+        } else if (length < 2) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.INSUFFICIENT_DIMENSION, 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  IllegalArgumentException if the arrays lengths do not match or
+     * there is insufficient data
+     */
+    public double covariance(final double[] xArray, final double[] yArray)
+        throws IllegalArgumentException {
+        return covariance(xArray, yArray, true);
+    }
+
+    /**
+     * Throws IllegalArgumentException of the matrix does not have at least
+     * two columns and two rows
+     * @param matrix matrix to check
+     */
+    private void checkSufficientData(final RealMatrix matrix) {
+        int nRows = matrix.getRowDimension();
+        int nCols = matrix.getColumnDimension();
+        if (nRows < 2 || nCols < 2) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.INSUFFICIENT_ROWS_AND_COLUMNS,
+                    nRows, nCols);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/stat/correlation/PearsonsCorrelation.java b/src/main/java/org/apache/commons/math/stat/correlation/PearsonsCorrelation.java
new file mode 100644
index 0000000..6467c69
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/correlation/PearsonsCorrelation.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.math.stat.correlation;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.distribution.TDistribution;
+import org.apache.commons.math.distribution.TDistributionImpl;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.exception.NullArgumentException;
+import org.apache.commons.math.exception.DimensionMismatchException;
+import org.apache.commons.math.linear.RealMatrix;
+import org.apache.commons.math.linear.BlockRealMatrix;
+import org.apache.commons.math.stat.regression.SimpleRegression;
+import org.apache.commons.math.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>
+ * <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.
+ *
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ * @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.
+     *
+     * @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 PearsonsCorrelation(double[][] data) {
+        this(new BlockRealMatrix(data));
+    }
+
+    /**
+     * Create a PearsonsCorrelation from a RealMatrix whose columns
+     * represent variables to be correlated.
+     *
+     * @param matrix matrix with columns representing variables to correlate
+     */
+    public PearsonsCorrelation(RealMatrix matrix) {
+        checkSufficientData(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
+     *
+     * @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>
+     *
+     * @return matrix of correlation standard errors
+     */
+    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>
+     *
+     * @return matrix of p-values
+     * @throws MathException if an error occurs estimating probabilities
+     */
+    public RealMatrix getCorrelationPValues() throws MathException {
+        TDistribution tDistribution = new TDistributionImpl(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.
+     *
+     * @param matrix matrix with columns representing variables to correlate
+     * @return correlation matrix
+     */
+    public RealMatrix computeCorrelationMatrix(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 correlation matrix for the columns of the
+     * input rectangular array.  The colums of the array represent values
+     * of variables to be correlated.
+     *
+     * @param data matrix with columns representing variables to correlate
+     * @return correlation matrix
+     */
+    public RealMatrix computeCorrelationMatrix(double[][] data) {
+       return computeCorrelationMatrix(new BlockRealMatrix(data));
+    }
+
+    /**
+     * Computes the Pearson's product-moment correlation coefficient between the two arrays.
+     *
+     * </p>Throws IllegalArgumentException if the arrays do not have the same length
+     * or their common length is less than 2</p>
+     *
+     * @param xArray first data array
+     * @param yArray second data array
+     * @return Returns Pearson's correlation coefficient for the two arrays
+     * @throws  IllegalArgumentException if the arrays lengths do not match or
+     * there is insufficient data
+     */
+    public double correlation(final double[] xArray, final double[] yArray) throws IllegalArgumentException {
+        SimpleRegression regression = new SimpleRegression();
+        if (xArray.length != yArray.length) {
+            throw new DimensionMismatchException(xArray.length, yArray.length);
+        } else if (xArray.length < 2) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  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 IllegalArgumentException of the matrix does not have at least
+     * two columns and two rows
+     *
+     * @param matrix matrix to check for sufficiency
+     */
+    private void checkSufficientData(final RealMatrix matrix) {
+        int nRows = matrix.getRowDimension();
+        int nCols = matrix.getColumnDimension();
+        if (nRows < 2 || nCols < 2) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.INSUFFICIENT_ROWS_AND_COLUMNS,
+                    nRows, nCols);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/stat/correlation/SpearmansCorrelation.java b/src/main/java/org/apache/commons/math/stat/correlation/SpearmansCorrelation.java
new file mode 100644
index 0000000..fe121fe
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/correlation/SpearmansCorrelation.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.math.stat.correlation;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.linear.BlockRealMatrix;
+import org.apache.commons.math.linear.RealMatrix;
+import org.apache.commons.math.stat.ranking.NaturalRanking;
+import org.apache.commons.math.stat.ranking.RankingAlgorithm;
+
+/**
+ * <p>Spearman's rank correlation. This implementation performs a rank
+ * transformation on the input data and then computes {@link PearsonsCorrelation}
+ * on the ranked data.</p>
+ *
+ * <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.</p>
+ *
+ * @since 2.0
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ */
+
+public class SpearmansCorrelation {
+
+    /** Input data */
+    private final RealMatrix data;
+
+    /** Ranking algorithm  */
+    private final RankingAlgorithm rankingAlgorithm;
+
+    /** Rank correlation */
+    private final PearsonsCorrelation rankCorrelation;
+
+    /**
+     * Create a SpearmansCorrelation with the given input data matrix
+     * and ranking algorithm.
+     *
+     * @param dataMatrix matrix of data with columns representing
+     * variables to correlate
+     * @param rankingAlgorithm ranking algorithm
+     */
+    public SpearmansCorrelation(final RealMatrix dataMatrix, final RankingAlgorithm rankingAlgorithm) {
+        this.data = dataMatrix.copy();
+        this.rankingAlgorithm = rankingAlgorithm;
+        rankTransform(data);
+        rankCorrelation = new PearsonsCorrelation(data);
+    }
+
+    /**
+     * 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 without data.
+     */
+    public SpearmansCorrelation() {
+        data = null;
+        this.rankingAlgorithm = new NaturalRanking();
+        rankCorrelation = null;
+    }
+
+    /**
+     * Calculate the Spearman Rank Correlation Matrix.
+     *
+     * @return Spearman Rank Correlation Matrix
+     */
+    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>
+     *
+     * @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(RealMatrix matrix) {
+        RealMatrix matrixCopy = matrix.copy();
+        rankTransform(matrixCopy);
+        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(double[][] matrix) {
+       return computeCorrelationMatrix(new BlockRealMatrix(matrix));
+    }
+
+    /**
+     * Computes the Spearman's rank correlation coefficient between the two arrays.
+     *
+     * </p>Throws IllegalArgumentException if the arrays do not have the same length
+     * or their common length is less than 2</p>
+     *
+     * @param xArray first data array
+     * @param yArray second data array
+     * @return Returns Spearman's rank correlation coefficient for the two arrays
+     * @throws  IllegalArgumentException if the arrays lengths do not match or
+     * there is insufficient data
+     */
+    public double correlation(final double[] xArray, final double[] yArray)
+    throws IllegalArgumentException {
+        if (xArray.length != yArray.length) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, xArray.length, yArray.length);
+        } else if (xArray.length < 2) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.INSUFFICIENT_DIMENSION, xArray.length, 2);
+        } else {
+            return new PearsonsCorrelation().correlation(rankingAlgorithm.rank(xArray),
+                    rankingAlgorithm.rank(yArray));
+        }
+    }
+
+    /**
+     * Applies rank transform to each of the columns of <code>matrix</code>
+     * using the current <code>rankingAlgorithm</code>
+     *
+     * @param matrix matrix to transform
+     */
+    private void rankTransform(RealMatrix matrix) {
+        for (int i = 0; i < matrix.getColumnDimension(); i++) {
+            matrix.setColumn(i, rankingAlgorithm.rank(matrix.getColumn(i)));
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/stat/correlation/package.html b/src/main/java/org/apache/commons/math/stat/correlation/package.html
new file mode 100644
index 0000000..8b12fc2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/correlation/package.html
@@ -0,0 +1,22 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 744716 $ $Date: 2009-02-15 19:38:49 +0100 (dim. 15 févr. 2009) $ -->
+    <body>
+        Correlations/Covariance computations.
+    </body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/stat/descriptive/AbstractStorelessUnivariateStatistic.java b/src/main/java/org/apache/commons/math/stat/descriptive/AbstractStorelessUnivariateStatistic.java
new file mode 100644
index 0000000..9e721ea
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/AbstractStorelessUnivariateStatistic.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.math.stat.descriptive;
+
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.exception.NullArgumentException;
+import org.apache.commons.math.util.MathUtils;
+
+/**
+ *
+ * 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>
+ *
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ */
+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, an IllegalArgumentException is thrown.</p>
+     * @param values input array
+     * @return the value of the statistic applied to the input array
+     * @see org.apache.commons.math.stat.descriptive.UnivariateStatistic#evaluate(double[])
+     */
+    @Override
+    public double evaluate(final double[] values) {
+        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
+     * IllegalArgumentException 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
+     * @see org.apache.commons.math.stat.descriptive.UnivariateStatistic#evaluate(double[], int, int)
+     */
+    @Override
+    public double evaluate(final double[] values, final int begin, final int length) {
+        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 IllegalArgumentException if values is null
+     * @see org.apache.commons.math.stat.descriptive.StorelessUnivariateStatistic#incrementAll(double[])
+     */
+    public void incrementAll(double[] values) {
+        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 IllegalArgumentException if values is null
+     * @see org.apache.commons.math.stat.descriptive.StorelessUnivariateStatistic#incrementAll(double[], int, int)
+     */
+    public void incrementAll(double[] values, int begin, int length) {
+        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 MathUtils.equalsIncludingNaN(stat.getResult(), this.getResult()) &&
+               MathUtils.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/math/stat/descriptive/AbstractUnivariateStatistic.java b/src/main/java/org/apache/commons/math/stat/descriptive/AbstractUnivariateStatistic.java
new file mode 100644
index 0000000..354dee6
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/AbstractUnivariateStatistic.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.math.stat.descriptive;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.DimensionMismatchException;
+import org.apache.commons.math.exception.NotPositiveException;
+import org.apache.commons.math.exception.NullArgumentException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * 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>
+ *
+ * @version $Revision: 1006299 $ $Date: 2010-10-10 16:47:17 +0200 (dim. 10 oct. 2010) $
+ */
+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.
+     * @param values data array to store
+     * @param begin the index of the first element to include
+     * @param length the number of elements to include
+     * @see #evaluate()
+     */
+    public void setData(final double[] values, final int begin, final int length) {
+        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
+     * </p>
+     * @return the value of the statistic applied to the stored data
+     */
+    public double evaluate() {
+        return evaluate(storedData);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public double evaluate(final double[] values) {
+        test(values, 0, 0);
+        return evaluate(values, 0, values.length);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public abstract double evaluate(final double[] values, final int begin, final int length);
+
+    /**
+     * {@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>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.
+     * </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 IllegalArgumentException if the indices are invalid or the array is null
+     */
+    protected boolean test(
+        final double[] values,
+        final int begin,
+        final int length) {
+
+        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 MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.SUBARRAY_ENDS_AFTER_ARRAY_END);
+        }
+
+        if (length == 0) {
+            return false;
+        }
+
+        return true;
+
+    }
+
+    /**
+     * 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 IllegalArgumentException 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) {
+
+        if (weights == null) {
+            throw new NullArgumentException(LocalizedFormats.INPUT_ARRAY);
+        }
+
+        if (weights.length != values.length) {
+            throw new DimensionMismatchException(weights.length, values.length);
+        }
+
+        boolean containsPositiveWeight = false;
+        for (int i = begin; i < begin + length; i++) {
+            if (Double.isNaN(weights[i])) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                        LocalizedFormats.NAN_ELEMENT_AT_INDEX, i);
+            }
+            if (Double.isInfinite(weights[i])) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                        LocalizedFormats.INFINITE_ARRAY_ELEMENT, weights[i], i);
+            }
+            if (weights[i] < 0) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                      LocalizedFormats.NEGATIVE_ELEMENT_AT_INDEX, i, weights[i]);
+            }
+            if (!containsPositiveWeight && weights[i] > 0.0) {
+                containsPositiveWeight = true;
+            }
+        }
+
+        if (!containsPositiveWeight) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.WEIGHT_AT_LEAST_ONE_NON_ZERO);
+        }
+
+        return test(values, begin, length);
+    }
+}
+
diff --git a/src/main/java/org/apache/commons/math/stat/descriptive/AggregateSummaryStatistics.java b/src/main/java/org/apache/commons/math/stat/descriptive/AggregateSummaryStatistics.java
new file mode 100644
index 0000000..98c58c8
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/AggregateSummaryStatistics.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.math.stat.descriptive;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.Iterator;
+
+/**
+ * <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 unecessary computation and synchronization delays.</p>
+ *
+ * @since 2.0
+ * @version $Revision: 811833 $ $Date: 2009-09-06 18:27:50 +0200 (dim. 06 sept. 2009) $
+ *
+ */
+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() {
+        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.
+     * @see #createContributingStatistics()
+     */
+    public AggregateSummaryStatistics(SummaryStatistics prototypeStatistics) {
+        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);
+
+        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<SummaryStatistics> statistics) {
+        if (statistics == null) {
+            return null;
+        }
+        Iterator<SummaryStatistics> iterator = statistics.iterator();
+        if (!iterator.hasNext()) {
+            return null;
+        }
+        SummaryStatistics current = iterator.next();
+        long n = current.getN();
+        double min = current.getMin();
+        double sum = current.getSum();
+        double max = current.getMax();
+        double m2 = current.getSecondMoment();
+        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;
+            m2 = m2 + current.getSecondMoment() + 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
+         */
+        public 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/math/stat/descriptive/DescriptiveStatistics.java b/src/main/java/org/apache/commons/math/stat/descriptive/DescriptiveStatistics.java
new file mode 100644
index 0000000..e5a18dc
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/DescriptiveStatistics.java
@@ -0,0 +1,721 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.stat.descriptive;
+
+import java.io.Serializable;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Arrays;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.stat.descriptive.moment.GeometricMean;
+import org.apache.commons.math.stat.descriptive.moment.Kurtosis;
+import org.apache.commons.math.stat.descriptive.moment.Mean;
+import org.apache.commons.math.stat.descriptive.moment.Skewness;
+import org.apache.commons.math.stat.descriptive.moment.Variance;
+import org.apache.commons.math.stat.descriptive.rank.Max;
+import org.apache.commons.math.stat.descriptive.rank.Min;
+import org.apache.commons.math.stat.descriptive.rank.Percentile;
+import org.apache.commons.math.stat.descriptive.summary.Sum;
+import org.apache.commons.math.stat.descriptive.summary.SumOfSquares;
+import org.apache.commons.math.util.ResizableDoubleArray;
+import org.apache.commons.math.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>
+ *
+ * @version $Revision: 1054186 $ $Date: 2011-01-01 03:28:46 +0100 (sam. 01 janv. 2011) $
+ */
+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
+     */
+    protected 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.
+     */
+    public DescriptiveStatistics(int window) {
+        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
+     */
+    public DescriptiveStatistics(DescriptiveStatistics original) {
+        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.
+     */
+    public void removeMostRecentValue() {
+        eDA.discardMostRecentElements(1);
+    }
+
+    /**
+     * 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
+     */
+    public double replaceMostRecentValue(double v) {
+        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
+     * @return The geometricMean, Double.NaN if no values have been added,
+     * or if the product of the available values is less than or equal to 0.
+     */
+    public double getGeometricMean() {
+        return apply(geometricMeanImpl);
+    }
+
+    /**
+     * 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.
+     */
+    public double getVariance() {
+        return apply(varianceImpl);
+    }
+
+    /**
+     * 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 skewness of the available values. Skewness is a
+     * measure of the asymmetry of a given distribution.
+     * @return The skewness, Double.NaN if no values have been added
+     * or 0.0 for a value set &lt;=2.
+     */
+    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 no values have been added, or 0.0
+     * for a value set &lt;=3.
+     */
+    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 which 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
+     * @param windowSize sets the size of the window.
+     */
+    public void setWindowSize(int windowSize) {
+        if (windowSize < 1) {
+            if (windowSize != INFINITE_WINDOW) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                      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>IllegalArgumentException</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 IllegalStateException if percentile implementation has been
+     *  overridden and the supplied implementation does not support setQuantile
+     * values
+     */
+    public double getPercentile(double p) {
+        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 MathRuntimeException.createIllegalArgumentException(
+                      LocalizedFormats.PERCENTILE_IMPLEMENTATION_UNSUPPORTED_METHOD,
+                      percentileImpl.getClass().getName(), SET_QUANTILE_METHOD_NAME);
+            } catch (IllegalAccessException e2) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                      LocalizedFormats.PERCENTILE_IMPLEMENTATION_CANNOT_ACCESS_METHOD,
+                      SET_QUANTILE_METHOD_NAME, percentileImpl.getClass().getName());
+            } catch (InvocationTargetException e3) {
+                throw MathRuntimeException.createIllegalArgumentException(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);
+        outBuffer.append("median: ").append(getPercentile(50)).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) {
+        return stat.evaluate(eDA.getInternalValues(), eDA.start(), eDA.getNumElements());
+    }
+
+    // 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 IllegalArgumentException if the supplied implementation does not
+     *  provide a <code>setQuantile</code> method
+     * @since 1.2
+     */
+    public synchronized void setPercentileImpl(
+            UnivariateStatistic percentileImpl) {
+        try {
+            percentileImpl.getClass().getMethod(SET_QUANTILE_METHOD_NAME,
+                    new Class[] {Double.TYPE}).invoke(percentileImpl,
+                            new Object[] {Double.valueOf(50.0d)});
+        } catch (NoSuchMethodException e1) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.PERCENTILE_IMPLEMENTATION_UNSUPPORTED_METHOD,
+                  percentileImpl.getClass().getName(), SET_QUANTILE_METHOD_NAME);
+        } catch (IllegalAccessException e2) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.PERCENTILE_IMPLEMENTATION_CANNOT_ACCESS_METHOD,
+                  SET_QUANTILE_METHOD_NAME, percentileImpl.getClass().getName());
+        } catch (InvocationTargetException e3) {
+            throw MathRuntimeException.createIllegalArgumentException(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();
+        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 NullPointerException if either source or dest is null
+     */
+    public static void copy(DescriptiveStatistics source, DescriptiveStatistics 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/math/stat/descriptive/MultivariateSummaryStatistics.java b/src/main/java/org/apache/commons/math/stat/descriptive/MultivariateSummaryStatistics.java
new file mode 100644
index 0000000..8062f5b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/MultivariateSummaryStatistics.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.math.stat.descriptive;
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+import org.apache.commons.math.DimensionMismatchException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.linear.RealMatrix;
+import org.apache.commons.math.stat.descriptive.moment.GeometricMean;
+import org.apache.commons.math.stat.descriptive.moment.Mean;
+import org.apache.commons.math.stat.descriptive.moment.VectorialCovariance;
+import org.apache.commons.math.stat.descriptive.rank.Max;
+import org.apache.commons.math.stat.descriptive.rank.Min;
+import org.apache.commons.math.stat.descriptive.summary.Sum;
+import org.apache.commons.math.stat.descriptive.summary.SumOfLogs;
+import org.apache.commons.math.stat.descriptive.summary.SumOfSquares;
+import org.apache.commons.math.util.MathUtils;
+import org.apache.commons.math.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
+ * @version $Revision: 1042376 $ $Date: 2010-12-05 16:54:55 +0100 (dim. 05 déc. 2010) $
+ */
+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 MathUtils.equalsIncludingNaN(stat.getGeometricMean(), getGeometricMean()) &&
+               MathUtils.equalsIncludingNaN(stat.getMax(),           getMax())           &&
+               MathUtils.equalsIncludingNaN(stat.getMean(),          getMean())          &&
+               MathUtils.equalsIncludingNaN(stat.getMin(),           getMin())           &&
+               MathUtils.equalsIncludingNaN(stat.getN(),             getN())             &&
+               MathUtils.equalsIncludingNaN(stat.getSum(),           getSum())           &&
+               MathUtils.equalsIncludingNaN(stat.getSumSq(),         getSumSq())         &&
+               MathUtils.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 IllegalStateException if data has already been added
+     *  (i.e if n > 0)
+     */
+    private void setImpl(StorelessUnivariateStatistic[] newImpl,
+                         StorelessUnivariateStatistic[] oldImpl)
+       throws DimensionMismatchException, IllegalStateException {
+        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 IllegalStateException if data has already been added
+     *  (i.e if n > 0)
+     */
+    public void setSumImpl(StorelessUnivariateStatistic[] sumImpl)
+      throws 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 IllegalStateException if data has already been added
+     *  (i.e if n > 0)
+     */
+    public void setSumsqImpl(StorelessUnivariateStatistic[] sumsqImpl)
+      throws 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 IllegalStateException if data has already been added
+     *  (i.e if n > 0)
+     */
+    public void setMinImpl(StorelessUnivariateStatistic[] minImpl)
+      throws 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 IllegalStateException if data has already been added
+     *  (i.e if n > 0)
+     */
+    public void setMaxImpl(StorelessUnivariateStatistic[] maxImpl)
+      throws 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 IllegalStateException if data has already been added
+     *  (i.e if n > 0)
+     */
+    public void setSumLogImpl(StorelessUnivariateStatistic[] sumLogImpl)
+      throws 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 IllegalStateException if data has already been added
+     *  (i.e if n > 0)
+     */
+    public void setGeoMeanImpl(StorelessUnivariateStatistic[] geoMeanImpl)
+      throws 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 IllegalStateException if data has already been added
+     *  (i.e if n > 0)
+     */
+    public void setMeanImpl(StorelessUnivariateStatistic[] meanImpl)
+      throws DimensionMismatchException {
+        setImpl(meanImpl, this.meanImpl);
+    }
+
+    /**
+     * Throws IllegalStateException if n > 0.
+     */
+    private void checkEmpty() {
+        if (n > 0) {
+            throw MathRuntimeException.createIllegalStateException(
+                    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/math/stat/descriptive/StatisticalMultivariateSummary.java b/src/main/java/org/apache/commons/math/stat/descriptive/StatisticalMultivariateSummary.java
new file mode 100644
index 0000000..517788c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/StatisticalMultivariateSummary.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.math.stat.descriptive;
+
+import org.apache.commons.math.linear.RealMatrix;
+
+/**
+ *  Reporting interface for basic multivariate statistics.
+ *
+ * @since 1.2
+ * @version $Revision: 811786 $ $Date: 2009-09-06 11:36:08 +0200 (dim. 06 sept. 2009) $
+ */
+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/math/stat/descriptive/StatisticalSummary.java b/src/main/java/org/apache/commons/math/stat/descriptive/StatisticalSummary.java
new file mode 100644
index 0000000..5592053
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/StatisticalSummary.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.math.stat.descriptive;
+
+/**
+ *  Reporting interface for basic univariate statistics.
+ *
+  * @version $Revision: 811786 $ $Date: 2009-09-06 11:36:08 +0200 (dim. 06 sept. 2009) $
+ */
+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/math/stat/descriptive/StatisticalSummaryValues.java b/src/main/java/org/apache/commons/math/stat/descriptive/StatisticalSummaryValues.java
new file mode 100644
index 0000000..e72639a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/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.math.stat.descriptive;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.util.FastMath;
+import org.apache.commons.math.util.MathUtils;
+
+/**
+ *  Value object representing the results of a univariate statistical summary.
+ *
+ * @version $Revision: 1054186 $ $Date: 2011-01-01 03:28:46 +0100 (sam. 01 janv. 2011) $
+ */
+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 MathUtils.equalsIncludingNaN(stat.getMax(),      getMax())  &&
+               MathUtils.equalsIncludingNaN(stat.getMean(),     getMean()) &&
+               MathUtils.equalsIncludingNaN(stat.getMin(),      getMin())  &&
+               MathUtils.equalsIncludingNaN(stat.getN(),        getN())    &&
+               MathUtils.equalsIncludingNaN(stat.getSum(),      getSum())  &&
+               MathUtils.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() {
+        StringBuilder outBuffer = new StringBuilder();
+        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/math/stat/descriptive/StorelessUnivariateStatistic.java b/src/main/java/org/apache/commons/math/stat/descriptive/StorelessUnivariateStatistic.java
new file mode 100644
index 0000000..9b9fcb4
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/StorelessUnivariateStatistic.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.math.stat.descriptive;
+
+/**
+ * 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>
+ *
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+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 IllegalArgumentException if the array is null
+     */
+    void incrementAll(double[] values);
+
+    /**
+     * 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 IllegalArgumentException if the array is null or the index
+     */
+    void incrementAll(double[] values, int start, int length);
+
+    /**
+     * 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/math/stat/descriptive/SummaryStatistics.java b/src/main/java/org/apache/commons/math/stat/descriptive/SummaryStatistics.java
new file mode 100644
index 0000000..017a84d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/SummaryStatistics.java
@@ -0,0 +1,717 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.stat.descriptive;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.stat.descriptive.moment.GeometricMean;
+import org.apache.commons.math.stat.descriptive.moment.Mean;
+import org.apache.commons.math.stat.descriptive.moment.SecondMoment;
+import org.apache.commons.math.stat.descriptive.moment.Variance;
+import org.apache.commons.math.stat.descriptive.rank.Max;
+import org.apache.commons.math.stat.descriptive.rank.Min;
+import org.apache.commons.math.stat.descriptive.summary.Sum;
+import org.apache.commons.math.stat.descriptive.summary.SumOfLogs;
+import org.apache.commons.math.stat.descriptive.summary.SumOfSquares;
+import org.apache.commons.math.util.MathUtils;
+import org.apache.commons.math.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>
+ * @version $Revision: 1042376 $ $Date: 2010-12-05 16:54:55 +0100 (dim. 05 déc. 2010) $
+ */
+public class SummaryStatistics implements StatisticalSummary, Serializable {
+
+    /** Serialization UID */
+    private static final long serialVersionUID = -2021321786743555871L;
+
+    /** count of values that have been added */
+    protected long n = 0;
+
+    /** SecondMoment is used to compute the mean and variance */
+    protected SecondMoment secondMoment = new SecondMoment();
+
+    /** sum of values that have been added */
+    protected Sum sum = new Sum();
+
+    /** sum of the square of each value that has been added */
+    protected SumOfSquares sumsq = new SumOfSquares();
+
+    /** min of values that have been added */
+    protected Min min = new Min();
+
+    /** max of values that have been added */
+    protected Max max = new Max();
+
+    /** sumLog of values that have been added */
+    protected SumOfLogs sumLog = new SumOfLogs();
+
+    /** geoMean of values that have been added */
+    protected GeometricMean geoMean = new GeometricMean(sumLog);
+
+    /** mean of values that have been added */
+    protected Mean mean = new Mean();
+
+    /** variance of values that have been added */
+    protected Variance variance = new Variance();
+
+    /** 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
+     */
+    public SummaryStatistics(SummaryStatistics original) {
+        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 instanceof Mean)) {
+            meanImpl.increment(value);
+        }
+        if (!(varianceImpl instanceof Variance)) {
+            varianceImpl.increment(value);
+        }
+        if (!(geoMeanImpl instanceof GeometricMean)) {
+            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() {
+        if (mean == meanImpl) {
+            return new Mean(secondMoment).getResult();
+        } else {
+            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 variance of the values that have been added.
+     * <p>
+     * Double.NaN is returned if no values have been added.
+     * </p>
+     * @return the variance
+     */
+    public double getVariance() {
+        if (varianceImpl == variance) {
+            return new Variance(secondMoment).getResult();
+        } else {
+            return varianceImpl.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("mean: ").append(getMean()).append(endl);
+        outBuffer.append("geometric mean: ").append(getGeometricMean())
+            .append(endl);
+        outBuffer.append("variance: ").append(getVariance()).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 MathUtils.equalsIncludingNaN(stat.getGeometricMean(), getGeometricMean()) &&
+               MathUtils.equalsIncludingNaN(stat.getMax(),           getMax())           &&
+               MathUtils.equalsIncludingNaN(stat.getMean(),          getMean())          &&
+               MathUtils.equalsIncludingNaN(stat.getMin(),           getMin())           &&
+               MathUtils.equalsIncludingNaN(stat.getN(),             getN())             &&
+               MathUtils.equalsIncludingNaN(stat.getSum(),           getSum())           &&
+               MathUtils.equalsIncludingNaN(stat.getSumsq(),         getSumsq())         &&
+               MathUtils.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 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 IllegalStateException if data has already been added (i.e if n >
+     *         0)
+     * @since 1.2
+     */
+    public void setSumImpl(StorelessUnivariateStatistic sumImpl) {
+        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 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 IllegalStateException if data has already been added (i.e if n >
+     *         0)
+     * @since 1.2
+     */
+    public void setSumsqImpl(StorelessUnivariateStatistic sumsqImpl) {
+        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 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 IllegalStateException if data has already been added (i.e if n >
+     *         0)
+     * @since 1.2
+     */
+    public void setMinImpl(StorelessUnivariateStatistic minImpl) {
+        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 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 IllegalStateException if data has already been added (i.e if n >
+     *         0)
+     * @since 1.2
+     */
+    public void setMaxImpl(StorelessUnivariateStatistic maxImpl) {
+        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 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 IllegalStateException if data has already been added (i.e if n >
+     *         0)
+     * @since 1.2
+     */
+    public void setSumLogImpl(StorelessUnivariateStatistic sumLogImpl) {
+        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 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 IllegalStateException if data has already been added (i.e if n >
+     *         0)
+     * @since 1.2
+     */
+    public void setGeoMeanImpl(StorelessUnivariateStatistic geoMeanImpl) {
+        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 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 IllegalStateException if data has already been added (i.e if n >
+     *         0)
+     * @since 1.2
+     */
+    public void setMeanImpl(StorelessUnivariateStatistic meanImpl) {
+        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 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 varianceImpl the StorelessUnivariateStatistic instance to use for
+     *        computing the variance
+     * @throws IllegalStateException if data has already been added (i.e if n >
+     *         0)
+     * @since 1.2
+     */
+    public void setVarianceImpl(StorelessUnivariateStatistic varianceImpl) {
+        checkEmpty();
+        this.varianceImpl = varianceImpl;
+    }
+
+    /**
+     * Throws IllegalStateException if n > 0.
+     */
+    private void checkEmpty() {
+        if (n > 0) {
+            throw MathRuntimeException.createIllegalStateException(
+                    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();
+        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 NullPointerException if either source or dest is null
+     */
+    public static void copy(SummaryStatistics source, SummaryStatistics dest) {
+        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.sumLogImpl = source.sumLogImpl.copy();
+        dest.sumsqImpl = source.sumsqImpl.copy();
+        if (source.getGeoMeanImpl() instanceof GeometricMean) {
+            // Keep geoMeanImpl, sumLogImpl in synch
+            dest.geoMeanImpl = new GeometricMean((SumOfLogs) dest.sumLogImpl);
+        } else {
+            dest.geoMeanImpl = source.geoMeanImpl.copy();
+        }
+        SecondMoment.copy(source.secondMoment, dest.secondMoment);
+        dest.n = source.n;
+
+        // 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/math/stat/descriptive/SynchronizedDescriptiveStatistics.java b/src/main/java/org/apache/commons/math/stat/descriptive/SynchronizedDescriptiveStatistics.java
new file mode 100644
index 0000000..f1a932d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/SynchronizedDescriptiveStatistics.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.math.stat.descriptive;
+
+/**
+ * Implementation of
+ * {@link org.apache.commons.math.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
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+public class SynchronizedDescriptiveStatistics extends DescriptiveStatistics {
+
+    /** Serialization UID */
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Construct an instance with infinite window
+     */
+    public SynchronizedDescriptiveStatistics() {
+        this(INFINITE_WINDOW);
+    }
+
+    /**
+     * Construct an instance with finite window
+     * @param window the finite window size.
+     */
+    public SynchronizedDescriptiveStatistics(int window) {
+        super(window);
+    }
+
+    /**
+     * A copy constructor. Creates a deep-copy of the {@code original}.
+     *
+     * @param original the {@code SynchronizedDescriptiveStatistics} instance to copy
+     */
+    public SynchronizedDescriptiveStatistics(SynchronizedDescriptiveStatistics original) {
+        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[] getValues() {
+        return super.getValues();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized int getWindowSize() {
+        return super.getWindowSize();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void setWindowSize(int windowSize) {
+        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();
+        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 NullPointerException if either source or dest is null
+     */
+    public static void copy(SynchronizedDescriptiveStatistics source,
+            SynchronizedDescriptiveStatistics dest) {
+        synchronized (source) {
+            synchronized (dest) {
+                DescriptiveStatistics.copy(source, dest);
+            }
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/stat/descriptive/SynchronizedMultivariateSummaryStatistics.java b/src/main/java/org/apache/commons/math/stat/descriptive/SynchronizedMultivariateSummaryStatistics.java
new file mode 100644
index 0000000..190e092
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/SynchronizedMultivariateSummaryStatistics.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.math.stat.descriptive;
+
+import org.apache.commons.math.DimensionMismatchException;
+import org.apache.commons.math.linear.RealMatrix;
+
+/**
+ * Implementation of
+ * {@link org.apache.commons.math.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
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+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 {
+        super.setSumImpl(sumImpl);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized StorelessUnivariateStatistic[] getSumsqImpl() {
+        return super.getSumsqImpl();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void setSumsqImpl(StorelessUnivariateStatistic[] sumsqImpl)
+      throws DimensionMismatchException {
+        super.setSumsqImpl(sumsqImpl);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized StorelessUnivariateStatistic[] getMinImpl() {
+        return super.getMinImpl();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void setMinImpl(StorelessUnivariateStatistic[] minImpl)
+      throws DimensionMismatchException {
+        super.setMinImpl(minImpl);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized StorelessUnivariateStatistic[] getMaxImpl() {
+        return super.getMaxImpl();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void setMaxImpl(StorelessUnivariateStatistic[] maxImpl)
+      throws DimensionMismatchException {
+        super.setMaxImpl(maxImpl);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized StorelessUnivariateStatistic[] getSumLogImpl() {
+        return super.getSumLogImpl();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void setSumLogImpl(StorelessUnivariateStatistic[] sumLogImpl)
+      throws DimensionMismatchException {
+        super.setSumLogImpl(sumLogImpl);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized StorelessUnivariateStatistic[] getGeoMeanImpl() {
+        return super.getGeoMeanImpl();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void setGeoMeanImpl(StorelessUnivariateStatistic[] geoMeanImpl)
+      throws DimensionMismatchException {
+        super.setGeoMeanImpl(geoMeanImpl);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized StorelessUnivariateStatistic[] getMeanImpl() {
+        return super.getMeanImpl();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void setMeanImpl(StorelessUnivariateStatistic[] meanImpl)
+      throws DimensionMismatchException {
+        super.setMeanImpl(meanImpl);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/stat/descriptive/SynchronizedSummaryStatistics.java b/src/main/java/org/apache/commons/math/stat/descriptive/SynchronizedSummaryStatistics.java
new file mode 100644
index 0000000..07bfbf2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/SynchronizedSummaryStatistics.java
@@ -0,0 +1,333 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.stat.descriptive;
+
+/**
+ * Implementation of
+ * {@link org.apache.commons.math.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
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+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
+     */
+    public SynchronizedSummaryStatistics(SynchronizedSummaryStatistics original) {
+        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 getVariance() {
+        return super.getVariance();
+    }
+
+    /**
+     * {@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) {
+        super.setSumImpl(sumImpl);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized StorelessUnivariateStatistic getSumsqImpl() {
+        return super.getSumsqImpl();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void setSumsqImpl(StorelessUnivariateStatistic sumsqImpl) {
+        super.setSumsqImpl(sumsqImpl);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized StorelessUnivariateStatistic getMinImpl() {
+        return super.getMinImpl();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void setMinImpl(StorelessUnivariateStatistic minImpl) {
+        super.setMinImpl(minImpl);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized StorelessUnivariateStatistic getMaxImpl() {
+        return super.getMaxImpl();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void setMaxImpl(StorelessUnivariateStatistic maxImpl) {
+        super.setMaxImpl(maxImpl);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized StorelessUnivariateStatistic getSumLogImpl() {
+        return super.getSumLogImpl();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void setSumLogImpl(StorelessUnivariateStatistic sumLogImpl) {
+        super.setSumLogImpl(sumLogImpl);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized StorelessUnivariateStatistic getGeoMeanImpl() {
+        return super.getGeoMeanImpl();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void setGeoMeanImpl(StorelessUnivariateStatistic geoMeanImpl) {
+        super.setGeoMeanImpl(geoMeanImpl);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized StorelessUnivariateStatistic getMeanImpl() {
+        return super.getMeanImpl();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void setMeanImpl(StorelessUnivariateStatistic meanImpl) {
+        super.setMeanImpl(meanImpl);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized StorelessUnivariateStatistic getVarianceImpl() {
+        return super.getVarianceImpl();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public synchronized void setVarianceImpl(StorelessUnivariateStatistic varianceImpl) {
+        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();
+        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 NullPointerException if either source or dest is null
+     */
+    public static void copy(SynchronizedSummaryStatistics source,
+            SynchronizedSummaryStatistics dest) {
+        synchronized (source) {
+            synchronized (dest) {
+                SummaryStatistics.copy(source, dest);
+            }
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/stat/descriptive/UnivariateStatistic.java b/src/main/java/org/apache/commons/math/stat/descriptive/UnivariateStatistic.java
new file mode 100644
index 0000000..92c9ee2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/UnivariateStatistic.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.math.stat.descriptive;
+
+
+/**
+ * Base interface implemented by all statistics.
+ *
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+public interface UnivariateStatistic {
+
+    /**
+     * 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
+     */
+    double evaluate(double[] values);
+
+    /**
+     * 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
+     */
+    double evaluate(double[] values, int begin, int length);
+
+    /**
+     * 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/math/stat/descriptive/WeightedEvaluation.java b/src/main/java/org/apache/commons/math/stat/descriptive/WeightedEvaluation.java
new file mode 100644
index 0000000..54a0216
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/WeightedEvaluation.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.math.stat.descriptive;
+
+/**
+ * Weighted evaluation for statistics.
+ *
+ * @since 2.1
+ * @version $Revision: 894474 $ $Date: 2009-12-29 21:02:37 +0100 (mar. 29 déc. 2009) $
+ */
+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
+     */
+    double evaluate(double[] values, double[] weights);
+
+    /**
+     * 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
+     */
+    double evaluate(double[] values, double[] weights, int begin, int length);
+
+}
diff --git a/src/main/java/org/apache/commons/math/stat/descriptive/moment/FirstMoment.java b/src/main/java/org/apache/commons/math/stat/descriptive/moment/FirstMoment.java
new file mode 100644
index 0000000..4103e50
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/moment/FirstMoment.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.math.stat.descriptive.moment;
+
+import java.io.Serializable;
+import org.apache.commons.math.stat.descriptive.AbstractStorelessUnivariateStatistic;
+
+/**
+ * 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.</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>
+ *
+ * @version $Revision: 1006299 $ $Date: 2010-10-10 16:47:17 +0200 (dim. 10 oct. 2010) $
+ */
+public 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
+     */
+    public 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
+     */
+     public FirstMoment(FirstMoment original) {
+         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();
+        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 NullPointerException if either source or dest is null
+     */
+    public static void copy(FirstMoment source, FirstMoment 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/math/stat/descriptive/moment/FourthMoment.java b/src/main/java/org/apache/commons/math/stat/descriptive/moment/FourthMoment.java
new file mode 100644
index 0000000..6e7d8d2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/moment/FourthMoment.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.math.stat.descriptive.moment;
+
+import java.io.Serializable;
+
+/**
+ * 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. </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>
+ *
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+public class FourthMoment extends ThirdMoment implements Serializable{
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = 4763990447117157611L;
+
+    /** fourth moment of values that have been added */
+    protected double m4;
+
+    /**
+     * Create a FourthMoment instance
+     */
+    public 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
+     */
+     public FourthMoment(FourthMoment original) {
+         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();
+        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 NullPointerException if either source or dest is null
+     */
+    public static void copy(FourthMoment source, FourthMoment dest) {
+        ThirdMoment.copy(source, dest);
+        dest.m4 = source.m4;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/stat/descriptive/moment/GeometricMean.java b/src/main/java/org/apache/commons/math/stat/descriptive/moment/GeometricMean.java
new file mode 100644
index 0000000..a24a3c8
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/moment/GeometricMean.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.math.stat.descriptive.moment;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.stat.descriptive.AbstractStorelessUnivariateStatistic;
+import org.apache.commons.math.stat.descriptive.StorelessUnivariateStatistic;
+import org.apache.commons.math.stat.descriptive.summary.SumOfLogs;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * 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>
+ *
+ *
+ * @version $Revision: 1006299 $ $Date: 2010-10-10 16:47:17 +0200 (dim. 10 oct. 2010) $
+ */
+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
+     */
+    public GeometricMean(GeometricMean original) {
+        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();
+        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 IllegalArgumentException 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) {
+        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 IllegalStateException if data has already been added
+     *  (i.e if n > 0)
+     */
+    public void setSumLogImpl(
+            StorelessUnivariateStatistic sumLogImpl) {
+        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 NullPointerException if either source or dest is null
+     */
+    public static void copy(GeometricMean source, GeometricMean dest) {
+        dest.setData(source.getDataRef());
+        dest.sumOfLogs = source.sumOfLogs.copy();
+    }
+
+
+    /**
+     * Throws IllegalStateException if n > 0.
+     */
+    private void checkEmpty() {
+        if (getN() > 0) {
+            throw MathRuntimeException.createIllegalStateException(
+                    LocalizedFormats.VALUES_ADDED_BEFORE_CONFIGURING_STATISTIC,
+                    getN());
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/stat/descriptive/moment/Kurtosis.java b/src/main/java/org/apache/commons/math/stat/descriptive/moment/Kurtosis.java
new file mode 100644
index 0000000..f648051
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/moment/Kurtosis.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.math.stat.descriptive.moment;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.stat.descriptive.AbstractStorelessUnivariateStatistic;
+import org.apache.commons.math.util.FastMath;
+
+
+/**
+ * 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.</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>
+ *
+ * @version $Revision: 1006299 $ $Date: 2010-10-10 16:47:17 +0200 (dim. 10 oct. 2010) $
+ */
+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
+     */
+    public Kurtosis(Kurtosis original) {
+        copy(original, this);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void increment(final double d) {
+        if (incMoment) {
+            moment.increment(d);
+        }  else  {
+            throw MathRuntimeException.createIllegalStateException(
+                    LocalizedFormats.CANNOT_INCREMENT_STATISTIC_CONSTRUCTED_FROM_EXTERNAL_MOMENTS);
+        }
+    }
+
+    /**
+     * {@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.m4 -
+                                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();
+        } else  {
+            throw MathRuntimeException.createIllegalStateException(
+                    LocalizedFormats.CANNOT_CLEAR_STATISTIC_CONSTRUCTED_FROM_EXTERNAL_MOMENTS);
+        }
+    }
+
+    /**
+     * {@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 IllegalArgumentException 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) {
+        // 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();
+        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 NullPointerException if either source or dest is null
+     */
+    public static void copy(Kurtosis source, Kurtosis dest) {
+        dest.setData(source.getDataRef());
+        dest.moment = source.moment.copy();
+        dest.incMoment = source.incMoment;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/stat/descriptive/moment/Mean.java b/src/main/java/org/apache/commons/math/stat/descriptive/moment/Mean.java
new file mode 100644
index 0000000..c5aa9da
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/moment/Mean.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.math.stat.descriptive.moment;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.stat.descriptive.AbstractStorelessUnivariateStatistic;
+import org.apache.commons.math.stat.descriptive.WeightedEvaluation;
+import org.apache.commons.math.stat.descriptive.summary.Sum;
+
+/**
+ * <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.
+ * </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.
+ *
+ * @version $Revision: 1006299 $ $Date: 2010-10-10 16:47:17 +0200 (dim. 10 oct. 2010) $
+ */
+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
+     */
+    public Mean(Mean original) {
+        copy(original, this);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @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 IllegalArgumentException 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) {
+        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 IllegalArgumentException if the parameters are not valid
+     * @since 2.1
+     */
+    public double evaluate(final double[] values, final double[] weights,
+                           final int begin, final int length) {
+        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>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>
+     * </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 IllegalArgumentException if the parameters are not valid
+     * @since 2.1
+     */
+    public double evaluate(final double[] values, final double[] weights) {
+        return evaluate(values, weights, 0, values.length);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Mean copy() {
+        Mean result = new Mean();
+        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 NullPointerException if either source or dest is null
+     */
+    public static void copy(Mean source, Mean dest) {
+        dest.setData(source.getDataRef());
+        dest.incMoment = source.incMoment;
+        dest.moment = source.moment.copy();
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/stat/descriptive/moment/SecondMoment.java b/src/main/java/org/apache/commons/math/stat/descriptive/moment/SecondMoment.java
new file mode 100644
index 0000000..ae8ef8e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/moment/SecondMoment.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.math.stat.descriptive.moment;
+
+import java.io.Serializable;
+
+/**
+ * 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.</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>
+ *
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+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
+     */
+    public SecondMoment(SecondMoment original) {
+        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();
+        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 NullPointerException if either source or dest is null
+     */
+    public static void copy(SecondMoment source, SecondMoment dest) {
+        FirstMoment.copy(source, dest);
+        dest.m2 = source.m2;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/stat/descriptive/moment/SemiVariance.java b/src/main/java/org/apache/commons/math/stat/descriptive/moment/SemiVariance.java
new file mode 100644
index 0000000..04aa456
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/moment/SemiVariance.java
@@ -0,0 +1,379 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.stat.descriptive.moment;
+
+import java.io.Serializable;
+import org.apache.commons.math.exception.NullArgumentException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.stat.descriptive.AbstractUnivariateStatistic;
+
+/**
+ * <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>
+ *
+ * @version $Revision: 1006299 $ $Date: 2010-10-10 16:47:17 +0200 (dim. 10 oct. 2010) $
+ * @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
+     */
+    public SemiVariance(final SemiVariance original) {
+        copy(original, this);
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public SemiVariance copy() {
+        SemiVariance result = new SemiVariance();
+        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 NullPointerException if either source or dest is null
+     */
+    public static void copy(final SemiVariance source, SemiVariance dest) {
+        dest.setData(source.getDataRef());
+        dest.biasCorrected = source.biasCorrected;
+        dest.varianceDirection = source.varianceDirection;
+    }
+
+
+    /**
+     * This method calculates {@link SemiVariance} for the entire array against the mean, using
+     * instance properties varianceDirection and biasCorrection.
+     *
+     * @param values the input array
+     * @return the SemiVariance
+     * @throws IllegalArgumentException if values is null
+     *
+     */
+    @Override
+    public double evaluate(final double[] values) {
+        if (values == null) {
+            throw new NullArgumentException(LocalizedFormats.INPUT_ARRAY);
+         }
+        return evaluate(values, 0, values.length);
+    }
+
+
+    /**
+      * <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 IllegalArgumentException if the parameters are not valid
+      *
+      */
+      @Override
+      public double evaluate(final double[] values, final int start, final int length) {
+        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 IllegalArgumentException if values is null
+       *
+       */
+      public double evaluate(final double[] values, Direction direction) {
+          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>IllegalArgumentException</code> if the array is null.</p>
+       *
+       * @param values the input array
+       * @param cutoff the reference point
+       * @return the SemiVariance
+       * @throws IllegalArgumentException if values is null
+       */
+      public double evaluate(final double[] values, final double cutoff) {
+          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>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
+       * @return the SemiVariance
+       * @throws IllegalArgumentException if values is null
+       */
+      public double evaluate(final double[] values, final double cutoff, final Direction direction) {
+          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 IllegalArgumentException 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) {
+
+        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/math/stat/descriptive/moment/Skewness.java b/src/main/java/org/apache/commons/math/stat/descriptive/moment/Skewness.java
new file mode 100644
index 0000000..d16f956
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/moment/Skewness.java
@@ -0,0 +1,213 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.stat.descriptive.moment;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.stat.descriptive.AbstractStorelessUnivariateStatistic;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * 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>
+ * <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>
+ *
+ * @version $Revision: 1006299 $ $Date: 2010-10-10 16:47:17 +0200 (dim. 10 oct. 2010) $
+ */
+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
+     */
+    public Skewness(Skewness original) {
+        copy(original, this);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @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 IllegalArgumentException 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) {
+
+        // 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();
+        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 NullPointerException if either source or dest is null
+     */
+    public static void copy(Skewness source, Skewness 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/math/stat/descriptive/moment/StandardDeviation.java b/src/main/java/org/apache/commons/math/stat/descriptive/moment/StandardDeviation.java
new file mode 100644
index 0000000..837ae3b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/moment/StandardDeviation.java
@@ -0,0 +1,271 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.stat.descriptive.moment;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.stat.descriptive.AbstractStorelessUnivariateStatistic;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * 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>
+ *
+ * @version $Revision: 1006299 $ $Date: 2010-10-10 16:47:17 +0200 (dim. 10 oct. 2010) $
+ */
+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
+     */
+    public StandardDeviation(StandardDeviation original) {
+        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>IllegalArgumentException</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 IllegalArgumentException if the array is null
+     */
+    @Override
+    public double evaluate(final double[] values)  {
+        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>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 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 IllegalArgumentException 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)  {
+       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 IllegalArgumentException 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)  {
+        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>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
+     * @return the standard deviation of the values or Double.NaN if length = 0
+     * @throws IllegalArgumentException if the array is null
+     */
+    public double evaluate(final double[] values, final double mean)  {
+        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();
+        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 NullPointerException if either source or dest is null
+     */
+    public static void copy(StandardDeviation source, StandardDeviation dest) {
+        dest.setData(source.getDataRef());
+        dest.variance = source.variance.copy();
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/stat/descriptive/moment/ThirdMoment.java b/src/main/java/org/apache/commons/math/stat/descriptive/moment/ThirdMoment.java
new file mode 100644
index 0000000..5c50989
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/moment/ThirdMoment.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.math.stat.descriptive.moment;
+
+import java.io.Serializable;
+
+
+/**
+ * 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.</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>
+ *
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+public 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
+     */
+    public 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
+     */
+    public ThirdMoment(ThirdMoment original) {
+        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();
+        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 NullPointerException if either source or dest is null
+     */
+    public static void copy(ThirdMoment source, ThirdMoment dest) {
+        SecondMoment.copy(source, dest);
+        dest.m3 = source.m3;
+        dest.nDevSq = source.nDevSq;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/stat/descriptive/moment/Variance.java b/src/main/java/org/apache/commons/math/stat/descriptive/moment/Variance.java
new file mode 100644
index 0000000..6ce6835
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/moment/Variance.java
@@ -0,0 +1,610 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.stat.descriptive.moment;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.exception.NullArgumentException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.stat.descriptive.WeightedEvaluation;
+import org.apache.commons.math.stat.descriptive.AbstractStorelessUnivariateStatistic;
+
+/**
+ * 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>
+ *
+ * @version $Revision: 1006299 $ $Date: 2010-10-10 16:47:17 +0200 (dim. 10 oct. 2010) $
+ */
+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;
+
+    /**
+     * Boolean test to determine if this Variance should also increment
+     * the second moment, this evaluates to false when this Variance is
+     * constructed with an external SecondMoment as a parameter.
+     */
+    protected boolean incMoment = true;
+
+    /**
+     * Determines whether or not bias correction is applied when computing the
+     * value of the statisic.  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.
+     *
+     * @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
+     */
+    public Variance(Variance original) {
+        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>
+     */
+    @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>IllegalArgumentException</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 IllegalArgumentException if the array is null
+     */
+    @Override
+    public double evaluate(final double[] values) {
+        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.
+     * <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>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 variance of the values or Double.NaN if length = 0
+     * @throws IllegalArgumentException 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) {
+
+        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, MathUtils.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>IllegalArgumentException</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 IllegalArgumentException if the parameters are not valid
+     * @since 2.1
+     */
+    public double evaluate(final double[] values, final double[] weights,
+                           final int begin, final int length) {
+
+        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, MathUtils.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>
+     * </ul></p>
+     * <p>
+     * Does not change the internal state of the statistic.</p>
+     * <p>
+     * Throws <code>IllegalArgumentException</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 IllegalArgumentException if the parameters are not valid
+     * @since 2.1
+     */
+    public double evaluate(final double[] values, final double[] weights) {
+        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>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 variance of the values or Double.NaN if length = 0
+     * @throws IllegalArgumentException 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) {
+
+        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>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
+     * @return the variance of the values or Double.NaN if the array is empty
+     * @throws IllegalArgumentException if the array is null
+     */
+    public double evaluate(final double[] values, final double mean) {
+        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, MathUtils.normalizeArray(weights, values.length), mean); </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>
+     *
+     * @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 IllegalArgumentException 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) {
+
+        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 = 0; i < weights.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, MathUtils.normalizeArray(weights, values.length), mean); </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>
+     * </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 IllegalArgumentException if the parameters are not valid
+     * @since 2.1
+     */
+    public double evaluate(final double[] values, final double[] weights, final double mean) {
+        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();
+        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 NullPointerException if either source or dest is null
+     */
+    public static void copy(Variance source, Variance dest) {
+        if (source == null ||
+            dest == null) {
+            throw new NullArgumentException();
+        }
+        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/math/stat/descriptive/moment/VectorialCovariance.java b/src/main/java/org/apache/commons/math/stat/descriptive/moment/VectorialCovariance.java
new file mode 100644
index 0000000..71afc68
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/moment/VectorialCovariance.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.math.stat.descriptive.moment;
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+import org.apache.commons.math.DimensionMismatchException;
+import org.apache.commons.math.linear.MatrixUtils;
+import org.apache.commons.math.linear.RealMatrix;
+
+/**
+ * Returns the covariance matrix of the available vectors.
+ * @since 1.2
+ * @version $Revision: 922714 $ $Date: 2010-03-14 02:35:14 +0100 (dim. 14 mars 2010) $
+ */
+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
+     * @exception 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/math/stat/descriptive/moment/VectorialMean.java b/src/main/java/org/apache/commons/math/stat/descriptive/moment/VectorialMean.java
new file mode 100644
index 0000000..ef57657
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/moment/VectorialMean.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.math.stat.descriptive.moment;
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+import org.apache.commons.math.DimensionMismatchException;
+
+/**
+ * Returns the arithmetic mean of the available vectors.
+ * @since 1.2
+ * @version $Revision: 922714 $ $Date: 2010-03-14 02:35:14 +0100 (dim. 14 mars 2010) $
+ */
+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
+     * @exception 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/math/stat/descriptive/moment/package.html b/src/main/java/org/apache/commons/math/stat/descriptive/moment/package.html
new file mode 100644
index 0000000..e024095
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/moment/package.html
@@ -0,0 +1,20 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 480440 $ $Date: 2006-11-29 08:14:12 +0100 (mer. 29 nov. 2006) $ -->
+    <body>Summary statistics based on moments.</body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/stat/descriptive/package.html b/src/main/java/org/apache/commons/math/stat/descriptive/package.html
new file mode 100644
index 0000000..981fda4
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/package.html
@@ -0,0 +1,41 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $ -->
+    <body>
+        Generic univariate summary statistic objects.
+
+        <h3>UnivariateStatistic API Usage Examples:</h3>
+        <h4>UnivariateStatistic:</h4>
+        <code>/* evaluation approach */<br/> double[] values = new double[] { 1, 2,
+            3, 4, 5 };<br/> <span style="font-weight: bold;">UnivariateStatistic stat
+            = new Mean();</span><br/> System.out.println("mean = " + <span
+            style="font-weight: bold;">stat.evaluate(values)</span>);<br/> </code>
+        <h4>StorelessUnivariateStatistic:</h4>
+        <code>/* incremental approach */<br/> double[] values = new double[] { 1, 2,
+            3, 4, 5 };<br/> <span style="font-weight: bold;">
+            StorelessUnivariateStatistic stat = new Mean();</span><br/>
+            System.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;
+            System.out.println("current mean = " + <span style="font-weight: bold;">
+            stat2.getResult()</span>);<br/> }<br/> <span style="font-weight: bold;">
+            stat.clear();</span><br/> System.out.println("mean after clear is NaN = "
+            + <span style="font-weight: bold;">stat.getResult()</span>);</code>
+    </body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/stat/descriptive/rank/Max.java b/src/main/java/org/apache/commons/math/stat/descriptive/rank/Max.java
new file mode 100644
index 0000000..1b15750
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/rank/Max.java
@@ -0,0 +1,163 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.stat.descriptive.rank;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.stat.descriptive.AbstractStorelessUnivariateStatistic;
+
+/**
+ * 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>
+ *
+ * @version $Revision: 1006299 $ $Date: 2010-10-10 16:47:17 +0200 (dim. 10 oct. 2010) $
+ */
+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
+     */
+    public Max(Max original) {
+        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>IllegalArgumentException</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 IllegalArgumentException 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) {
+        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();
+        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 NullPointerException if either source or dest is null
+     */
+    public static void copy(Max source, Max dest) {
+        dest.setData(source.getDataRef());
+        dest.n = source.n;
+        dest.value = source.value;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/stat/descriptive/rank/Median.java b/src/main/java/org/apache/commons/math/stat/descriptive/rank/Median.java
new file mode 100644
index 0000000..6e13b13
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/rank/Median.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.math.stat.descriptive.rank;
+
+import java.io.Serializable;
+
+
+/**
+ * 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>
+ *
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+public class Median extends Percentile implements Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -3961477041290915687L;
+
+    /**
+     * Default constructor.
+     */
+    public Median() {
+        super(50.0);
+    }
+
+    /**
+     * Copy constructor, creates a new {@code Median} identical
+     * to the {@code original}
+     *
+     * @param original the {@code Median} instance to copy
+     */
+    public Median(Median original) {
+        super(original);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/stat/descriptive/rank/Min.java b/src/main/java/org/apache/commons/math/stat/descriptive/rank/Min.java
new file mode 100644
index 0000000..1c264c6
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/rank/Min.java
@@ -0,0 +1,163 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.stat.descriptive.rank;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.stat.descriptive.AbstractStorelessUnivariateStatistic;
+
+/**
+ * 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>
+ *
+ * @version $Revision: 1006299 $ $Date: 2010-10-10 16:47:17 +0200 (dim. 10 oct. 2010) $
+ */
+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
+     */
+    public Min(Min original) {
+        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>IllegalArgumentException</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 IllegalArgumentException 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) {
+        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();
+        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 NullPointerException if either source or dest is null
+     */
+    public static void copy(Min source, Min dest) {
+        dest.setData(source.getDataRef());
+        dest.n = source.n;
+        dest.value = source.value;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/stat/descriptive/rank/Percentile.java b/src/main/java/org/apache/commons/math/stat/descriptive/rank/Percentile.java
new file mode 100644
index 0000000..0c8a90f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/rank/Percentile.java
@@ -0,0 +1,497 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.stat.descriptive.rank;
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.stat.descriptive.AbstractUnivariateStatistic;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * 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>).  If <code>pos >= n</code> return the largest
+ * element in the array; otherwise</li>
+ * <li>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>
+ * Since 2.2, Percentile implementation uses only selection instead of complete
+ * sorting and caches selection algorithm state between calls to the various
+ * {@code evaluate} methods when several percentiles are to be computed on the same data.
+ * This greatly improves efficiency, both for single percentile and multiple
+ * percentiles computations. However, it also induces a need to be sure the data
+ * at one call to {@code evaluate} is the same as the data with the cached algorithm
+ * state from the previous calls. Percentile does this by checking the array reference
+ * itself and a checksum of its content by default. If the user already knows he calls
+ * {@code evaluate} on an immutable array, he can save the checking time by calling the
+ * {@code evaluate} methods that do <em>not</em>
+ * </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>
+ *
+ * @version $Revision: 1006299 $ $Date: 2010-10-10 16:47:17 +0200 (dim. 10 oct. 2010) $
+ */
+public class Percentile extends AbstractUnivariateStatistic implements Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -8091216485095130416L;
+
+    /** Minimum size under which we use a simple insertion sort rather than Hoare's select. */
+    private static final int MIN_SELECT_SIZE = 15;
+
+    /** Maximum number of partitioning pivots cached (each level double the number of pivots). */
+    private static final int MAX_CACHED_LEVELS = 10;
+
+    /** Determines what percentile is computed when evaluate() is activated
+     * with no quantile argument */
+    private double quantile = 0.0;
+
+    /** Cached pivots. */
+    private int[] cachedPivots;
+
+    /**
+     * Constructs a Percentile with a default quantile
+     * value of 50.0.
+     */
+    public Percentile() {
+        this(50.0);
+    }
+
+    /**
+     * Constructs a Percentile with the specific quantile value.
+     * @param p the quantile
+     * @throws IllegalArgumentException  if p is not greater than 0 and less
+     * than or equal to 100
+     */
+    public Percentile(final double p) {
+        setQuantile(p);
+        cachedPivots = null;
+    }
+
+    /**
+     * Copy constructor, creates a new {@code Percentile} identical
+     * to the {@code original}
+     *
+     * @param original the {@code Percentile} instance to copy
+     */
+    public Percentile(Percentile original) {
+        copy(original, this);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setData(final double[] values) {
+        if (values == null) {
+            cachedPivots = null;
+        } else {
+            cachedPivots = new int[(0x1 << MAX_CACHED_LEVELS) - 1];
+            Arrays.fill(cachedPivots, -1);
+        }
+        super.setData(values);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void setData(final double[] values, final int begin, final int length) {
+        if (values == null) {
+            cachedPivots = null;
+        } else {
+            cachedPivots = new int[(0x1 << MAX_CACHED_LEVELS) - 1];
+            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
+     * </p>
+     * @param p the percentile value to compute
+     * @return the value of the statistic applied to the stored data
+     */
+    public double evaluate(final double p) {
+        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>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) </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 IllegalArgumentException if <code>values</code> is null
+     *     or p is invalid
+     */
+    public double evaluate(final double[] values, final double p) {
+        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>IllegalArgumentException</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 IllegalArgumentException if the parameters are not valid
+     *
+     */
+    @Override
+    public double evaluate( final double[] values, final int start, final int length) {
+        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>IllegalArgumentException</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 IllegalArgumentException 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) {
+
+        test(values, begin, length);
+
+        if ((p > 100) || (p <= 0)) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.OUT_OF_BOUNDS_QUANTILE_VALUE, p);
+        }
+        if (length == 0) {
+            return Double.NaN;
+        }
+        if (length == 1) {
+            return values[begin]; // always return single value for n = 1
+        }
+        double n = length;
+        double pos = p * (n + 1) / 100;
+        double fpos = FastMath.floor(pos);
+        int intPos = (int) fpos;
+        double dif = pos - fpos;
+        double[] work;
+        int[] pivotsHeap;
+        if (values == getDataRef()) {
+            work = getDataRef();
+            pivotsHeap = cachedPivots;
+        } else {
+            work = new double[length];
+            System.arraycopy(values, begin, work, 0, length);
+            pivotsHeap = new int[(0x1 << MAX_CACHED_LEVELS) - 1];
+            Arrays.fill(pivotsHeap, -1);
+        }
+
+        if (pos < 1) {
+            return select(work, pivotsHeap, 0);
+        }
+        if (pos >= n) {
+            return select(work, pivotsHeap, length - 1);
+        }
+        double lower = select(work, pivotsHeap, intPos - 1);
+        double upper = select(work, pivotsHeap, intPos);
+        return lower + dif * (upper - lower);
+    }
+
+    /**
+     * Select the k<sup>th</sup> smallest element from work array
+     * @param work work array (will be reorganized during the call)
+     * @param pivotsHeap set of pivot index corresponding to elements that
+     * are already at their sorted location, stored as an implicit heap
+     * (i.e. a sorted binary tree stored in a flat array, where the
+     * children of a node at index n are at indices 2n+1 for the left
+     * child and 2n+2 for the right child, with 0-based indices)
+     * @param k index of the desired element
+     * @return k<sup>th</sup> smallest element
+     */
+    private double select(final double[] work, final int[] pivotsHeap, final int k) {
+
+        int begin = 0;
+        int end   = work.length;
+        int node  = 0;
+
+        while (end - begin > MIN_SELECT_SIZE) {
+
+            final int pivot;
+            if ((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, medianOf3(work, begin, end));
+                if (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 = Math.min(2 * node + 1, pivotsHeap.length); // the min is here to avoid integer overflow
+            } else {
+                // the element is in the right partition
+                begin = pivot + 1;
+                node  = Math.min(2 * node + 2, pivotsHeap.length); // the min is here to avoid integer overflow
+            }
+
+        }
+
+        // the element is somewhere in the small sub-array
+        // sort the sub-array using insertion sort
+        insertionSort(work, begin, end);
+        return work[k];
+
+    }
+
+    /** Select a pivot index as the median of three
+     * @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
+     */
+    int medianOf3(final double[] work, final int begin, final int end) {
+
+        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;
+            }
+        }
+
+    }
+
+    /**
+     * Partition an array slice around a pivot
+     * <p>
+     * Partitioning exchanges array elements such that all elements
+     * smaller than pivot are before it and all elements larger than
+     * pivot are after it
+     * </p>
+     * @param work data array
+     * @param begin index of the first element of the slice
+     * @param end index after the last element of the slice
+     * @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;
+
+    }
+
+    /**
+     * Sort in place a (small) array slice using insertion sort
+     * @param work array to sort
+     * @param begin index of the first element of the slice to sort
+     * @param end index after the last element of the slice to sort
+     */
+    private void insertionSort(final double[] work, final int begin, final int end) {
+        for (int j = begin + 1; j < end; j++) {
+            final double saved = work[j];
+            int i = j - 1;
+            while ((i >= begin) && (saved < work[i])) {
+                work[i + 1] = work[i];
+                i--;
+            }
+            work[i + 1] = saved;
+        }
+    }
+
+    /**
+     * Returns the value of the quantile field (determines what percentile is
+     * computed when evaluate() is called with no quantile argument).
+     *
+     * @return quantile
+     */
+    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 IllegalArgumentException  if p is not greater than 0 and less
+     * than or equal to 100
+     */
+    public void setQuantile(final double p) {
+        if (p <= 0 || p > 100) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.OUT_OF_BOUNDS_QUANTILE_VALUE, p);
+        }
+        quantile = p;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Percentile copy() {
+        Percentile result = new Percentile();
+        copy(this, result);
+        return result;
+    }
+
+    /**
+     * Copies source to dest.
+     * <p>Neither source nor dest can be null.</p>
+     *
+     * @param source Percentile to copy
+     * @param dest Percentile to copy to
+     * @throws NullPointerException if either source or dest is null
+     */
+    public static void copy(Percentile source, Percentile dest) {
+        dest.setData(source.getDataRef());
+        if (source.cachedPivots != null) {
+            System.arraycopy(source.cachedPivots, 0, dest.cachedPivots, 0, source.cachedPivots.length);
+        }
+        dest.quantile = source.quantile;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/stat/descriptive/rank/package.html b/src/main/java/org/apache/commons/math/stat/descriptive/rank/package.html
new file mode 100644
index 0000000..c69107b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/rank/package.html
@@ -0,0 +1,20 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 480440 $ $Date: 2006-11-29 08:14:12 +0100 (mer. 29 nov. 2006) $ -->
+    <body>Summary statistics based on ranks.</body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/stat/descriptive/summary/Product.java b/src/main/java/org/apache/commons/math/stat/descriptive/summary/Product.java
new file mode 100644
index 0000000..c7d1d76
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/summary/Product.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.math.stat.descriptive.summary;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.stat.descriptive.AbstractStorelessUnivariateStatistic;
+import org.apache.commons.math.stat.descriptive.WeightedEvaluation;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Returns the product of the available values.
+ * <p>
+ * If there are no values in the dataset, or 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>
+ *
+ * @version $Revision: 1006299 $ $Date: 2010-10-10 16:47:17 +0200 (dim. 10 oct. 2010) $
+ */
+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 = Double.NaN;
+    }
+
+    /**
+     * Copy constructor, creates a new {@code Product} identical
+     * to the {@code original}
+     *
+     * @param original the {@code Product} instance to copy
+     */
+    public Product(Product original) {
+        copy(original, this);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void increment(final double d) {
+        if (n == 0) {
+            value = d;
+        } else {
+            value *= d;
+        }
+        n++;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public double getResult() {
+        return value;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long getN() {
+        return n;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void clear() {
+        value = Double.NaN;
+        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>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 product of the values or Double.NaN if length = 0
+     * @throws IllegalArgumentException 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) {
+        double product = Double.NaN;
+        if (test(values, begin, length)) {
+            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>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>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 Double.NaN if length = 0
+     * @throws IllegalArgumentException if the parameters are not valid
+     * @since 2.1
+     */
+    public double evaluate(final double[] values, final double[] weights,
+                           final int begin, final int length) {
+        double product = Double.NaN;
+        if (test(values, weights, begin, length)) {
+            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>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>
+     * </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 IllegalArgumentException if the parameters are not valid
+     * @since 2.1
+     */
+    public double evaluate(final double[] values, final double[] weights) {
+        return evaluate(values, weights, 0, values.length);
+    }
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Product copy() {
+        Product result = new Product();
+        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 NullPointerException if either source or dest is null
+     */
+    public static void copy(Product source, Product dest) {
+        dest.setData(source.getDataRef());
+        dest.n = source.n;
+        dest.value = source.value;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/stat/descriptive/summary/Sum.java b/src/main/java/org/apache/commons/math/stat/descriptive/summary/Sum.java
new file mode 100644
index 0000000..7188ea8
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/summary/Sum.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.math.stat.descriptive.summary;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.stat.descriptive.AbstractStorelessUnivariateStatistic;
+
+
+/**
+  * Returns the sum of the available values.
+ * <p>
+ * If there are no values in the dataset, or 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>
+ *
+ * @version $Revision: 1006299 $ $Date: 2010-10-10 16:47:17 +0200 (dim. 10 oct. 2010) $
+ */
+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 = Double.NaN;
+    }
+
+    /**
+     * Copy constructor, creates a new {@code Sum} identical
+     * to the {@code original}
+     *
+     * @param original the {@code Sum} instance to copy
+     */
+    public Sum(Sum original) {
+        copy(original, this);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void increment(final double d) {
+        if (n == 0) {
+            value = d;
+        } else {
+            value += d;
+        }
+        n++;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public double getResult() {
+        return value;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long getN() {
+        return n;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void clear() {
+        value = Double.NaN;
+        n = 0;
+    }
+
+    /**
+     * 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.</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 Double.NaN if length = 0
+     * @throws IllegalArgumentException 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) {
+        double sum = Double.NaN;
+        if (test(values, begin, length)) {
+            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 <code>Double.NaN</code> if the designated subarray
+     * is empty.
+     * <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>
+     * 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 Double.NaN if length = 0
+     * @throws IllegalArgumentException if the parameters are not valid
+     * @since 2.1
+     */
+    public double evaluate(final double[] values, final double[] weights,
+                           final int begin, final int length) {
+        double sum = Double.NaN;
+        if (test(values, weights, begin, length)) {
+            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>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>
+     * </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 IllegalArgumentException if the parameters are not valid
+     * @since 2.1
+     */
+    public double evaluate(final double[] values, final double[] weights) {
+        return evaluate(values, weights, 0, values.length);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Sum copy() {
+        Sum result = new Sum();
+        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 NullPointerException if either source or dest is null
+     */
+    public static void copy(Sum source, Sum dest) {
+        dest.setData(source.getDataRef());
+        dest.n = source.n;
+        dest.value = source.value;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/stat/descriptive/summary/SumOfLogs.java b/src/main/java/org/apache/commons/math/stat/descriptive/summary/SumOfLogs.java
new file mode 100644
index 0000000..331d5d2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/summary/SumOfLogs.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.math.stat.descriptive.summary;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.stat.descriptive.AbstractStorelessUnivariateStatistic;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Returns the sum of the natural logs for this collection of values.
+ * <p>
+ * Uses {@link java.lang.Math#log(double)} to compute the logs.  Therefore,
+ * <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>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>
+ *
+ * @version $Revision: 1006299 $ $Date: 2010-10-10 16:47:17 +0200 (dim. 10 oct. 2010) $
+ */
+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
+     */
+    public SumOfLogs(SumOfLogs original) {
+        copy(original, this);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void increment(final double d) {
+        value += FastMath.log(d);
+        n++;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public double getResult() {
+        if (n > 0) {
+            return value;
+        } else {
+            return Double.NaN;
+        }
+    }
+
+    /**
+     * {@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>IllegalArgumentException</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 Double.NaN if
+     * length = 0
+     * @throws IllegalArgumentException 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) {
+        double sumLog = Double.NaN;
+        if (test(values, begin, length)) {
+            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();
+        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 NullPointerException if either source or dest is null
+     */
+    public static void copy(SumOfLogs source, SumOfLogs dest) {
+        dest.setData(source.getDataRef());
+        dest.n = source.n;
+        dest.value = source.value;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/stat/descriptive/summary/SumOfSquares.java b/src/main/java/org/apache/commons/math/stat/descriptive/summary/SumOfSquares.java
new file mode 100644
index 0000000..a632bf6
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/summary/SumOfSquares.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.math.stat.descriptive.summary;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.stat.descriptive.AbstractStorelessUnivariateStatistic;
+
+/**
+ * Returns the sum of the squares of the available values.
+ * <p>
+ * If there are no values in the dataset, or 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>
+ *
+ * @version $Revision: 1006299 $ $Date: 2010-10-10 16:47:17 +0200 (dim. 10 oct. 2010) $
+ */
+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 = Double.NaN;
+    }
+
+    /**
+     * Copy constructor, creates a new {@code SumOfSquares} identical
+     * to the {@code original}
+     *
+     * @param original the {@code SumOfSquares} instance to copy
+     */
+    public SumOfSquares(SumOfSquares original) {
+        copy(original, this);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void increment(final double d) {
+        if (n == 0) {
+            value = d * d;
+        } else {
+            value += d * d;
+        }
+        n++;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public double getResult() {
+        return value;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public long getN() {
+        return n;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void clear() {
+        value = Double.NaN;
+        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>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 sum of the squares of the values or Double.NaN if length = 0
+     * @throws IllegalArgumentException 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) {
+        double sumSq = Double.NaN;
+        if (test(values, begin, length)) {
+            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();
+        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 NullPointerException if either source or dest is null
+     */
+    public static void copy(SumOfSquares source, SumOfSquares dest) {
+        dest.setData(source.getDataRef());
+        dest.n = source.n;
+        dest.value = source.value;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/stat/descriptive/summary/package.html b/src/main/java/org/apache/commons/math/stat/descriptive/summary/package.html
new file mode 100644
index 0000000..db7f731
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/descriptive/summary/package.html
@@ -0,0 +1,20 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 480440 $ $Date: 2006-11-29 08:14:12 +0100 (mer. 29 nov. 2006) $ -->
+    <body>Other summary statistics.</body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/stat/inference/ChiSquareTest.java b/src/main/java/org/apache/commons/math/stat/inference/ChiSquareTest.java
new file mode 100644
index 0000000..6a3ecac
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/inference/ChiSquareTest.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.math.stat.inference;
+
+import org.apache.commons.math.MathException;
+
+/**
+ * An interface for Chi-Square tests.
+ * <p>This interface handles only known distributions. If the distribution is
+ * unknown and should be provided by a sample, then the {@link UnknownDistributionChiSquareTest
+ * UnknownDistributionChiSquareTest} extended interface should be used instead.</p>
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+public interface ChiSquareTest {
+
+     /**
+     * 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 >= 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>
+     *
+     * @param observed array of observed frequency counts
+     * @param expected array of expected frequency counts
+     * @return chiSquare statistic
+     * @throws IllegalArgumentException if preconditions are not met
+     */
+    double chiSquare(double[] expected, long[] observed)
+        throws IllegalArgumentException;
+
+    /**
+     * 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 >= 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>
+     *
+     * @param observed array of observed frequency counts
+     * @param expected array of expected frequency counts
+     * @return p-value
+     * @throws IllegalArgumentException if preconditions are not met
+     * @throws MathException if an error occurs computing the p-value
+     */
+    double chiSquareTest(double[] expected, long[] observed)
+        throws IllegalArgumentException, MathException;
+
+    /**
+     * 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 >= 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 </code>
+     * </li></ul></p><p>
+     * If any of the preconditions are not met, an
+     * <code>IllegalArgumentException</code> is thrown.</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 IllegalArgumentException if preconditions are not met
+     * @throws MathException if an error occurs performing the test
+     */
+    boolean chiSquareTest(double[] expected, long[] observed, double alpha)
+        throws IllegalArgumentException, MathException;
+
+    /**
+     *  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 >= 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 statistic
+     * @throws IllegalArgumentException if preconditions are not met
+     */
+    double chiSquare(long[][] counts)
+    throws IllegalArgumentException;
+
+    /**
+     * 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 >= 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 IllegalArgumentException if preconditions are not met
+     * @throws MathException if an error occurs computing the p-value
+     */
+    double chiSquareTest(long[][] counts)
+    throws IllegalArgumentException, MathException;
+
+    /**
+     * 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 >= 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 IllegalArgumentException if preconditions are not met
+     * @throws MathException if an error occurs performing the test
+     */
+    boolean chiSquareTest(long[][] counts, double alpha)
+    throws IllegalArgumentException, MathException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/stat/inference/ChiSquareTestImpl.java b/src/main/java/org/apache/commons/math/stat/inference/ChiSquareTestImpl.java
new file mode 100644
index 0000000..abb32a5
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/inference/ChiSquareTestImpl.java
@@ -0,0 +1,424 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.stat.inference;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.distribution.ChiSquaredDistribution;
+import org.apache.commons.math.distribution.ChiSquaredDistributionImpl;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Implements Chi-Square test statistics defined in the
+ * {@link UnknownDistributionChiSquareTest} interface.
+ *
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ */
+public class ChiSquareTestImpl implements UnknownDistributionChiSquareTest {
+
+    /** Distribution used to compute inference statistics. */
+    private ChiSquaredDistribution distribution;
+
+    /**
+     * Construct a ChiSquareTestImpl
+     */
+    public ChiSquareTestImpl() {
+        this(new ChiSquaredDistributionImpl(1.0));
+    }
+
+    /**
+     * Create a test instance using the given distribution for computing
+     * inference statistics.
+     * @param x distribution used to compute inference statistics.
+     * @since 1.2
+     */
+    public ChiSquareTestImpl(ChiSquaredDistribution x) {
+        super();
+        setDistribution(x);
+    }
+     /**
+     * {@inheritDoc}
+     * <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 chi-square test statistic
+     * @throws IllegalArgumentException if preconditions are not met
+     * or length is less than 2
+     */
+    public double chiSquare(double[] expected, long[] observed)
+        throws IllegalArgumentException {
+        if (expected.length < 2) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.INSUFFICIENT_DIMENSION, expected.length, 2);
+        }
+        if (expected.length != observed.length) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, expected.length, observed.length);
+        }
+        checkPositive(expected);
+        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;
+    }
+
+    /**
+     * {@inheritDoc}
+     * <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 IllegalArgumentException if preconditions are not met
+     * @throws MathException if an error occurs computing the p-value
+     */
+    public double chiSquareTest(double[] expected, long[] observed)
+        throws IllegalArgumentException, MathException {
+        distribution.setDegreesOfFreedom(expected.length - 1.0);
+        return 1.0 - distribution.cumulativeProbability(
+            chiSquare(expected, observed));
+    }
+
+    /**
+     * {@inheritDoc}
+     * <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 IllegalArgumentException if preconditions are not met
+     * @throws MathException if an error occurs performing the test
+     */
+    public boolean chiSquareTest(double[] expected, long[] observed,
+            double alpha) throws IllegalArgumentException, MathException {
+        if ((alpha <= 0) || (alpha > 0.5)) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.OUT_OF_BOUND_SIGNIFICANCE_LEVEL,
+                  alpha, 0, 0.5);
+        }
+        return chiSquareTest(expected, observed) < alpha;
+    }
+
+    /**
+     * @param counts array representation of 2-way table
+     * @return chi-square test statistic
+     * @throws IllegalArgumentException if preconditions are not met
+     */
+    public double chiSquare(long[][] counts) throws IllegalArgumentException {
+
+        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;
+    }
+
+    /**
+     * @param counts array representation of 2-way table
+     * @return p-value
+     * @throws IllegalArgumentException if preconditions are not met
+     * @throws MathException if an error occurs computing the p-value
+     */
+    public double chiSquareTest(long[][] counts)
+    throws IllegalArgumentException, MathException {
+        checkArray(counts);
+        double df = ((double) counts.length -1) * ((double) counts[0].length - 1);
+        distribution.setDegreesOfFreedom(df);
+        return 1 - distribution.cumulativeProbability(chiSquare(counts));
+    }
+
+    /**
+     * @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 IllegalArgumentException if preconditions are not met
+     * @throws MathException if an error occurs performing the test
+     */
+    public boolean chiSquareTest(long[][] counts, double alpha)
+    throws IllegalArgumentException, MathException {
+        if ((alpha <= 0) || (alpha > 0.5)) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.OUT_OF_BOUND_SIGNIFICANCE_LEVEL,
+                  alpha, 0.0, 0.5);
+        }
+        return chiSquareTest(counts) < alpha;
+    }
+
+    /**
+     * @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 chi-square test statistic
+     * @throws IllegalArgumentException if preconditions are not met
+     * @since 1.2
+     */
+    public double chiSquareDataSetsComparison(long[] observed1, long[] observed2)
+        throws IllegalArgumentException {
+
+        // Make sure lengths are same
+        if (observed1.length < 2) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.INSUFFICIENT_DIMENSION, observed1.length, 2);
+        }
+        if (observed1.length != observed2.length) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE,
+                  observed1.length, observed2.length);
+        }
+
+        // Ensure non-negative counts
+        checkNonNegative(observed1);
+        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) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.OBSERVED_COUNTS_ALL_ZERO, 1);
+        }
+        if (countSum2 == 0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.OBSERVED_COUNTS_ALL_ZERO, 2);
+        }
+        // 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 MathRuntimeException.createIllegalArgumentException(
+                      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;
+    }
+
+    /**
+     * @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 IllegalArgumentException if preconditions are not met
+     * @throws MathException if an error occurs computing the p-value
+     * @since 1.2
+     */
+    public double chiSquareTestDataSetsComparison(long[] observed1, long[] observed2)
+        throws IllegalArgumentException, MathException {
+        distribution.setDegreesOfFreedom((double) observed1.length - 1);
+        return 1 - distribution.cumulativeProbability(
+                chiSquareDataSetsComparison(observed1, observed2));
+    }
+
+    /**
+     * @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 IllegalArgumentException if preconditions are not met
+     * @throws MathException if an error occurs performing the test
+     * @since 1.2
+     */
+    public boolean chiSquareTestDataSetsComparison(long[] observed1, long[] observed2,
+            double alpha) throws IllegalArgumentException, MathException {
+        if ((alpha <= 0) || (alpha > 0.5)) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.OUT_OF_BOUND_SIGNIFICANCE_LEVEL,
+                  alpha, 0.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,
+     * throwing IllegalArgumentException if any of these checks fail.
+     *
+     * @param in input 2-way table to check
+     * @throws IllegalArgumentException if the array is not valid
+     */
+    private void checkArray(long[][] in) throws IllegalArgumentException {
+
+        if (in.length < 2) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.INSUFFICIENT_DIMENSION, in.length, 2);
+        }
+
+        if (in[0].length < 2) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.INSUFFICIENT_DIMENSION, in[0].length, 2);
+        }
+
+        checkRectangular(in);
+        checkNonNegative(in);
+
+    }
+
+    //---------------------  Private array methods -- should find a utility home for these
+
+    /**
+     * Throws IllegalArgumentException if the input array is not rectangular.
+     *
+     * @param in array to be tested
+     * @throws NullPointerException if input array is null
+     * @throws IllegalArgumentException if input array is not rectangular
+     */
+    private void checkRectangular(long[][] in) {
+        for (int i = 1; i < in.length; i++) {
+            if (in[i].length != in[0].length) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                      LocalizedFormats.DIFFERENT_ROWS_LENGTHS,
+                      in[i].length, in[0].length);
+            }
+        }
+    }
+
+    /**
+     * Check all entries of the input array are > 0.
+     *
+     * @param in array to be tested
+     * @exception IllegalArgumentException if one entry is not positive
+     */
+    private void checkPositive(double[] in) throws IllegalArgumentException {
+        for (int i = 0; i < in.length; i++) {
+            if (in[i] <= 0) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                      LocalizedFormats.NOT_POSITIVE_ELEMENT_AT_INDEX,
+                      i, in[i]);
+            }
+        }
+    }
+
+    /**
+     * Check all entries of the input array are >= 0.
+     *
+     * @param in array to be tested
+     * @exception IllegalArgumentException if one entry is negative
+     */
+    private void checkNonNegative(long[] in) throws IllegalArgumentException {
+        for (int i = 0; i < in.length; i++) {
+            if (in[i] < 0) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                      LocalizedFormats.NEGATIVE_ELEMENT_AT_INDEX,
+                      i, in[i]);
+            }
+        }
+    }
+
+    /**
+     * Check all entries of the input array are >= 0.
+     *
+     * @param in array to be tested
+     * @exception IllegalArgumentException if one entry is negative
+     */
+    private void checkNonNegative(long[][] in) throws IllegalArgumentException {
+        for (int i = 0; i < in.length; i ++) {
+            for (int j = 0; j < in[i].length; j++) {
+                if (in[i][j] < 0) {
+                    throw MathRuntimeException.createIllegalArgumentException(
+                          LocalizedFormats.NEGATIVE_ELEMENT_AT_2D_INDEX,
+                          i, j, in[i][j]);
+                }
+            }
+        }
+    }
+
+    /**
+     * Modify the distribution used to compute inference statistics.
+     *
+     * @param value
+     *            the new distribution
+     * @since 1.2
+     */
+    public void setDistribution(ChiSquaredDistribution value) {
+        distribution = value;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/stat/inference/OneWayAnova.java b/src/main/java/org/apache/commons/math/stat/inference/OneWayAnova.java
new file mode 100644
index 0000000..a2cde47
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/inference/OneWayAnova.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.math.stat.inference;
+
+import org.apache.commons.math.MathException;
+import java.util.Collection;
+
+/**
+ * An interface for one-way ANOVA (analysis of variance).
+ *
+ * <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.math.stat.inference.TTest}.
+ * </p>
+ *
+ * @since 1.2
+ * @version $Revision: 811786 $ $Date: 2009-09-06 11:36:08 +0200 (dim. 06 sept. 2009) $
+ */
+public interface 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>
+     *
+     * @param categoryData <code>Collection</code> of <code>double[]</code>
+     * arrays each containing data for one category
+     * @return Fvalue
+     * @throws IllegalArgumentException if the preconditions are not met
+     * @throws MathException if the statistic can not be computed do to a
+     *         convergence or other numerical error.
+     */
+    double anovaFValue(Collection<double[]> categoryData)
+        throws IllegalArgumentException, MathException;
+
+    /**
+     * 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>
+     *
+     * @param categoryData <code>Collection</code> of <code>double[]</code>
+     * arrays each containing data for one category
+     * @return Pvalue
+     * @throws IllegalArgumentException if the preconditions are not met
+     * @throws MathException if the statistic can not be computed do to a
+     *         convergence or other numerical error.
+     */
+    double anovaPValue(Collection<double[]> categoryData)
+        throws IllegalArgumentException, MathException;
+
+    /**
+     * 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>
+     *
+     * @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 IllegalArgumentException if the preconditions are not met
+     * @throws MathException if the statistic can not be computed do to a
+     *         convergence or other numerical error.
+     */
+    boolean anovaTest(Collection<double[]> categoryData, double alpha)
+        throws IllegalArgumentException, MathException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/stat/inference/OneWayAnovaImpl.java b/src/main/java/org/apache/commons/math/stat/inference/OneWayAnovaImpl.java
new file mode 100644
index 0000000..a47d0cf
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/inference/OneWayAnovaImpl.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.math.stat.inference;
+
+import java.util.Collection;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.distribution.FDistribution;
+import org.apache.commons.math.distribution.FDistributionImpl;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.stat.descriptive.summary.Sum;
+import org.apache.commons.math.stat.descriptive.summary.SumOfSquares;
+
+
+/**
+ * Implements one-way ANOVA statistics defined in the {@link OneWayAnovaImpl}
+ * interface.
+ *
+ * <p>Uses the
+ * {@link org.apache.commons.math.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
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ */
+public class OneWayAnovaImpl implements OneWayAnova  {
+
+    /**
+     * Default constructor.
+     */
+    public OneWayAnovaImpl() {
+    }
+
+    /**
+     * {@inheritDoc}<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>
+     */
+    public double anovaFValue(Collection<double[]> categoryData)
+        throws IllegalArgumentException, MathException {
+        AnovaStats a = anovaStats(categoryData);
+        return a.F;
+    }
+
+    /**
+     * {@inheritDoc}<p>
+     * This implementation uses the
+     * {@link org.apache.commons.math.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>
+     */
+    public double anovaPValue(Collection<double[]> categoryData)
+        throws IllegalArgumentException, MathException {
+        AnovaStats a = anovaStats(categoryData);
+        FDistribution fdist = new FDistributionImpl(a.dfbg, a.dfwg);
+        return 1.0 - fdist.cumulativeProbability(a.F);
+    }
+
+    /**
+     * {@inheritDoc}<p>
+     * This implementation uses the
+     * {@link org.apache.commons.math.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>
+     */
+    public boolean anovaTest(Collection<double[]> categoryData, double alpha)
+        throws IllegalArgumentException, MathException {
+        if ((alpha <= 0) || (alpha > 0.5)) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  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
+     * @return computed AnovaStats
+     * @throws IllegalArgumentException if categoryData does not meet
+     * preconditions specified in the interface definition
+     * @throws MathException if an error occurs computing the Anova stats
+     */
+    private AnovaStats anovaStats(Collection<double[]> categoryData)
+        throws IllegalArgumentException, MathException {
+
+        // check if we have enough categories
+        if (categoryData.size() < 2) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.TWO_OR_MORE_CATEGORIES_REQUIRED,
+                  categoryData.size());
+        }
+
+        // check if each category has enough data and all is double[]
+        for (double[] array : categoryData) {
+            if (array.length <= 1) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                      LocalizedFormats.TWO_OR_MORE_VALUES_IN_CATEGORY_REQUIRED,
+                      array.length);
+            }
+        }
+
+        int dfwg = 0;
+        double sswg = 0;
+        Sum totsum = new Sum();
+        SumOfSquares totsumsq = new SumOfSquares();
+        int totnum = 0;
+
+        for (double[] data : categoryData) {
+
+            Sum sum = new Sum();
+            SumOfSquares sumsq = new SumOfSquares();
+            int num = 0;
+
+            for (int i = 0; i < data.length; i++) {
+                double val = data[i];
+
+                // within category
+                num++;
+                sum.increment(val);
+                sumsq.increment(val);
+
+                // for all categories
+                totnum++;
+                totsum.increment(val);
+                totsumsq.increment(val);
+            }
+            dfwg += num - 1;
+            double ss = sumsq.getResult() - sum.getResult() * sum.getResult() / num;
+            sswg += ss;
+        }
+        double sst = totsumsq.getResult() - totsum.getResult() *
+            totsum.getResult()/totnum;
+        double ssbg = sst - sswg;
+        int dfbg = categoryData.size() - 1;
+        double msbg = ssbg/dfbg;
+        double mswg = sswg/dfwg;
+        double F = msbg/mswg;
+
+        return new AnovaStats(dfbg, dfwg, F);
+    }
+
+    /**
+        Convenience class to pass dfbg,dfwg,F values around within AnovaImpl.
+        No get/set methods provided.
+    */
+    private static class AnovaStats {
+
+        /** Degrees of freedom in numerator (between groups). */
+        private int dfbg;
+
+        /** Degrees of freedom in denominator (within groups). */
+        private int dfwg;
+
+        /** Statistic. */
+        private 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/math/stat/inference/TTest.java b/src/main/java/org/apache/commons/math/stat/inference/TTest.java
new file mode 100644
index 0000000..0ccb0c0
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/inference/TTest.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.math.stat.inference;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.stat.descriptive.StatisticalSummary;
+
+/**
+ * An interface 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>
+ *
+ *
+ * @version $Revision: 811786 $ $Date: 2009-09-06 11:36:08 +0200 (dim. 06 sept. 2009) $
+ */
+public interface 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 IllegalArgumentException if the precondition is not met
+     * @throws MathException if the statistic can not be computed do to a
+     *         convergence or other numerical error.
+     */
+    double pairedT(double[] sample1, double[] sample2)
+        throws IllegalArgumentException, MathException;
+    /**
+     * 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 IllegalArgumentException if the precondition is not met
+     * @throws MathException if an error occurs computing the p-value
+     */
+    double pairedTTest(double[] sample1, double[] sample2)
+        throws IllegalArgumentException, MathException;
+    /**
+     * 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 < 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 IllegalArgumentException if the preconditions are not met
+     * @throws MathException if an error occurs performing the test
+     */
+    boolean pairedTTest(
+        double[] sample1,
+        double[] sample2,
+        double alpha)
+        throws IllegalArgumentException, MathException;
+    /**
+     * 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 IllegalArgumentException if input array length is less than 2
+     */
+    double t(double mu, double[] observed)
+        throws IllegalArgumentException;
+    /**
+     * 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() > = 2</code>.
+     * </li></ul></p>
+     *
+     * @param mu comparison constant
+     * @param sampleStats DescriptiveStatistics holding sample summary statitstics
+     * @return t statistic
+     * @throws IllegalArgumentException if the precondition is not met
+     */
+    double t(double mu, StatisticalSummary sampleStats)
+        throws IllegalArgumentException;
+    /**
+     * 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-statisitc 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 IllegalArgumentException if the precondition is not met
+     */
+    double homoscedasticT(double[] sample1, double[] sample2)
+        throws IllegalArgumentException;
+    /**
+     * 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-statisitc 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 IllegalArgumentException if the precondition is not met
+     */
+    double t(double[] sample1, double[] sample2)
+        throws IllegalArgumentException;
+    /**
+     * 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-statisitc 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 IllegalArgumentException if the precondition is not met
+     */
+    double t(
+        StatisticalSummary sampleStats1,
+        StatisticalSummary sampleStats2)
+        throws IllegalArgumentException;
+    /**
+     * 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-statisitc 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 IllegalArgumentException if the precondition is not met
+     */
+    double homoscedasticT(
+        StatisticalSummary sampleStats1,
+        StatisticalSummary sampleStats2)
+        throws IllegalArgumentException;
+    /**
+     * 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 IllegalArgumentException if the precondition is not met
+     * @throws MathException if an error occurs computing the p-value
+     */
+    double tTest(double mu, double[] sample)
+        throws IllegalArgumentException, MathException;
+    /**
+     * 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 IllegalArgumentException if the precondition is not met
+     * @throws MathException if an error computing the p-value
+     */
+    boolean tTest(double mu, double[] sample, double alpha)
+        throws IllegalArgumentException, MathException;
+    /**
+     * 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 IllegalArgumentException if the precondition is not met
+     * @throws MathException if an error occurs computing the p-value
+     */
+    double tTest(double mu, StatisticalSummary sampleStats)
+        throws IllegalArgumentException, MathException;
+    /**
+     * 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 IllegalArgumentException if the precondition is not met
+     * @throws MathException if an error occurs computing the p-value
+     */
+    boolean tTest(
+        double mu,
+        StatisticalSummary sampleStats,
+        double alpha)
+        throws IllegalArgumentException, MathException;
+    /**
+     * 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 IllegalArgumentException if the precondition is not met
+     * @throws MathException if an error occurs computing the p-value
+     */
+    double tTest(double[] sample1, double[] sample2)
+        throws IllegalArgumentException, MathException;
+    /**
+     * 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 IllegalArgumentException if the precondition is not met
+     * @throws MathException if an error occurs computing the p-value
+     */
+    double homoscedasticTTest(
+        double[] sample1,
+        double[] sample2)
+        throws IllegalArgumentException, MathException;
+    /**
+     * 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 IllegalArgumentException if the preconditions are not met
+     * @throws MathException if an error occurs performing the test
+     */
+    boolean tTest(
+        double[] sample1,
+        double[] sample2,
+        double alpha)
+        throws IllegalArgumentException, MathException;
+    /**
+     * 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 IllegalArgumentException if the preconditions are not met
+     * @throws MathException if an error occurs performing the test
+     */
+    boolean homoscedasticTTest(
+        double[] sample1,
+        double[] sample2,
+        double alpha)
+        throws IllegalArgumentException, MathException;
+    /**
+     * 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 popuation 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 IllegalArgumentException if the precondition is not met
+     * @throws MathException if an error occurs computing the p-value
+     */
+    double tTest(
+        StatisticalSummary sampleStats1,
+        StatisticalSummary sampleStats2)
+        throws IllegalArgumentException, MathException;
+    /**
+     * 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 IllegalArgumentException if the precondition is not met
+     * @throws MathException if an error occurs computing the p-value
+     */
+    double homoscedasticTTest(
+        StatisticalSummary sampleStats1,
+        StatisticalSummary sampleStats2)
+        throws IllegalArgumentException, MathException;
+    /**
+     * 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 IllegalArgumentException if the preconditions are not met
+     * @throws MathException if an error occurs performing the test
+     */
+    boolean tTest(
+        StatisticalSummary sampleStats1,
+        StatisticalSummary sampleStats2,
+        double alpha)
+        throws IllegalArgumentException, MathException;
+}
diff --git a/src/main/java/org/apache/commons/math/stat/inference/TTestImpl.java b/src/main/java/org/apache/commons/math/stat/inference/TTestImpl.java
new file mode 100644
index 0000000..d4d1a12
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/inference/TTestImpl.java
@@ -0,0 +1,1069 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.stat.inference;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.distribution.TDistribution;
+import org.apache.commons.math.distribution.TDistributionImpl;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.stat.StatUtils;
+import org.apache.commons.math.stat.descriptive.StatisticalSummary;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Implements t-test statistics defined in the {@link TTest} interface.
+ * <p>
+ * Uses commons-math {@link org.apache.commons.math.distribution.TDistributionImpl}
+ * implementation to estimate exact p-values.</p>
+ *
+ * @version $Revision: 1042336 $ $Date: 2010-12-05 13:40:48 +0100 (dim. 05 déc. 2010) $
+ */
+public class TTestImpl implements TTest  {
+
+    /** Distribution used to compute inference statistics.
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    private TDistribution distribution;
+
+    /**
+     * Default constructor.
+     */
+    public TTestImpl() {
+        this(new TDistributionImpl(1.0));
+    }
+
+    /**
+     * Create a test instance using the given distribution for computing
+     * inference statistics.
+     * @param t distribution used to compute inference statistics.
+     * @since 1.2
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    public TTestImpl(TDistribution t) {
+        super();
+        setDistribution(t);
+    }
+
+    /**
+     * 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 IllegalArgumentException if the precondition is not met
+     * @throws MathException if the statistic can not be computed do to a
+     *         convergence or other numerical error.
+     */
+    public double pairedT(double[] sample1, double[] sample2)
+        throws IllegalArgumentException, MathException {
+        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 IllegalArgumentException if the precondition is not met
+     * @throws MathException if an error occurs computing the p-value
+     */
+    public double pairedTTest(double[] sample1, double[] sample2)
+        throws IllegalArgumentException, MathException {
+        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 < 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 IllegalArgumentException if the preconditions are not met
+     * @throws MathException if an error occurs performing the test
+     */
+    public boolean pairedTTest(double[] sample1, double[] sample2, double alpha)
+        throws IllegalArgumentException, MathException {
+        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 IllegalArgumentException if input array length is less than 2
+     */
+    public double t(double mu, double[] observed)
+    throws IllegalArgumentException {
+        checkSampleData(observed);
+        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() > = 2</code>.
+     * </li></ul></p>
+     *
+     * @param mu comparison constant
+     * @param sampleStats DescriptiveStatistics holding sample summary statitstics
+     * @return t statistic
+     * @throws IllegalArgumentException if the precondition is not met
+     */
+    public double t(double mu, StatisticalSummary sampleStats)
+    throws IllegalArgumentException {
+        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-statisitc 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 IllegalArgumentException if the precondition is not met
+     */
+    public double homoscedasticT(double[] sample1, double[] sample2)
+    throws IllegalArgumentException {
+        checkSampleData(sample1);
+        checkSampleData(sample2);
+        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-statisitc 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 IllegalArgumentException if the precondition is not met
+     */
+    public double t(double[] sample1, double[] sample2)
+    throws IllegalArgumentException {
+        checkSampleData(sample1);
+        checkSampleData(sample2);
+        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-statisitc 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 IllegalArgumentException if the precondition is not met
+     */
+    public double t(StatisticalSummary sampleStats1,
+                    StatisticalSummary sampleStats2)
+    throws IllegalArgumentException {
+        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-statisitc 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>
+     * 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 IllegalArgumentException if the precondition is not met
+     */
+    public double homoscedasticT(StatisticalSummary sampleStats1,
+            StatisticalSummary sampleStats2)
+    throws IllegalArgumentException {
+        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 IllegalArgumentException if the precondition is not met
+     * @throws MathException if an error occurs computing the p-value
+     */
+    public double tTest(double mu, double[] sample)
+    throws IllegalArgumentException, MathException {
+        checkSampleData(sample);
+        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 IllegalArgumentException if the precondition is not met
+     * @throws MathException if an error computing the p-value
+     */
+    public boolean tTest(double mu, double[] sample, double alpha)
+    throws IllegalArgumentException, MathException {
+        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 IllegalArgumentException if the precondition is not met
+     * @throws MathException if an error occurs computing the p-value
+     */
+    public double tTest(double mu, StatisticalSummary sampleStats)
+    throws IllegalArgumentException, MathException {
+        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 IllegalArgumentException if the precondition is not met
+     * @throws MathException if an error occurs computing the p-value
+     */
+    public boolean tTest( double mu, StatisticalSummary sampleStats,
+            double alpha)
+    throws IllegalArgumentException, MathException {
+        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 IllegalArgumentException if the precondition is not met
+     * @throws MathException if an error occurs computing the p-value
+     */
+    public double tTest(double[] sample1, double[] sample2)
+    throws IllegalArgumentException, MathException {
+        checkSampleData(sample1);
+        checkSampleData(sample2);
+        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>
+     * 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 IllegalArgumentException if the precondition is not met
+     * @throws MathException if an error occurs computing the p-value
+     */
+    public double homoscedasticTTest(double[] sample1, double[] sample2)
+    throws IllegalArgumentException, MathException {
+        checkSampleData(sample1);
+        checkSampleData(sample2);
+        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 IllegalArgumentException if the preconditions are not met
+     * @throws MathException if an error occurs performing the test
+     */
+    public boolean tTest(double[] sample1, double[] sample2,
+            double alpha)
+    throws IllegalArgumentException, MathException {
+        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 IllegalArgumentException if the preconditions are not met
+     * @throws MathException if an error occurs performing the test
+     */
+    public boolean homoscedasticTTest(double[] sample1, double[] sample2,
+            double alpha)
+    throws IllegalArgumentException, MathException {
+        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 popuation 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 IllegalArgumentException if the precondition is not met
+     * @throws MathException if an error occurs computing the p-value
+     */
+    public double tTest(StatisticalSummary sampleStats1, StatisticalSummary sampleStats2)
+    throws IllegalArgumentException, MathException {
+        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 IllegalArgumentException if the precondition is not met
+     * @throws MathException if an error occurs computing the p-value
+     */
+    public double homoscedasticTTest(StatisticalSummary sampleStats1,
+                                     StatisticalSummary sampleStats2)
+    throws IllegalArgumentException, MathException {
+        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 IllegalArgumentException if the preconditions are not met
+     * @throws MathException if an error occurs performing the test
+     */
+    public boolean tTest(StatisticalSummary sampleStats1,
+            StatisticalSummary sampleStats2, double alpha)
+    throws IllegalArgumentException, MathException {
+        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(double m, double mu, double v, 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(double m1, double m2,  double v1, double v2, double n1,
+            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(double m1, double m2,  double v1,
+            double v2, double n1, double n2)  {
+            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 MathException if an error occurs computing the p-value
+     */
+    protected double tTest(double m, double mu, double v, double n)
+    throws MathException {
+        double t = FastMath.abs(t(m, mu, v, n));
+        distribution.setDegreesOfFreedom(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 MathException if an error occurs computing the p-value
+     */
+    protected double tTest(double m1, double m2, double v1, double v2,
+            double n1, double n2)
+    throws MathException {
+        double t = FastMath.abs(t(m1, m2, v1, v2, n1, n2));
+        double degreesOfFreedom = 0;
+        degreesOfFreedom = df(v1, v2, n1, n2);
+        distribution.setDegreesOfFreedom(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 MathException if an error occurs computing the p-value
+     */
+    protected double homoscedasticTTest(double m1, double m2, double v1,
+            double v2, double n1, double n2)
+    throws MathException {
+        double t = FastMath.abs(homoscedasticT(m1, m2, v1, v2, n1, n2));
+        double degreesOfFreedom = n1 + n2 - 2;
+        distribution.setDegreesOfFreedom(degreesOfFreedom);
+        return 2.0 * distribution.cumulativeProbability(-t);
+    }
+
+    /**
+     * Modify the distribution used to compute inference statistics.
+     * @param value the new distribution
+     * @since 1.2
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    public void setDistribution(TDistribution value) {
+        distribution = value;
+    }
+
+    /** Check significance level.
+     * @param alpha significance level
+     * @exception IllegalArgumentException if significance level is out of bounds
+     */
+    private void checkSignificanceLevel(final double alpha)
+        throws IllegalArgumentException {
+        if ((alpha <= 0) || (alpha > 0.5)) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.OUT_OF_BOUND_SIGNIFICANCE_LEVEL,
+                  alpha, 0.0, 0.5);
+        }
+    }
+
+    /** Check sample data.
+     * @param data sample data
+     * @exception IllegalArgumentException if there is not enough sample data
+     */
+    private void checkSampleData(final double[] data)
+        throws IllegalArgumentException {
+        if ((data == null) || (data.length < 2)) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.INSUFFICIENT_DATA_FOR_T_STATISTIC,
+                  (data == null) ? 0 : data.length);
+        }
+    }
+
+    /** Check sample data.
+     * @param stat statistical summary
+     * @exception IllegalArgumentException if there is not enough sample data
+     */
+    private void checkSampleData(final StatisticalSummary stat)
+        throws IllegalArgumentException {
+        if ((stat == null) || (stat.getN() < 2)) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.INSUFFICIENT_DATA_FOR_T_STATISTIC,
+                  (stat == null) ? 0 : stat.getN());
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/stat/inference/TestUtils.java b/src/main/java/org/apache/commons/math/stat/inference/TestUtils.java
new file mode 100644
index 0000000..5023d55
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/inference/TestUtils.java
@@ -0,0 +1,436 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.stat.inference;
+
+import java.util.Collection;
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.stat.descriptive.StatisticalSummary;
+
+/**
+ * A collection of static methods to create inference test instances or to
+ * perform inference tests.
+ *
+ * <p>
+ * The set methods are not compatible with using the class in multiple threads,
+ * and have therefore been deprecated (along with the getters).
+ * The setters and getters will be removed in version 3.0.
+ *
+ * @since 1.1
+ * @version $Revision: 1067582 $ $Date: 2011-02-06 04:55:32 +0100 (dim. 06 févr. 2011) $
+ */
+public class TestUtils  {
+
+    /** Singleton TTest instance using default implementation. */
+    private static TTest tTest = new TTestImpl();
+
+    /** Singleton ChiSquareTest instance using default implementation. */
+    private static ChiSquareTest chiSquareTest =
+        new ChiSquareTestImpl();
+
+    /** Singleton ChiSquareTest instance using default implementation. */
+    private static UnknownDistributionChiSquareTest unknownDistributionChiSquareTest =
+        new ChiSquareTestImpl();
+
+    /** Singleton OneWayAnova instance using default implementation. */
+    private static OneWayAnova oneWayAnova =
+        new OneWayAnovaImpl();
+
+    /**
+     * Prevent instantiation.
+     */
+    protected TestUtils() {
+        super();
+    }
+
+    /**
+     * Set the (singleton) TTest instance.
+     *
+     * @param chiSquareTest the new instance to use
+     * @since 1.2
+     * @deprecated 2.2 will be removed in 3.0 - not compatible with use from multiple threads
+     */
+    @Deprecated
+    public static void setChiSquareTest(TTest chiSquareTest) {
+        TestUtils.tTest = chiSquareTest;
+    }
+
+    /**
+     * Return a (singleton) TTest instance.  Does not create a new instance.
+     *
+     * @return a TTest instance
+     * @deprecated 2.2 will be removed in 3.0
+     */
+    @Deprecated
+    public static TTest getTTest() {
+        return tTest;
+    }
+
+    /**
+     * Set the (singleton) ChiSquareTest instance.
+     *
+     * @param chiSquareTest the new instance to use
+     * @since 1.2
+     * @deprecated 2.2 will be removed in 3.0 - not compatible with use from multiple threads
+     */
+    @Deprecated
+    public static void setChiSquareTest(ChiSquareTest chiSquareTest) {
+        TestUtils.chiSquareTest = chiSquareTest;
+    }
+
+    /**
+     * Return a (singleton) ChiSquareTest instance.  Does not create a new instance.
+     *
+     * @return a ChiSquareTest instance
+     * @deprecated 2.2 will be removed in 3.0
+     */
+    @Deprecated
+    public static ChiSquareTest getChiSquareTest() {
+        return chiSquareTest;
+    }
+
+    /**
+     * Set the (singleton) UnknownDistributionChiSquareTest instance.
+     *
+     * @param unknownDistributionChiSquareTest the new instance to use
+     * @since 1.2
+     * @deprecated 2.2 will be removed in 3.0 - not compatible with use from multiple threads
+     */
+    @Deprecated
+    public static void setUnknownDistributionChiSquareTest(UnknownDistributionChiSquareTest unknownDistributionChiSquareTest) {
+        TestUtils.unknownDistributionChiSquareTest = unknownDistributionChiSquareTest;
+    }
+
+    /**
+     * Return a (singleton) UnknownDistributionChiSquareTest instance.  Does not create a new instance.
+     *
+     * @return a UnknownDistributionChiSquareTest instance
+     * @deprecated 2.2 will be removed in 3.0
+     */
+    @Deprecated
+    public static UnknownDistributionChiSquareTest getUnknownDistributionChiSquareTest() {
+        return unknownDistributionChiSquareTest;
+    }
+
+    /**
+     * Set the (singleton) OneWayAnova instance
+     *
+     * @param oneWayAnova the new instance to use
+     * @since 1.2
+     * @deprecated 2.2 will be removed in 3.0 - not compatible with use from multiple threads
+     */
+    @Deprecated
+    public static void setOneWayAnova(OneWayAnova oneWayAnova) {
+        TestUtils.oneWayAnova = oneWayAnova;
+    }
+
+    /**
+     * Return a (singleton) OneWayAnova instance.  Does not create a new instance.
+     *
+     * @return a OneWayAnova instance
+     * @since 1.2
+     * @deprecated 2.2 will be removed in 3.0
+     */
+    @Deprecated
+    public static OneWayAnova getOneWayAnova() {
+        return oneWayAnova;
+    }
+
+
+    // CHECKSTYLE: stop JavadocMethodCheck
+
+    /**
+     * @see org.apache.commons.math.stat.inference.TTest#homoscedasticT(double[], double[])
+     */
+    public static double homoscedasticT(double[] sample1, double[] sample2)
+        throws IllegalArgumentException {
+        return tTest.homoscedasticT(sample1, sample2);
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.inference.TTest#homoscedasticT(org.apache.commons.math.stat.descriptive.StatisticalSummary, org.apache.commons.math.stat.descriptive.StatisticalSummary)
+     */
+    public static double homoscedasticT(StatisticalSummary sampleStats1,
+        StatisticalSummary sampleStats2)
+        throws IllegalArgumentException {
+        return tTest.homoscedasticT(sampleStats1, sampleStats2);
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.inference.TTest#homoscedasticTTest(double[], double[], double)
+     */
+    public static boolean homoscedasticTTest(double[] sample1, double[] sample2,
+            double alpha)
+        throws IllegalArgumentException, MathException {
+        return tTest. homoscedasticTTest(sample1, sample2, alpha);
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.inference.TTest#homoscedasticTTest(double[], double[])
+     */
+    public static double homoscedasticTTest(double[] sample1, double[] sample2)
+        throws IllegalArgumentException, MathException {
+        return tTest.homoscedasticTTest(sample1, sample2);
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.inference.TTest#homoscedasticTTest(org.apache.commons.math.stat.descriptive.StatisticalSummary, org.apache.commons.math.stat.descriptive.StatisticalSummary)
+     */
+    public static double homoscedasticTTest(StatisticalSummary sampleStats1,
+        StatisticalSummary sampleStats2)
+        throws IllegalArgumentException, MathException {
+        return tTest.homoscedasticTTest(sampleStats1, sampleStats2);
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.inference.TTest#pairedT(double[], double[])
+     */
+    public static double pairedT(double[] sample1, double[] sample2)
+        throws IllegalArgumentException, MathException {
+        return tTest.pairedT(sample1, sample2);
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.inference.TTest#pairedTTest(double[], double[], double)
+     */
+    public static boolean pairedTTest(double[] sample1, double[] sample2,
+        double alpha)
+        throws IllegalArgumentException, MathException {
+        return tTest.pairedTTest(sample1, sample2, alpha);
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.inference.TTest#pairedTTest(double[], double[])
+     */
+    public static double pairedTTest(double[] sample1, double[] sample2)
+        throws IllegalArgumentException, MathException {
+        return tTest.pairedTTest(sample1, sample2);
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.inference.TTest#t(double, double[])
+     */
+    public static double t(double mu, double[] observed)
+        throws IllegalArgumentException {
+        return tTest.t(mu, observed);
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.inference.TTest#t(double, org.apache.commons.math.stat.descriptive.StatisticalSummary)
+     */
+    public static double t(double mu, StatisticalSummary sampleStats)
+        throws IllegalArgumentException {
+        return tTest.t(mu, sampleStats);
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.inference.TTest#t(double[], double[])
+     */
+    public static double t(double[] sample1, double[] sample2)
+        throws IllegalArgumentException {
+        return tTest.t(sample1, sample2);
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.inference.TTest#t(org.apache.commons.math.stat.descriptive.StatisticalSummary, org.apache.commons.math.stat.descriptive.StatisticalSummary)
+     */
+    public static double t(StatisticalSummary sampleStats1,
+            StatisticalSummary sampleStats2)
+        throws IllegalArgumentException {
+        return tTest.t(sampleStats1, sampleStats2);
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.inference.TTest#tTest(double, double[], double)
+     */
+    public static boolean tTest(double mu, double[] sample, double alpha)
+        throws IllegalArgumentException, MathException {
+        return tTest.tTest(mu, sample, alpha);
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.inference.TTest#tTest(double, double[])
+     */
+    public static double tTest(double mu, double[] sample)
+        throws IllegalArgumentException, MathException {
+        return tTest.tTest(mu, sample);
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.inference.TTest#tTest(double, org.apache.commons.math.stat.descriptive.StatisticalSummary, double)
+     */
+    public static boolean tTest(double mu, StatisticalSummary sampleStats,
+        double alpha)
+        throws IllegalArgumentException, MathException {
+        return tTest. tTest(mu, sampleStats, alpha);
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.inference.TTest#tTest(double, org.apache.commons.math.stat.descriptive.StatisticalSummary)
+     */
+    public static double tTest(double mu, StatisticalSummary sampleStats)
+        throws IllegalArgumentException, MathException {
+        return tTest.tTest(mu, sampleStats);
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.inference.TTest#tTest(double[], double[], double)
+     */
+    public static boolean tTest(double[] sample1, double[] sample2, double alpha)
+        throws IllegalArgumentException, MathException {
+        return tTest.tTest(sample1, sample2, alpha);
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.inference.TTest#tTest(double[], double[])
+     */
+    public static double tTest(double[] sample1, double[] sample2)
+        throws IllegalArgumentException, MathException {
+        return tTest.tTest(sample1, sample2);
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.inference.TTest#tTest(org.apache.commons.math.stat.descriptive.StatisticalSummary, org.apache.commons.math.stat.descriptive.StatisticalSummary, double)
+     */
+    public static boolean tTest(StatisticalSummary sampleStats1,
+        StatisticalSummary sampleStats2, double alpha)
+        throws IllegalArgumentException, MathException {
+        return tTest. tTest(sampleStats1, sampleStats2, alpha);
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.inference.TTest#tTest(org.apache.commons.math.stat.descriptive.StatisticalSummary, org.apache.commons.math.stat.descriptive.StatisticalSummary)
+     */
+    public static double tTest(StatisticalSummary sampleStats1,
+        StatisticalSummary sampleStats2)
+        throws IllegalArgumentException, MathException {
+        return tTest.tTest(sampleStats1, sampleStats2);
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.inference.ChiSquareTest#chiSquare(double[], long[])
+     */
+    public static double chiSquare(double[] expected, long[] observed)
+        throws IllegalArgumentException {
+        return chiSquareTest.chiSquare(expected, observed);
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.inference.ChiSquareTest#chiSquare(long[][])
+     */
+    public static double chiSquare(long[][] counts)
+        throws IllegalArgumentException {
+        return chiSquareTest.chiSquare(counts);
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.inference.ChiSquareTest#chiSquareTest(double[], long[], double)
+     */
+    public static boolean chiSquareTest(double[] expected, long[] observed,
+        double alpha)
+        throws IllegalArgumentException, MathException {
+        return chiSquareTest.chiSquareTest(expected, observed, alpha);
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.inference.ChiSquareTest#chiSquareTest(double[], long[])
+     */
+    public static double chiSquareTest(double[] expected, long[] observed)
+        throws IllegalArgumentException, MathException {
+        return chiSquareTest.chiSquareTest(expected, observed);
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.inference.ChiSquareTest#chiSquareTest(long[][], double)
+     */
+    public static boolean chiSquareTest(long[][] counts, double alpha)
+        throws IllegalArgumentException, MathException {
+        return chiSquareTest. chiSquareTest(counts, alpha);
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.inference.ChiSquareTest#chiSquareTest(long[][])
+     */
+    public static double chiSquareTest(long[][] counts)
+        throws IllegalArgumentException, MathException {
+        return chiSquareTest. chiSquareTest(counts);
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.inference.UnknownDistributionChiSquareTest#chiSquareDataSetsComparison(long[], long[])
+     *
+     * @since 1.2
+     */
+    public static double chiSquareDataSetsComparison(long[] observed1, long[] observed2)
+        throws IllegalArgumentException {
+        return unknownDistributionChiSquareTest.chiSquareDataSetsComparison(observed1, observed2);
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.inference.UnknownDistributionChiSquareTest#chiSquareTestDataSetsComparison(long[], long[])
+     *
+     * @since 1.2
+     */
+    public static double chiSquareTestDataSetsComparison(long[] observed1, long[] observed2)
+        throws IllegalArgumentException, MathException {
+        return unknownDistributionChiSquareTest.chiSquareTestDataSetsComparison(observed1, observed2);
+    }
+
+
+    /**
+     * @see org.apache.commons.math.stat.inference.UnknownDistributionChiSquareTest#chiSquareTestDataSetsComparison(long[], long[], double)
+     *
+     * @since 1.2
+     */
+    public static boolean chiSquareTestDataSetsComparison(long[] observed1, long[] observed2,
+        double alpha)
+        throws IllegalArgumentException, MathException {
+        return unknownDistributionChiSquareTest.chiSquareTestDataSetsComparison(observed1, observed2, alpha);
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.inference.OneWayAnova#anovaFValue(Collection)
+     *
+     * @since 1.2
+     */
+    public static double oneWayAnovaFValue(Collection<double[]> categoryData)
+    throws IllegalArgumentException, MathException {
+        return oneWayAnova.anovaFValue(categoryData);
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.inference.OneWayAnova#anovaPValue(Collection)
+     *
+     * @since 1.2
+     */
+    public static double oneWayAnovaPValue(Collection<double[]> categoryData)
+    throws IllegalArgumentException, MathException {
+        return oneWayAnova.anovaPValue(categoryData);
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.inference.OneWayAnova#anovaTest(Collection,double)
+     *
+     * @since 1.2
+     */
+    public static boolean oneWayAnovaTest(Collection<double[]> categoryData, double alpha)
+    throws IllegalArgumentException, MathException {
+        return oneWayAnova.anovaTest(categoryData, alpha);
+    }
+
+    // CHECKSTYLE: resume JavadocMethodCheck
+
+}
diff --git a/src/main/java/org/apache/commons/math/stat/inference/UnknownDistributionChiSquareTest.java b/src/main/java/org/apache/commons/math/stat/inference/UnknownDistributionChiSquareTest.java
new file mode 100644
index 0000000..662e4d6
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/inference/UnknownDistributionChiSquareTest.java
@@ -0,0 +1,144 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.stat.inference;
+
+import org.apache.commons.math.MathException;
+
+/**
+ * An interface for Chi-Square tests for unknown distributions.
+ * <p>Two samples tests are used when the distribution is unknown <i>a priori</i>
+ * but provided by one sample. We compare the second sample against the first.</p>
+ *
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ * @since 1.2
+ */
+public interface UnknownDistributionChiSquareTest extends ChiSquareTest {
+
+    /**
+     * <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 statistic
+     * @throws IllegalArgumentException if preconditions are not met
+     */
+    double chiSquareDataSetsComparison(long[] observed1, long[] observed2)
+        throws IllegalArgumentException;
+
+    /**
+     * <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 IllegalArgumentException if preconditions are not met
+     * @throws MathException if an error occurs computing the p-value
+     */
+    double chiSquareTestDataSetsComparison(long[] observed1, long[] observed2)
+      throws IllegalArgumentException, MathException;
+
+    /**
+     * <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 IllegalArgumentException if preconditions are not met
+     * @throws MathException if an error occurs performing the test
+     */
+    boolean chiSquareTestDataSetsComparison(long[] observed1, long[] observed2, double alpha)
+      throws IllegalArgumentException, MathException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/stat/inference/package.html b/src/main/java/org/apache/commons/math/stat/inference/package.html
new file mode 100644
index 0000000..288eebf
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/inference/package.html
@@ -0,0 +1,23 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 480440 $ $Date: 2006-11-29 08:14:12 +0100 (mer. 29 nov. 2006) $ -->
+    <body>
+      Classes providing hypothesis testing and confidence interval
+      construction.
+    </body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/stat/package.html b/src/main/java/org/apache/commons/math/stat/package.html
new file mode 100644
index 0000000..d62d67a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/package.html
@@ -0,0 +1,20 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 480440 $ $Date: 2006-11-29 08:14:12 +0100 (mer. 29 nov. 2006) $ -->
+    <body>Data storage, manipulation and summary routines.</body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/stat/ranking/NaNStrategy.java b/src/main/java/org/apache/commons/math/stat/ranking/NaNStrategy.java
new file mode 100644
index 0000000..cffa7d1
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/ranking/NaNStrategy.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.math.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>
+ * </ul>
+ *
+ * @since 2.0
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+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
+}
diff --git a/src/main/java/org/apache/commons/math/stat/ranking/NaturalRanking.java b/src/main/java/org/apache/commons/math/stat/ranking/NaturalRanking.java
new file mode 100644
index 0000000..f51189c
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/ranking/NaturalRanking.java
@@ -0,0 +1,464 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.stat.ranking;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.commons.math.exception.MathInternalError;
+import org.apache.commons.math.random.RandomData;
+import org.apache.commons.math.random.RandomDataImpl;
+import org.apache.commons.math.random.RandomGenerator;
+import org.apache.commons.math.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#MAXIMAL} 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
+ * @version $Revision: 1061496 $ $Date: 2011-01-20 21:32:16 +0100 (jeu. 20 janv. 2011) $
+ */
+public class NaturalRanking implements RankingAlgorithm {
+
+    /** default NaN strategy */
+    public static final NaNStrategy DEFAULT_NAN_STRATEGY = NaNStrategy.MAXIMAL;
+
+    /** 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 RandomData 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 RandomDataImpl();
+    }
+
+    /**
+     * 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 RandomDataImpl();
+    }
+
+    /**
+     * 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 RandomDataImpl(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 RandomDataImpl(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
+     */
+    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;
+            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()) {
+                    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
+         */
+        public 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);
+        }
+
+        /**
+         * 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/math/stat/ranking/RankingAlgorithm.java b/src/main/java/org/apache/commons/math/stat/ranking/RankingAlgorithm.java
new file mode 100644
index 0000000..b01f324
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/ranking/RankingAlgorithm.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.math.stat.ranking;
+
+/**
+ * Interface representing a rank transformation.
+ *
+ * @since 2.0
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+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/math/stat/ranking/TiesStrategy.java b/src/main/java/org/apache/commons/math/stat/ranking/TiesStrategy.java
new file mode 100644
index 0000000..794c229
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/ranking/TiesStrategy.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.math.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
+ * @version $Revision: 981332 $ $Date: 2010-08-02 00:24:31 +0200 (lun. 02 août 2010) $
+ */
+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/math/stat/ranking/package.html b/src/main/java/org/apache/commons/math/stat/ranking/package.html
new file mode 100644
index 0000000..63e0c4a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/ranking/package.html
@@ -0,0 +1,22 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision:$ $Date:$ -->
+    <body>
+      Classes providing rank transformations.
+    </body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/stat/regression/AbstractMultipleLinearRegression.java b/src/main/java/org/apache/commons/math/stat/regression/AbstractMultipleLinearRegression.java
new file mode 100644
index 0000000..9757682
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/regression/AbstractMultipleLinearRegression.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.math.stat.regression;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.linear.RealMatrix;
+import org.apache.commons.math.linear.Array2DRowRealMatrix;
+import org.apache.commons.math.linear.RealVector;
+import org.apache.commons.math.linear.ArrayRealVector;
+import org.apache.commons.math.stat.descriptive.moment.Variance;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Abstract base class for implementations of MultipleLinearRegression.
+ * @version $Revision: 1073459 $ $Date: 2011-02-22 20:18:12 +0100 (mar. 22 févr. 2011) $
+ * @since 2.0
+ */
+public abstract class AbstractMultipleLinearRegression implements
+        MultipleLinearRegression {
+
+    /** X sample data. */
+    protected RealMatrix X;
+
+    /** Y sample data. */
+    protected RealVector Y;
+
+    /** Whether or not the regression model includes an intercept.  True means no intercept. */
+    private boolean noIntercept = false;
+
+    /**
+     * @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 IllegalArgumentException if the preconditions are not met
+     */
+    public void newSampleData(double[] data, int nobs, int nvars) {
+        if (data == null) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.NULL_NOT_ALLOWED);
+        }
+        if (data.length != nobs * (nvars + 1)) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.INVALID_REGRESSION_ARRAY, data.length, nobs, nvars);
+        }
+        if (nobs <= nvars) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.NOT_ENOUGH_DATA_FOR_NUMBER_OF_PREDICTORS);
+        }
+        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.X = new Array2DRowRealMatrix(x);
+        this.Y = new ArrayRealVector(y);
+    }
+
+    /**
+     * Loads new y sample data, overriding any previous data.
+     *
+     * @param y the array representing the y sample
+     * @throws IllegalArgumentException if y is null or empty
+     */
+    protected void newYSampleData(double[] y) {
+        if (y == null) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.NULL_NOT_ALLOWED);
+        }
+        if (y.length == 0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.NO_DATA);
+        }
+        this.Y = 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 IllegalArgumentException if x is null, empty or not rectangular
+     */
+    protected void newXSampleData(double[][] x) {
+        if (x == null) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.NULL_NOT_ALLOWED);
+        }
+        if (x.length == 0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.NO_DATA);
+        }
+        if (noIntercept) {
+            this.X = 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 MathRuntimeException.createIllegalArgumentException(
+                            LocalizedFormats.DIFFERENT_ROWS_LENGTHS,
+                            x[i].length, nVars);
+                }
+                xAug[i][0] = 1.0d;
+                System.arraycopy(x[i], 0, xAug[i], 1, nVars);
+            }
+            this.X = 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 IllegalArgumentException if any of the checks fail
+     *
+     */
+    protected void validateSampleData(double[][] x, double[] y) {
+        if ((x == null) || (y == null) || (x.length != y.length)) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  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 MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.NO_DATA);
+        }
+        if (x[0].length + 1 > x.length) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  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 IllegalArgumentException if the number of rows in x is not equal
+     * to the number of rows in covariance or covariance is not square.
+     */
+    protected void validateCovarianceData(double[][] x, double[][] covariance) {
+        if (x.length != covariance.length) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                 LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, x.length, covariance.length);
+        }
+        if (covariance.length > 0 && covariance.length != covariance[0].length) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.NON_SQUARE_MATRIX,
+                  covariance.length, covariance[0].length);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public double[] estimateRegressionParameters() {
+        RealVector b = calculateBeta();
+        return b.getData();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public double[] estimateResiduals() {
+        RealVector b = calculateBeta();
+        RealVector e = Y.subtract(X.operate(b));
+        return e.getData();
+    }
+
+    /**
+     * {@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 Math.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(Y.getData());
+    }
+
+    /**
+     * <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) /
+               (X.getRowDimension() - X.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 Y.subtract(X.operate(b));
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/stat/regression/GLSMultipleLinearRegression.java b/src/main/java/org/apache/commons/math/stat/regression/GLSMultipleLinearRegression.java
new file mode 100644
index 0000000..dc6ef0d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/regression/GLSMultipleLinearRegression.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.math.stat.regression;
+
+import org.apache.commons.math.linear.LUDecompositionImpl;
+import org.apache.commons.math.linear.RealMatrix;
+import org.apache.commons.math.linear.Array2DRowRealMatrix;
+import org.apache.commons.math.linear.RealVector;
+
+/**
+ * The GLS implementation of the 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>
+ * @version $Revision: 1073460 $ $Date: 2011-02-22 20:22:39 +0100 (mar. 22 févr. 2011) $
+ * @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 LUDecompositionImpl(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 = X.transpose();
+        RealMatrix XTOIX = XT.multiply(OI).multiply(X);
+        RealMatrix inverse = new LUDecompositionImpl(XTOIX).getSolver().getInverse();
+        return inverse.multiply(XT).multiply(OI).operate(Y);
+    }
+
+    /**
+     * 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 = X.transpose().multiply(OI).multiply(X);
+        return new LUDecompositionImpl(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 / (X.getRowDimension() - X.getColumnDimension());
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/stat/regression/MultipleLinearRegression.java b/src/main/java/org/apache/commons/math/stat/regression/MultipleLinearRegression.java
new file mode 100644
index 0000000..b7aabd4
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/regression/MultipleLinearRegression.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.math.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>.
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ * @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/math/stat/regression/OLSMultipleLinearRegression.java b/src/main/java/org/apache/commons/math/stat/regression/OLSMultipleLinearRegression.java
new file mode 100644
index 0000000..22a59e8
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/regression/OLSMultipleLinearRegression.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.math.stat.regression;
+
+import org.apache.commons.math.linear.Array2DRowRealMatrix;
+import org.apache.commons.math.linear.LUDecompositionImpl;
+import org.apache.commons.math.linear.QRDecomposition;
+import org.apache.commons.math.linear.QRDecompositionImpl;
+import org.apache.commons.math.linear.RealMatrix;
+import org.apache.commons.math.linear.RealVector;
+import org.apache.commons.math.stat.StatUtils;
+import org.apache.commons.math.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 QRDecompositionImpl} 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>
+ *
+ * @version $Revision: 1073464 $ $Date: 2011-02-22 20:35:02 +0100 (mar. 22 févr. 2011) $
+ * @since 2.0
+ */
+public class OLSMultipleLinearRegression extends AbstractMultipleLinearRegression {
+
+    /** Cached QR decomposition of X matrix */
+    private QRDecomposition qr = null;
+
+    /**
+     * 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 IllegalArgumentException if the x and y array data are not
+     *             compatible for the regression
+     */
+    public void newSampleData(double[] y, double[][] x) {
+        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 QRDecompositionImpl(X);
+    }
+
+    /**
+     * <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.
+     *
+     * @return the hat matrix
+     */
+    public RealMatrix calculateHat() {
+        // Create augmented identity matrix
+        RealMatrix Q = qr.getQ();
+        final int p = qr.getR().getColumnDimension();
+        final int n = Q.getColumnDimension();
+        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
+        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
+     * @see #isNoIntercept()
+     * @since 2.2
+     */
+    public double calculateTotalSumOfSquares() {
+        if (isNoIntercept()) {
+            return StatUtils.sumSq(Y.getData());
+        } else {
+            return new SecondMoment().evaluate(Y.getData());
+        }
+    }
+
+    /**
+     * Returns the sum of squared residuals.
+     *
+     * @return residual sum of squares
+     * @since 2.2
+     */
+    public double calculateResidualSumOfSquares() {
+        final RealVector residuals = calculateResiduals();
+        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}
+     *
+     * @return R-square statistic
+     * @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>
+     *
+     * @return adjusted R-Squared statistic
+     * @see #isNoIntercept()
+     * @since 2.2
+     */
+    public double calculateAdjustedRSquared() {
+        final double n = X.getRowDimension();
+        if (isNoIntercept()) {
+            return 1 - (1 - calculateRSquared()) * (n / (n - X.getColumnDimension()));
+        } else {
+            return 1 - (calculateResidualSumOfSquares() * (n - 1)) /
+                (calculateTotalSumOfSquares() * (n - X.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 QRDecompositionImpl(X);
+    }
+
+    /**
+     * Calculates the regression coefficients using OLS.
+     *
+     * @return beta
+     */
+    @Override
+    protected RealVector calculateBeta() {
+        return qr.getSolver().solve(Y);
+    }
+
+    /**
+     * <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>
+     *
+     * @return The beta variance-covariance matrix
+     */
+    @Override
+    protected RealMatrix calculateBetaVariance() {
+        int p = X.getColumnDimension();
+        RealMatrix Raug = qr.getR().getSubMatrix(0, p - 1 , 0, p - 1);
+        RealMatrix Rinv = new LUDecompositionImpl(Raug).getSolver().getInverse();
+        return Rinv.multiply(Rinv.transpose());
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/stat/regression/SimpleRegression.java b/src/main/java/org/apache/commons/math/stat/regression/SimpleRegression.java
new file mode 100644
index 0000000..d950541
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/regression/SimpleRegression.java
@@ -0,0 +1,639 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.stat.regression;
+import java.io.Serializable;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.distribution.TDistribution;
+import org.apache.commons.math.distribution.TDistributionImpl;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * 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 requred 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>
+ * </ul></p>
+ *
+ * @version $Revision: 1042336 $ $Date: 2010-12-05 13:40:48 +0100 (dim. 05 déc. 2010) $
+ */
+public class SimpleRegression implements Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -3004689053607543335L;
+
+    /** the distribution used to compute inference statistics. */
+    private TDistribution distribution;
+
+    /** 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;
+
+    // ---------------------Public methods--------------------------------------
+
+    /**
+     * Create an empty SimpleRegression instance
+     */
+    public SimpleRegression() {
+        this(new TDistributionImpl(1.0));
+    }
+
+    /**
+     * Create an empty SimpleRegression using the given distribution object to
+     * compute inference statistics.
+     * @param t the distribution used to compute inference statistics.
+     * @since 1.2
+     * @deprecated in 2.2 (to be removed in 3.0). Please use the {@link
+     * #SimpleRegression(int) other constructor} instead.
+     */
+    @Deprecated
+    public SimpleRegression(TDistribution t) {
+        super();
+        setDistribution(t);
+    }
+
+    /**
+     * Create an empty SimpleRegression.
+     *
+     * @param degrees Number of degrees of freedom of the distribution
+     * used to compute inference statistics.
+     * @since 2.2
+     */
+    public SimpleRegression(int degrees) {
+        setDistribution(new TDistributionImpl(degrees));
+    }
+
+    /**
+     * 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(double x, double y) {
+        if (n == 0) {
+            xbar = x;
+            ybar = y;
+        } else {
+            double dx = x - xbar;
+            double dy = y - ybar;
+            sumXX += dx * dx * (double) n / (n + 1d);
+            sumYY += dy * dy * (double) n / (n + 1d);
+            sumXY += dx * dy * (double) n / (n + 1d);
+            xbar += dx / (n + 1.0);
+            ybar += dy / (n + 1.0);
+        }
+        sumX += x;
+        sumY += y;
+        n++;
+
+        if (n > 2) {
+            distribution.setDegreesOfFreedom(n - 2);
+        }
+    }
+
+
+    /**
+     * 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(double x, double y) {
+        if (n > 0) {
+            double dx = x - xbar;
+            double dy = y - ybar;
+            sumXX -= dx * dx * (double) n / (n - 1d);
+            sumYY -= dy * dy * (double) n / (n - 1d);
+            sumXY -= dx * dy * (double) n / (n - 1d);
+            xbar -= dx / (n - 1.0);
+            ybar -= dy / (n - 1.0);
+            sumX -= x;
+            sumY -= y;
+            n--;
+
+            if (n > 2) {
+                distribution.setDegreesOfFreedom(n - 2);
+            }
+        }
+    }
+
+    /**
+     * 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
+     */
+    public void addData(double[][] data) {
+        for (int i = 0; i < data.length; i++) {
+            addData(data[i][0], data[i][1]);
+        }
+    }
+
+
+    /**
+     * 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(double x) {
+        double b1 = getSlope();
+        return getIntercept(b1) + b1 * x;
+    }
+
+    /**
+     * Returns the intercept of the estimated regression line.
+     * <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
+     */
+    public double getIntercept() {
+        return getIntercept(getSlope());
+    }
+
+    /**
+    * 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 getSumSquaredErrors() / (n - 2);
+    }
+
+    /**
+     * 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>
+     *
+     * @return standard error associated with intercept estimate
+     */
+    public double getInterceptStdErr() {
+        return FastMath.sqrt(
+            getMeanSquareError() * ((1d / (double) 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 MathException if the confidence interval can not be computed.
+     */
+    public double getSlopeConfidenceInterval() throws MathException {
+        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>IllegalArgumentException</code> is thrown.
+     * </li></ul></p>
+     *
+     * @param alpha the desired significance level
+     * @return half-width of 95% confidence interval for the slope estimate
+     * @throws MathException if the confidence interval can not be computed.
+     */
+    public double getSlopeConfidenceInterval(double alpha)
+        throws MathException {
+        if (alpha >= 1 || alpha <= 0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.OUT_OF_BOUND_SIGNIFICANCE_LEVEL,
+                  alpha, 0.0, 1.0);
+        }
+        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 MathException if the significance level can not be computed.
+     */
+    public double getSignificance() throws MathException {
+        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(double slope) {
+        return (sumY - slope * sumX) / n;
+    }
+
+    /**
+     * Computes SSR from b1.
+     *
+     * @param slope regression slope estimate
+     * @return sum of squared deviations of predicted y values
+     */
+    private double getRegressionSumSquares(double slope) {
+        return slope * slope * sumXX;
+    }
+
+    /**
+     * Modify the distribution used to compute inference statistics.
+     * @param value the new distribution
+     * @since 1.2
+     * @deprecated in 2.2 (to be removed in 3.0).
+     */
+    @Deprecated
+    public void setDistribution(TDistribution value) {
+        distribution = value;
+
+        // modify degrees of freedom
+        if (n > 2) {
+            distribution.setDegreesOfFreedom(n - 2);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/stat/regression/package.html b/src/main/java/org/apache/commons/math/stat/regression/package.html
new file mode 100644
index 0000000..2538c6e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/stat/regression/package.html
@@ -0,0 +1,22 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 480440 $ $Date: 2006-11-29 08:14:12 +0100 (mer. 29 nov. 2006) $ -->
+    <body>
+      Statistical routines involving multivariate data.
+    </body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/transform/FastCosineTransformer.java b/src/main/java/org/apache/commons/math/transform/FastCosineTransformer.java
new file mode 100644
index 0000000..bda0fe2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/transform/FastCosineTransformer.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.math.transform;
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.complex.Complex;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Implements the <a href="http://documents.wolfram.com/v5/Add-onsLinks/
+ * StandardPackages/LinearAlgebra/FourierTrig.html">Fast Cosine Transform</a>
+ * for transformation of one-dimensional data sets. For reference, see
+ * <b>Fast Fourier Transforms</b>, ISBN 0849371635, chapter 3.
+ * <p>
+ * FCT is its own inverse, up to a multiplier depending on conventions.
+ * The equations are listed in the comments of the corresponding methods.</p>
+ * <p>
+ * Different from FFT and FST, FCT requires the length of data set to be
+ * power of 2 plus one. Users should especially pay attention to the
+ * function transformation on how this affects the sampling.</p>
+ * <p>As of version 2.0 this no longer implements Serializable</p>
+ *
+ * @version $Revision:670469 $ $Date:2008-06-23 10:01:38 +0200 (lun., 23 juin 2008) $
+ * @since 1.2
+ */
+public class FastCosineTransformer implements RealTransformer {
+
+    /**
+     * Construct a default transformer.
+     */
+    public FastCosineTransformer() {
+        super();
+    }
+
+    /**
+     * Transform the given real data set.
+     * <p>
+     * The formula is F<sub>n</sub> = (1/2) [f<sub>0</sub> + (-1)<sup>n</sup> f<sub>N</sub>] +
+     *                        &sum;<sub>k=1</sub><sup>N-1</sup> f<sub>k</sub> cos(&pi; nk/N)
+     * </p>
+     *
+     * @param f the real data array to be transformed
+     * @return the real transformed array
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    public double[] transform(double f[]) throws IllegalArgumentException {
+        return fct(f);
+    }
+
+    /**
+     * Transform the given real function, sampled on the given interval.
+     * <p>
+     * The formula is F<sub>n</sub> = (1/2) [f<sub>0</sub> + (-1)<sup>n</sup> f<sub>N</sub>] +
+     *                        &sum;<sub>k=1</sub><sup>N-1</sup> f<sub>k</sub> cos(&pi; nk/N)
+     * </p>
+     *
+     * @param f the function to be sampled and transformed
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @param n the number of sample points
+     * @return the real transformed array
+     * @throws FunctionEvaluationException if function cannot be evaluated
+     * at some point
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    public double[] transform(UnivariateRealFunction f,
+                              double min, double max, int n)
+        throws FunctionEvaluationException, IllegalArgumentException {
+        double data[] = FastFourierTransformer.sample(f, min, max, n);
+        return fct(data);
+    }
+
+    /**
+     * Transform the given real data set.
+     * <p>
+     * The formula is F<sub>n</sub> = &radic;(1/2N) [f<sub>0</sub> + (-1)<sup>n</sup> f<sub>N</sub>] +
+     *                        &radic;(2/N) &sum;<sub>k=1</sub><sup>N-1</sup> f<sub>k</sub> cos(&pi; nk/N)
+     * </p>
+     *
+     * @param f the real data array to be transformed
+     * @return the real transformed array
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    public double[] transform2(double f[]) throws IllegalArgumentException {
+
+        double scaling_coefficient = FastMath.sqrt(2.0 / (f.length-1));
+        return FastFourierTransformer.scaleArray(fct(f), scaling_coefficient);
+    }
+
+    /**
+     * Transform the given real function, sampled on the given interval.
+     * <p>
+     * The formula is F<sub>n</sub> = &radic;(1/2N) [f<sub>0</sub> + (-1)<sup>n</sup> f<sub>N</sub>] +
+     *                        &radic;(2/N) &sum;<sub>k=1</sub><sup>N-1</sup> f<sub>k</sub> cos(&pi; nk/N)
+     *
+     * </p>
+     *
+     * @param f the function to be sampled and transformed
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @param n the number of sample points
+     * @return the real transformed array
+     * @throws FunctionEvaluationException if function cannot be evaluated
+     * at some point
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    public double[] transform2(UnivariateRealFunction f,
+                               double min, double max, int n)
+        throws FunctionEvaluationException, IllegalArgumentException {
+
+        double data[] = FastFourierTransformer.sample(f, min, max, n);
+        double scaling_coefficient = FastMath.sqrt(2.0 / (n-1));
+        return FastFourierTransformer.scaleArray(fct(data), scaling_coefficient);
+    }
+
+    /**
+     * Inversely transform the given real data set.
+     * <p>
+     * The formula is f<sub>k</sub> = (1/N) [F<sub>0</sub> + (-1)<sup>k</sup> F<sub>N</sub>] +
+     *                        (2/N) &sum;<sub>n=1</sub><sup>N-1</sup> F<sub>n</sub> cos(&pi; nk/N)
+     * </p>
+     *
+     * @param f the real data array to be inversely transformed
+     * @return the real inversely transformed array
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    public double[] inversetransform(double f[]) throws IllegalArgumentException {
+
+        double scaling_coefficient = 2.0 / (f.length - 1);
+        return FastFourierTransformer.scaleArray(fct(f), scaling_coefficient);
+    }
+
+    /**
+     * Inversely transform the given real function, sampled on the given interval.
+     * <p>
+     * The formula is f<sub>k</sub> = (1/N) [F<sub>0</sub> + (-1)<sup>k</sup> F<sub>N</sub>] +
+     *                        (2/N) &sum;<sub>n=1</sub><sup>N-1</sup> F<sub>n</sub> cos(&pi; nk/N)
+     * </p>
+     *
+     * @param f the function to be sampled and inversely transformed
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @param n the number of sample points
+     * @return the real inversely transformed array
+     * @throws FunctionEvaluationException if function cannot be evaluated at some point
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    public double[] inversetransform(UnivariateRealFunction f,
+                                     double min, double max, int n)
+        throws FunctionEvaluationException, IllegalArgumentException {
+
+        double data[] = FastFourierTransformer.sample(f, min, max, n);
+        double scaling_coefficient = 2.0 / (n - 1);
+        return FastFourierTransformer.scaleArray(fct(data), scaling_coefficient);
+    }
+
+    /**
+     * Inversely transform the given real data set.
+     * <p>
+     * The formula is f<sub>k</sub> = &radic;(1/2N) [F<sub>0</sub> + (-1)<sup>k</sup> F<sub>N</sub>] +
+     *                        &radic;(2/N) &sum;<sub>n=1</sub><sup>N-1</sup> F<sub>n</sub> cos(&pi; nk/N)
+     * </p>
+     *
+     * @param f the real data array to be inversely transformed
+     * @return the real inversely transformed array
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    public double[] inversetransform2(double f[]) throws IllegalArgumentException {
+        return transform2(f);
+    }
+
+    /**
+     * Inversely transform the given real function, sampled on the given interval.
+     * <p>
+     * The formula is f<sub>k</sub> = &radic;(1/2N) [F<sub>0</sub> + (-1)<sup>k</sup> F<sub>N</sub>] +
+     *                        &radic;(2/N) &sum;<sub>n=1</sub><sup>N-1</sup> F<sub>n</sub> cos(&pi; nk/N)
+     * </p>
+     *
+     * @param f the function to be sampled and inversely transformed
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @param n the number of sample points
+     * @return the real inversely transformed array
+     * @throws FunctionEvaluationException if function cannot be evaluated at some point
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    public double[] inversetransform2(UnivariateRealFunction f,
+                                      double min, double max, int n)
+        throws FunctionEvaluationException, IllegalArgumentException {
+
+        return transform2(f, min, max, n);
+    }
+
+    /**
+     * Perform the FCT algorithm (including inverse).
+     *
+     * @param f the real data array to be transformed
+     * @return the real transformed array
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    protected double[] fct(double f[])
+        throws IllegalArgumentException {
+
+        final double transformed[] = new double[f.length];
+
+        final int n = f.length - 1;
+        if (!FastFourierTransformer.isPowerOf2(n)) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.NOT_POWER_OF_TWO_PLUS_ONE,
+                    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];
+        double t1 = 0.5 * (f[0] - f[n]);   // temporary variable for transformed[1]
+        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 = new FastFourierTransformer();
+        Complex y[] = transformer.transform(x);
+
+        // 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/math/transform/FastFourierTransformer.java b/src/main/java/org/apache/commons/math/transform/FastFourierTransformer.java
new file mode 100644
index 0000000..e6ced5e
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/transform/FastFourierTransformer.java
@@ -0,0 +1,912 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.transform;
+
+import java.io.Serializable;
+import java.lang.reflect.Array;
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.complex.Complex;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Implements the <a href="http://mathworld.wolfram.com/FastFourierTransform.html">
+ * Fast Fourier Transform</a> for transformation of one-dimensional data sets.
+ * For reference, see <b>Applied Numerical Linear Algebra</b>, ISBN 0898713897,
+ * chapter 6.
+ * <p>
+ * There are several conventions for the definition of FFT and inverse FFT,
+ * mainly on different coefficient and exponent. Here the equations are listed
+ * in the comments of the corresponding methods.</p>
+ * <p>
+ * We require the length of data set to be 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.</p>
+ *
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ * @since 1.2
+ */
+public class FastFourierTransformer implements Serializable {
+
+    /** Serializable version identifier. */
+    static final long serialVersionUID = 5138259215438106000L;
+
+    /** roots of unity */
+    private RootsOfUnity roots = new RootsOfUnity();
+
+    /**
+     * Construct a default transformer.
+     */
+    public FastFourierTransformer() {
+        super();
+    }
+
+    /**
+     * Transform the given real data set.
+     * <p>
+     * The formula is $ y_n = \Sigma_{k=0}^{N-1} e^{-2 \pi i nk/N} x_k $
+     * </p>
+     *
+     * @param f the real data array to be transformed
+     * @return the complex transformed array
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    public Complex[] transform(double f[])
+        throws IllegalArgumentException {
+        return fft(f, false);
+    }
+
+    /**
+     * Transform the given real function, sampled on the given interval.
+     * <p>
+     * The formula is $ y_n = \Sigma_{k=0}^{N-1} e^{-2 \pi i nk/N} x_k $
+     * </p>
+     *
+     * @param f the function to be sampled and transformed
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @param n the number of sample points
+     * @return the complex transformed array
+     * @throws FunctionEvaluationException if function cannot be evaluated
+     * at some point
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    public Complex[] transform(UnivariateRealFunction f,
+                               double min, double max, int n)
+        throws FunctionEvaluationException, IllegalArgumentException {
+        double data[] = sample(f, min, max, n);
+        return fft(data, false);
+    }
+
+    /**
+     * Transform the given complex data set.
+     * <p>
+     * The formula is $ y_n = \Sigma_{k=0}^{N-1} e^{-2 \pi i nk/N} x_k $
+     * </p>
+     *
+     * @param f the complex data array to be transformed
+     * @return the complex transformed array
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    public Complex[] transform(Complex f[])
+        throws IllegalArgumentException {
+        roots.computeOmega(f.length);
+        return fft(f);
+    }
+
+    /**
+     * Transform the given real data set.
+     * <p>
+     * The formula is $y_n = (1/\sqrt{N}) \Sigma_{k=0}^{N-1} e^{-2 \pi i nk/N} x_k$
+     * </p>
+     *
+     * @param f the real data array to be transformed
+     * @return the complex transformed array
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    public Complex[] transform2(double f[])
+        throws IllegalArgumentException {
+
+        double scaling_coefficient = 1.0 / FastMath.sqrt(f.length);
+        return scaleArray(fft(f, false), scaling_coefficient);
+    }
+
+    /**
+     * Transform the given real function, sampled on the given interval.
+     * <p>
+     * The formula is $y_n = (1/\sqrt{N}) \Sigma_{k=0}^{N-1} e^{-2 \pi i nk/N} x_k$
+     * </p>
+     *
+     * @param f the function to be sampled and transformed
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @param n the number of sample points
+     * @return the complex transformed array
+     * @throws FunctionEvaluationException if function cannot be evaluated
+     * at some point
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    public Complex[] transform2(UnivariateRealFunction f,
+                                double min, double max, int n)
+        throws FunctionEvaluationException, IllegalArgumentException {
+
+        double data[] = sample(f, min, max, n);
+        double scaling_coefficient = 1.0 / FastMath.sqrt(n);
+        return scaleArray(fft(data, false), scaling_coefficient);
+    }
+
+    /**
+     * Transform the given complex data set.
+     * <p>
+     * The formula is $y_n = (1/\sqrt{N}) \Sigma_{k=0}^{N-1} e^{-2 \pi i nk/N} x_k$
+     * </p>
+     *
+     * @param f the complex data array to be transformed
+     * @return the complex transformed array
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    public Complex[] transform2(Complex f[])
+        throws IllegalArgumentException {
+
+        roots.computeOmega(f.length);
+        double scaling_coefficient = 1.0 / FastMath.sqrt(f.length);
+        return scaleArray(fft(f), scaling_coefficient);
+    }
+
+    /**
+     * Inversely transform the given real data set.
+     * <p>
+     * The formula is $ x_k = (1/N) \Sigma_{n=0}^{N-1} e^{2 \pi i nk/N} y_n $
+     * </p>
+     *
+     * @param f the real data array to be inversely transformed
+     * @return the complex inversely transformed array
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    public Complex[] inversetransform(double f[])
+        throws IllegalArgumentException {
+
+        double scaling_coefficient = 1.0 / f.length;
+        return scaleArray(fft(f, true), scaling_coefficient);
+    }
+
+    /**
+     * Inversely transform the given real function, sampled on the given interval.
+     * <p>
+     * The formula is $ x_k = (1/N) \Sigma_{n=0}^{N-1} e^{2 \pi i nk/N} y_n $
+     * </p>
+     *
+     * @param f the function to be sampled and inversely transformed
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @param n the number of sample points
+     * @return the complex inversely transformed array
+     * @throws FunctionEvaluationException if function cannot be evaluated
+     * at some point
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    public Complex[] inversetransform(UnivariateRealFunction f,
+                                      double min, double max, int n)
+        throws FunctionEvaluationException, IllegalArgumentException {
+
+        double data[] = sample(f, min, max, n);
+        double scaling_coefficient = 1.0 / n;
+        return scaleArray(fft(data, true), scaling_coefficient);
+    }
+
+    /**
+     * Inversely transform the given complex data set.
+     * <p>
+     * The formula is $ x_k = (1/N) \Sigma_{n=0}^{N-1} e^{2 \pi i nk/N} y_n $
+     * </p>
+     *
+     * @param f the complex data array to be inversely transformed
+     * @return the complex inversely transformed array
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    public Complex[] inversetransform(Complex f[])
+        throws IllegalArgumentException {
+
+        roots.computeOmega(-f.length);    // pass negative argument
+        double scaling_coefficient = 1.0 / f.length;
+        return scaleArray(fft(f), scaling_coefficient);
+    }
+
+    /**
+     * Inversely transform the given real data set.
+     * <p>
+     * The formula is $x_k = (1/\sqrt{N}) \Sigma_{n=0}^{N-1} e^{2 \pi i nk/N} y_n$
+     * </p>
+     *
+     * @param f the real data array to be inversely transformed
+     * @return the complex inversely transformed array
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    public Complex[] inversetransform2(double f[])
+        throws IllegalArgumentException {
+
+        double scaling_coefficient = 1.0 / FastMath.sqrt(f.length);
+        return scaleArray(fft(f, true), scaling_coefficient);
+    }
+
+    /**
+     * Inversely transform the given real function, sampled on the given interval.
+     * <p>
+     * The formula is $x_k = (1/\sqrt{N}) \Sigma_{n=0}^{N-1} e^{2 \pi i nk/N} y_n$
+     * </p>
+     *
+     * @param f the function to be sampled and inversely transformed
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @param n the number of sample points
+     * @return the complex inversely transformed array
+     * @throws FunctionEvaluationException if function cannot be evaluated
+     * at some point
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    public Complex[] inversetransform2(UnivariateRealFunction f,
+                                       double min, double max, int n)
+        throws FunctionEvaluationException, IllegalArgumentException {
+
+        double data[] = sample(f, min, max, n);
+        double scaling_coefficient = 1.0 / FastMath.sqrt(n);
+        return scaleArray(fft(data, true), scaling_coefficient);
+    }
+
+    /**
+     * Inversely transform the given complex data set.
+     * <p>
+     * The formula is $x_k = (1/\sqrt{N}) \Sigma_{n=0}^{N-1} e^{2 \pi i nk/N} y_n$
+     * </p>
+     *
+     * @param f the complex data array to be inversely transformed
+     * @return the complex inversely transformed array
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    public Complex[] inversetransform2(Complex f[])
+        throws IllegalArgumentException {
+
+        roots.computeOmega(-f.length);    // pass negative argument
+        double scaling_coefficient = 1.0 / FastMath.sqrt(f.length);
+        return scaleArray(fft(f), scaling_coefficient);
+    }
+
+    /**
+     * Perform the base-4 Cooley-Tukey FFT algorithm (including inverse).
+     *
+     * @param f the real data array to be transformed
+     * @param isInverse the indicator of forward or inverse transform
+     * @return the complex transformed array
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    protected Complex[] fft(double f[], boolean isInverse)
+        throws IllegalArgumentException {
+
+        verifyDataSet(f);
+        Complex F[] = new Complex[f.length];
+        if (f.length == 1) {
+            F[0] = new Complex(f[0], 0.0);
+            return F;
+        }
+
+        // Rather than the naive real to complex conversion, pack 2N
+        // real numbers into N complex numbers for better performance.
+        int N = f.length >> 1;
+        Complex c[] = new Complex[N];
+        for (int i = 0; i < N; i++) {
+            c[i] = new Complex(f[2*i], f[2*i+1]);
+        }
+        roots.computeOmega(isInverse ? -N : N);
+        Complex z[] = fft(c);
+
+        // reconstruct the FFT result for the original array
+        roots.computeOmega(isInverse ? -2*N : 2*N);
+        F[0] = new Complex(2 * (z[0].getReal() + z[0].getImaginary()), 0.0);
+        F[N] = new Complex(2 * (z[0].getReal() - z[0].getImaginary()), 0.0);
+        for (int i = 1; i < N; i++) {
+            Complex A = z[N-i].conjugate();
+            Complex B = z[i].add(A);
+            Complex C = z[i].subtract(A);
+            //Complex D = roots.getOmega(i).multiply(Complex.I);
+            Complex D = new Complex(-roots.getOmegaImaginary(i),
+                                    roots.getOmegaReal(i));
+            F[i] = B.subtract(C.multiply(D));
+            F[2*N-i] = F[i].conjugate();
+        }
+
+        return scaleArray(F, 0.5);
+    }
+
+    /**
+     * Perform the base-4 Cooley-Tukey FFT algorithm (including inverse).
+     *
+     * @param data the complex data array to be transformed
+     * @return the complex transformed array
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    protected Complex[] fft(Complex data[])
+        throws IllegalArgumentException {
+
+        final int n = data.length;
+        final Complex f[] = new Complex[n];
+
+        // initial simple cases
+        verifyDataSet(data);
+        if (n == 1) {
+            f[0] = data[0];
+            return f;
+        }
+        if (n == 2) {
+            f[0] = data[0].add(data[1]);
+            f[1] = data[0].subtract(data[1]);
+            return f;
+        }
+
+        // permute original data array in bit-reversal order
+        int ii = 0;
+        for (int i = 0; i < n; i++) {
+            f[i] = data[ii];
+            int k = n >> 1;
+            while (ii >= k && k > 0) {
+                ii -= k; k >>= 1;
+            }
+            ii += k;
+        }
+
+        // the bottom base-4 round
+        for (int i = 0; i < n; i += 4) {
+            final Complex a = f[i].add(f[i+1]);
+            final Complex b = f[i+2].add(f[i+3]);
+            final Complex c = f[i].subtract(f[i+1]);
+            final Complex d = f[i+2].subtract(f[i+3]);
+            final Complex e1 = c.add(d.multiply(Complex.I));
+            final Complex e2 = c.subtract(d.multiply(Complex.I));
+            f[i] = a.add(b);
+            f[i+2] = a.subtract(b);
+            // omegaCount indicates forward or inverse transform
+            f[i+1] = roots.isForward() ? e2 : e1;
+            f[i+3] = roots.isForward() ? e1 : e2;
+        }
+
+        // iterations from bottom to top take O(N*logN) time
+        for (int i = 4; i < n; i <<= 1) {
+            final int m = n / (i<<1);
+            for (int j = 0; j < n; j += i<<1) {
+                for (int k = 0; k < i; k++) {
+                    //z = f[i+j+k].multiply(roots.getOmega(k*m));
+                    final int k_times_m = k*m;
+                    final double omega_k_times_m_real = roots.getOmegaReal(k_times_m);
+                    final double omega_k_times_m_imaginary = roots.getOmegaImaginary(k_times_m);
+                    //z = f[i+j+k].multiply(omega[k*m]);
+                    final Complex z = new Complex(
+                        f[i+j+k].getReal() * omega_k_times_m_real -
+                        f[i+j+k].getImaginary() * omega_k_times_m_imaginary,
+                        f[i+j+k].getReal() * omega_k_times_m_imaginary +
+                        f[i+j+k].getImaginary() * omega_k_times_m_real);
+
+                    f[i+j+k] = f[j+k].subtract(z);
+                    f[j+k] = f[j+k].add(z);
+                }
+            }
+        }
+        return f;
+    }
+
+    /**
+     * Sample the given univariate real function on the given interval.
+     * <p>
+     * The interval is divided equally into N sections and sample points
+     * are taken from min to max-(max-min)/N. Usually f(x) is periodic
+     * such that f(min) = f(max) (note max is not sampled), but we don't
+     * require that.</p>
+     *
+     * @param f the function to be sampled
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @param n the number of sample points
+     * @return the samples array
+     * @throws FunctionEvaluationException if function cannot be evaluated at some point
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    public static double[] sample(UnivariateRealFunction f, double min, double max, int n)
+        throws FunctionEvaluationException, IllegalArgumentException {
+
+        if (n <= 0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.NOT_POSITIVE_NUMBER_OF_SAMPLES,
+                    n);
+        }
+        verifyInterval(min, max);
+
+        double s[] = new double[n];
+        double h = (max - min) / n;
+        for (int i = 0; i < n; i++) {
+            s[i] = f.value(min + i * h);
+        }
+        return s;
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * Returns true if the argument is power of 2.
+     *
+     * @param n the number to test
+     * @return true if the argument is power of 2
+     */
+    public static boolean isPowerOf2(long n) {
+        return (n > 0) && ((n & (n - 1)) == 0);
+    }
+
+    /**
+     * Verifies that the data set has length of power of 2.
+     *
+     * @param d the data array
+     * @throws IllegalArgumentException if array length is not power of 2
+     */
+    public static void verifyDataSet(double d[]) throws IllegalArgumentException {
+        if (!isPowerOf2(d.length)) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.NOT_POWER_OF_TWO_CONSIDER_PADDING, d.length);
+        }
+    }
+
+    /**
+     * Verifies that the data set has length of power of 2.
+     *
+     * @param o the data array
+     * @throws IllegalArgumentException if array length is not power of 2
+     */
+    public static void verifyDataSet(Object o[]) throws IllegalArgumentException {
+        if (!isPowerOf2(o.length)) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.NOT_POWER_OF_TWO_CONSIDER_PADDING, o.length);
+        }
+    }
+
+    /**
+     * Verifies that the endpoints specify an interval.
+     *
+     * @param lower lower endpoint
+     * @param upper upper endpoint
+     * @throws IllegalArgumentException if not interval
+     */
+    public static void verifyInterval(double lower, double upper)
+        throws IllegalArgumentException {
+
+        if (lower >= upper) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.ENDPOINTS_NOT_AN_INTERVAL,
+                    lower, upper);
+        }
+    }
+
+    /**
+     * Performs a multi-dimensional Fourier transform on a given array.
+     * Use {@link #inversetransform2(Complex[])} and
+     * {@link #transform2(Complex[])} 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>,
+     * n<sub>x</sub>=number of elements in dimension x,
+     * and d=total number of dimensions.
+     *
+     * @param mdca Multi-Dimensional Complex Array id est Complex[][][][]
+     * @param forward inverseTransform2 is preformed if this is false
+     * @return transform of mdca as a Multi-Dimensional Complex Array id est Complex[][][][]
+     * @throws IllegalArgumentException if any dimension is not a power of two
+     */
+    public Object mdfft(Object mdca, boolean forward)
+        throws IllegalArgumentException {
+        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, forward, i, new int[0]);
+        }
+        return mdcm.getArray();
+    }
+
+    /**
+     * Performs one dimension of a multi-dimensional Fourier transform.
+     *
+     * @param mdcm input matrix
+     * @param forward inverseTransform2 is preformed if this is false
+     * @param d index of the dimension to process
+     * @param subVector recursion subvector
+     * @throws IllegalArgumentException if any dimension is not a power of two
+     */
+    private void mdfft(MultiDimensionalComplexMatrix mdcm, boolean forward,
+                       int d, int[] subVector)
+        throws IllegalArgumentException {
+        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);
+            }
+
+            if (forward)
+                temp = transform2(temp);
+            else
+                temp = inversetransform2(temp);
+
+            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, forward, d, vector);
+            } else {
+                for (int i = 0; i < dimensionSize[subVector.length]; i++) {
+                    vector[subVector.length] = i;
+                    //further split along the next dimension
+                    mdfft(mdcm, forward, d, vector);
+                }
+            }
+        }
+        return;
+    }
+
+    /**
+     * 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.
+     */
+    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
+         */
+        public 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 IllegalArgumentException if dimensions do not match
+         */
+        public Complex get(int... vector)
+            throws IllegalArgumentException {
+            if (vector == null) {
+                if (dimensionSize.length > 0) {
+                    throw MathRuntimeException.createIllegalArgumentException(
+                            LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, 0, dimensionSize.length);
+                }
+                return null;
+            }
+            if (vector.length != dimensionSize.length) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                        LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, 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 IllegalArgumentException if dimensions do not match
+         */
+        public Complex set(Complex magnitude, int... vector)
+            throws IllegalArgumentException {
+            if (vector == null) {
+                if (dimensionSize.length > 0) {
+                    throw MathRuntimeException.createIllegalArgumentException(
+                            LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, 0, dimensionSize.length);
+                }
+                return null;
+            }
+            if (vector.length != dimensionSize.length) {
+                throw MathRuntimeException.createIllegalArgumentException(
+                        LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE, 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);
+            }
+        }
+    }
+
+
+    /** Computes the n<sup>th</sup> roots of unity.
+     * A cache of already computed values is maintained.
+     */
+    private static class RootsOfUnity implements Serializable {
+
+      /** Serializable version id. */
+      private static final long serialVersionUID = 6404784357747329667L;
+
+      /** Number of roots of unity. */
+      private int      omegaCount;
+
+      /** Real part of the roots. */
+      private double[] omegaReal;
+
+      /** Imaginary part of the roots for forward transform. */
+      private double[] omegaImaginaryForward;
+
+      /** Imaginary part of the roots for reverse transform. */
+      private double[] omegaImaginaryInverse;
+
+      /** Forward/reverse indicator. */
+      private boolean  isForward;
+
+      /**
+       * Build an engine for computing then <sup>th</sup> roots of unity
+       */
+      public RootsOfUnity() {
+
+        omegaCount = 0;
+        omegaReal = null;
+        omegaImaginaryForward = null;
+        omegaImaginaryInverse = null;
+        isForward = true;
+
+      }
+
+      /**
+       * Check if computation has been done for forward or reverse transform.
+       * @return true if computation has been done for forward transform
+       * @throws IllegalStateException if no roots of unity have been computed yet
+       */
+      public synchronized boolean isForward() throws IllegalStateException {
+
+        if (omegaCount == 0) {
+          throw MathRuntimeException.createIllegalStateException(LocalizedFormats.ROOTS_OF_UNITY_NOT_COMPUTED_YET);
+        }
+        return isForward;
+
+      }
+
+      /** Computes the n<sup>th</sup> roots of unity.
+       * <p>The computed omega[] = { 1, w, w<sup>2</sup>, ... w<sup>(n-1)</sup> } where
+       * w = exp(-2 &pi; i / n), i = &sqrt;(-1).</p>
+       * <p>Note that n is positive for
+       * forward transform and negative for inverse transform.</p>
+       * @param n number of roots of unity to compute,
+       * positive for forward transform, negative for inverse transform
+       * @throws IllegalArgumentException if n = 0
+       */
+      public synchronized void computeOmega(int n) throws IllegalArgumentException {
+
+        if (n == 0) {
+          throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.CANNOT_COMPUTE_0TH_ROOT_OF_UNITY);
+        }
+
+        isForward = n > 0;
+
+        // avoid repetitive calculations
+        final int absN = FastMath.abs(n);
+
+        if (absN == omegaCount) {
+            return;
+        }
+
+        // calculate everything from scratch, for both forward and inverse versions
+        final double t    = 2.0 * FastMath.PI / absN;
+        final double cosT = FastMath.cos(t);
+        final double sinT = FastMath.sin(t);
+        omegaReal             = new double[absN];
+        omegaImaginaryForward = new double[absN];
+        omegaImaginaryInverse = new double[absN];
+        omegaReal[0]             = 1.0;
+        omegaImaginaryForward[0] = 0.0;
+        omegaImaginaryInverse[0] = 0.0;
+        for (int i = 1; i < absN; i++) {
+          omegaReal[i] =
+            omegaReal[i-1] * cosT + omegaImaginaryForward[i-1] * sinT;
+          omegaImaginaryForward[i] =
+             omegaImaginaryForward[i-1] * cosT - omegaReal[i-1] * sinT;
+          omegaImaginaryInverse[i] = -omegaImaginaryForward[i];
+        }
+        omegaCount = absN;
+
+      }
+
+      /**
+       * Get the real part of the k<sup>th</sup> n<sup>th</sup> root of unity
+       * @param k index of the n<sup>th</sup> root of unity
+       * @return real part of the k<sup>th</sup> n<sup>th</sup> root of unity
+       * @throws IllegalStateException if no roots of unity have been computed yet
+       * @throws IllegalArgumentException if k is out of range
+       */
+      public synchronized double getOmegaReal(int k)
+        throws IllegalStateException, IllegalArgumentException {
+
+        if (omegaCount == 0) {
+            throw MathRuntimeException.createIllegalStateException(LocalizedFormats.ROOTS_OF_UNITY_NOT_COMPUTED_YET);
+        }
+        if ((k < 0) || (k >= omegaCount)) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.OUT_OF_RANGE_ROOT_OF_UNITY_INDEX, k, 0, omegaCount - 1);
+        }
+
+        return omegaReal[k];
+
+      }
+
+      /**
+       * Get the imaginary part of the k<sup>th</sup> n<sup>th</sup> root of unity
+       * @param k index of the n<sup>th</sup> root of unity
+       * @return imaginary part of the k<sup>th</sup> n<sup>th</sup> root of unity
+       * @throws IllegalStateException if no roots of unity have been computed yet
+       * @throws IllegalArgumentException if k is out of range
+       */
+      public synchronized double getOmegaImaginary(int k)
+        throws IllegalStateException, IllegalArgumentException {
+
+        if (omegaCount == 0) {
+            throw MathRuntimeException.createIllegalStateException(LocalizedFormats.ROOTS_OF_UNITY_NOT_COMPUTED_YET);
+        }
+        if ((k < 0) || (k >= omegaCount)) {
+          throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.OUT_OF_RANGE_ROOT_OF_UNITY_INDEX, k, 0, omegaCount - 1);
+        }
+
+        return isForward ? omegaImaginaryForward[k] : omegaImaginaryInverse[k];
+
+      }
+
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/transform/FastHadamardTransformer.java b/src/main/java/org/apache/commons/math/transform/FastHadamardTransformer.java
new file mode 100644
index 0000000..db79c99
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/transform/FastHadamardTransformer.java
@@ -0,0 +1,252 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.transform;
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * 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).</p>
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ * @since 2.0
+ */
+public class FastHadamardTransformer implements RealTransformer {
+
+    /** {@inheritDoc} */
+    public double[] transform(double f[])
+        throws IllegalArgumentException {
+        return fht(f);
+    }
+
+    /** {@inheritDoc} */
+    public double[] transform(UnivariateRealFunction f,
+                              double min, double max, int n)
+        throws FunctionEvaluationException, IllegalArgumentException {
+        return fht(FastFourierTransformer.sample(f, min, max, n));
+    }
+
+    /** {@inheritDoc} */
+    public double[] inversetransform(double f[])
+    throws IllegalArgumentException {
+        return FastFourierTransformer.scaleArray(fht(f), 1.0 / f.length);
+   }
+
+    /** {@inheritDoc} */
+    public double[] inversetransform(UnivariateRealFunction f,
+                                     double min, double max, int n)
+        throws FunctionEvaluationException, IllegalArgumentException {
+        final double[] unscaled =
+            fht(FastFourierTransformer.sample(f, min, max, n));
+        return FastFourierTransformer.scaleArray(unscaled, 1.0 / n);
+    }
+
+    /**
+     * Transform the given real data set.
+     * <p>The integer transform cannot be inverted directly, due to a scaling
+     * factor it may lead to double results.</p>
+     * @param f the integer data array to be transformed (signal)
+     * @return the integer transformed array (spectrum)
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    public int[] transform(int f[])
+        throws IllegalArgumentException {
+        return fht(f);
+    }
+
+    /**
+     * The FHT (Fast Hadamard Transformation) which uses only subtraction and addition.
+     * <br>
+     * Requires <b>Nlog2N = n2</b><sup>n</sup> additions.
+     * <br>
+     * <br>
+     * <b><u>Short Table of manual calculation for N=8:</u></b>
+     * <ol>
+     * <li><b>x</b> is the input vector we want to transform</li>
+     * <li><b>y</b> is the output vector which is our desired result</li>
+     * <li>a and b are just helper rows</li>
+     * </ol>
+     * <pre>
+     * <code>
+     * +----+----------+---------+----------+
+     * | <b>x</b>  |    <b>a</b>     |    <b>b</b>    |    <b>y</b>     |
+     * +----+----------+---------+----------+
+     * | x<sub>0</sub> | a<sub>0</sub>=x<sub>0</sub>+x<sub>1</sub> | b<sub>0</sub>=a<sub>0</sub>+a<sub>1</sub> | y<sub>0</sub>=b<sub>0</sub>+b<sub>1</sub> |
+     * +----+----------+---------+----------+
+     * | x<sub>1</sub> | a<sub>1</sub>=x<sub>2</sub>+x<sub>3</sub> | b<sub>0</sub>=a<sub>2</sub>+a<sub>3</sub> | y<sub>0</sub>=b<sub>2</sub>+b<sub>3</sub> |
+     * +----+----------+---------+----------+
+     * | x<sub>2</sub> | a<sub>2</sub>=x<sub>4</sub>+x<sub>5</sub> | b<sub>0</sub>=a<sub>4</sub>+a<sub>5</sub> | y<sub>0</sub>=b<sub>4</sub>+b<sub>5</sub> |
+     * +----+----------+---------+----------+
+     * | x<sub>3</sub> | a<sub>3</sub>=x<sub>6</sub>+x<sub>7</sub> | b<sub>0</sub>=a<sub>6</sub>+a<sub>7</sub> | y<sub>0</sub>=b<sub>6</sub>+b<sub>7</sub> |
+     * +----+----------+---------+----------+
+     * | x<sub>4</sub> | a<sub>0</sub>=x<sub>0</sub>-x<sub>1</sub> | b<sub>0</sub>=a<sub>0</sub>-a<sub>1</sub> | y<sub>0</sub>=b<sub>0</sub>-b<sub>1</sub> |
+     * +----+----------+---------+----------+
+     * | x<sub>5</sub> | a<sub>1</sub>=x<sub>2</sub>-x<sub>3</sub> | b<sub>0</sub>=a<sub>2</sub>-a<sub>3</sub> | y<sub>0</sub>=b<sub>2</sub>-b<sub>3</sub> |
+     * +----+----------+---------+----------+
+     * | x<sub>6</sub> | a<sub>2</sub>=x<sub>4</sub>-x<sub>5</sub> | b<sub>0</sub>=a<sub>4</sub>-a<sub>5</sub> | y<sub>0</sub>=b<sub>4</sub>-b<sub>5</sub> |
+     * +----+----------+---------+----------+
+     * | x<sub>7</sub> | a<sub>3</sub>=x<sub>6</sub>-x<sub>7</sub> | b<sub>0</sub>=a<sub>6</sub>-a<sub>7</sub> | y<sub>0</sub>=b<sub>6</sub>-b<sub>7</sub> |
+     * +----+----------+---------+----------+
+     * </code>
+     * </pre>
+     *
+     * <b><u>How it works</u></b>
+     * <ol>
+     * <li>Construct a matrix with N rows and n+1 columns<br>   <b>hadm[n+1][N]</b>
+     * <br><i>(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][m])</i></li>
+     * <li>Place the input vector <b>x[N]</b> in the first column of the matrix <b>hadm</b></li>
+     * <li>The entries of the submatrix D<sub>top</sub> are calculated as follows.
+     * <br>D<sub>top</sub> goes from entry [0][1] to [N/2-1][n+1].
+     * <br>The columns of D<sub>top</sub> are the pairwise mutually exclusive sums of the previous column
+     * </li>
+     * <li>The entries of the submatrix D<sub>bottom</sub> are calculated as follows.
+     * <br>D<sub>bottom</sub> goes from entry [N/2][1] to [N][n+1].
+     * <br>The columns of D<sub>bottom</sub> are the pairwise differences of the previous column
+     * </li>
+     * <li>How D<sub>top</sub> and D<sub>bottom</sub> you can understand best with the example for N=8 above.
+     * <li>The output vector y is now in the last column of <b>hadm</b></li>
+     * <li><i>Algorithm from: http://www.archive.chipcenter.com/dsp/DSP000517F1.html</i></li>
+     * </ol>
+     * <br>
+     * <b><u>Visually</u></b>
+     * <pre>
+     *        +--------+---+---+---+-----+---+
+     *        |   0    | 1 | 2 | 3 | ... |n+1|
+     * +------+--------+---+---+---+-----+---+
+     * |0     | x<sub>0</sub>     |       /\            |
+     * |1     | x<sub>1</sub>     |       ||            |
+     * |2     | x<sub>2</sub>     |   <= D<sub>top</sub>  =>       |
+     * |...   | ...    |       ||            |
+     * |N/2-1 | x<sub>N/2-1</sub>  |       \/            |
+     * +------+--------+---+---+---+-----+---+
+     * |N/2   | x<sub>N/2</sub>   |       /\            |
+     * |N/2+1 | x<sub>N/2+1</sub>  |       ||            |
+     * |N/2+2 | x<sub>N/2+2</sub>  |  <= D<sub>bottom</sub>  =>      | which is in the last column of the matrix
+     * |...   | ...    |       ||            |
+     * |N     | x<sub>N/2</sub>   |        \/           |
+     * +------+--------+---+---+---+-----+---+
+     * </pre>
+     *
+     * @param x input vector
+     * @return y output vector
+     * @exception IllegalArgumentException if input array is not a power of 2
+     */
+    protected double[] fht(double x[]) throws IllegalArgumentException {
+
+        // n is the row count of the input vector x
+        final int n     = x.length;
+        final int halfN = n / 2;
+
+        // n has to be of the form n = 2^p !!
+        if (!FastFourierTransformer.isPowerOf2(n)) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.NOT_POWER_OF_TWO,
+                    n);
+        }
+
+        // Instead of creating a matrix with p+1 columns and n rows
+        // we will use two single dimension arrays which we will use 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) {
+                // D<sub>top</sub>
+                // 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) {
+                // D<sub>bottom</sub>
+                // 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;
+
+    }
+    /**
+     * The FHT (Fast Hadamard Transformation) which uses only subtraction and addition.
+     * @param x input vector
+     * @return y output vector
+     * @exception IllegalArgumentException if input array is not a power of 2
+     */
+    protected int[] fht(int x[]) throws IllegalArgumentException {
+
+        // n is the row count of the input vector x
+        final int n     = x.length;
+        final int halfN = n / 2;
+
+        // n has to be of the form n = 2^p !!
+        if (!FastFourierTransformer.isPowerOf2(n)) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.NOT_POWER_OF_TWO,
+                    n);
+        }
+
+        // Instead of creating a matrix with p+1 columns and n rows
+        // we will use two single dimension arrays which we will use 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) {
+                // D<sub>top</sub>
+                // 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) {
+                // D<sub>bottom</sub>
+                // 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/math/transform/FastSineTransformer.java b/src/main/java/org/apache/commons/math/transform/FastSineTransformer.java
new file mode 100644
index 0000000..28d7fce
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/transform/FastSineTransformer.java
@@ -0,0 +1,252 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.transform;
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.complex.Complex;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Implements the <a href="http://documents.wolfram.com/v5/Add-onsLinks/
+ * StandardPackages/LinearAlgebra/FourierTrig.html">Fast Sine Transform</a>
+ * for transformation of one-dimensional data sets. For reference, see
+ * <b>Fast Fourier Transforms</b>, ISBN 0849371635, chapter 3.
+ * <p>
+ * FST is its own inverse, up to a multiplier depending on conventions.
+ * The equations are listed in the comments of the corresponding methods.</p>
+ * <p>
+ * Similar to FFT, we also require the length of data set to be power of 2.
+ * In addition, the first element must be 0 and it's enforced in function
+ * transformation after sampling.</p>
+ * <p>As of version 2.0 this no longer implements Serializable</p>
+ *
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ * @since 1.2
+ */
+public class FastSineTransformer implements RealTransformer {
+
+    /**
+     * Construct a default transformer.
+     */
+    public FastSineTransformer() {
+        super();
+    }
+
+    /**
+     * Transform the given real data set.
+     * <p>
+     * The formula is F<sub>n</sub> = &sum;<sub>k=0</sub><sup>N-1</sup> f<sub>k</sub> sin(&pi; nk/N)
+     * </p>
+     *
+     * @param f the real data array to be transformed
+     * @return the real transformed array
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    public double[] transform(double f[])
+        throws IllegalArgumentException {
+        return fst(f);
+    }
+
+    /**
+     * Transform the given real function, sampled on the given interval.
+     * <p>
+     * The formula is F<sub>n</sub> = &sum;<sub>k=0</sub><sup>N-1</sup> f<sub>k</sub> sin(&pi; nk/N)
+     * </p>
+     *
+     * @param f the function to be sampled and transformed
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @param n the number of sample points
+     * @return the real transformed array
+     * @throws FunctionEvaluationException if function cannot be evaluated
+     * at some point
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    public double[] transform(UnivariateRealFunction f,
+                              double min, double max, int n)
+        throws FunctionEvaluationException, IllegalArgumentException {
+
+        double data[] = FastFourierTransformer.sample(f, min, max, n);
+        data[0] = 0.0;
+        return fst(data);
+    }
+
+    /**
+     * Transform the given real data set.
+     * <p>
+     * The formula is F<sub>n</sub> = &radic;(2/N) &sum;<sub>k=0</sub><sup>N-1</sup> f<sub>k</sub> sin(&pi; nk/N)
+     * </p>
+     *
+     * @param f the real data array to be transformed
+     * @return the real transformed array
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    public double[] transform2(double f[]) throws IllegalArgumentException {
+
+        double scaling_coefficient = FastMath.sqrt(2.0 / f.length);
+        return FastFourierTransformer.scaleArray(fst(f), scaling_coefficient);
+    }
+
+    /**
+     * Transform the given real function, sampled on the given interval.
+     * <p>
+     * The formula is F<sub>n</sub> = &radic;(2/N) &sum;<sub>k=0</sub><sup>N-1</sup> f<sub>k</sub> sin(&pi; nk/N)
+     * </p>
+     *
+     * @param f the function to be sampled and transformed
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @param n the number of sample points
+     * @return the real transformed array
+     * @throws FunctionEvaluationException if function cannot be evaluated
+     * at some point
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    public double[] transform2(
+        UnivariateRealFunction f, double min, double max, int n)
+        throws FunctionEvaluationException, IllegalArgumentException {
+
+        double data[] = FastFourierTransformer.sample(f, min, max, n);
+        data[0] = 0.0;
+        double scaling_coefficient = FastMath.sqrt(2.0 / n);
+        return FastFourierTransformer.scaleArray(fst(data), scaling_coefficient);
+    }
+
+    /**
+     * Inversely transform the given real data set.
+     * <p>
+     * The formula is f<sub>k</sub> = (2/N) &sum;<sub>n=0</sub><sup>N-1</sup> F<sub>n</sub> sin(&pi; nk/N)
+     * </p>
+     *
+     * @param f the real data array to be inversely transformed
+     * @return the real inversely transformed array
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    public double[] inversetransform(double f[]) throws IllegalArgumentException {
+
+        double scaling_coefficient = 2.0 / f.length;
+        return FastFourierTransformer.scaleArray(fst(f), scaling_coefficient);
+    }
+
+    /**
+     * Inversely transform the given real function, sampled on the given interval.
+     * <p>
+     * The formula is f<sub>k</sub> = (2/N) &sum;<sub>n=0</sub><sup>N-1</sup> F<sub>n</sub> sin(&pi; nk/N)
+     * </p>
+     *
+     * @param f the function to be sampled and inversely transformed
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @param n the number of sample points
+     * @return the real inversely transformed array
+     * @throws FunctionEvaluationException if function cannot be evaluated at some point
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    public double[] inversetransform(UnivariateRealFunction f, double min, double max, int n)
+        throws FunctionEvaluationException, IllegalArgumentException {
+
+        double data[] = FastFourierTransformer.sample(f, min, max, n);
+        data[0] = 0.0;
+        double scaling_coefficient = 2.0 / n;
+        return FastFourierTransformer.scaleArray(fst(data), scaling_coefficient);
+    }
+
+    /**
+     * Inversely transform the given real data set.
+     * <p>
+     * The formula is f<sub>k</sub> = &radic;(2/N) &sum;<sub>n=0</sub><sup>N-1</sup> F<sub>n</sub> sin(&pi; nk/N)
+     * </p>
+     *
+     * @param f the real data array to be inversely transformed
+     * @return the real inversely transformed array
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    public double[] inversetransform2(double f[]) throws IllegalArgumentException {
+
+        return transform2(f);
+    }
+
+    /**
+     * Inversely transform the given real function, sampled on the given interval.
+     * <p>
+     * The formula is f<sub>k</sub> = &radic;(2/N) &sum;<sub>n=0</sub><sup>N-1</sup> F<sub>n</sub> sin(&pi; nk/N)
+     * </p>
+     *
+     * @param f the function to be sampled and inversely transformed
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @param n the number of sample points
+     * @return the real inversely transformed array
+     * @throws FunctionEvaluationException if function cannot be evaluated at some point
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    public double[] inversetransform2(UnivariateRealFunction f, double min, double max, int n)
+        throws FunctionEvaluationException, IllegalArgumentException {
+
+        return transform2(f, min, max, n);
+    }
+
+    /**
+     * Perform the FST algorithm (including inverse).
+     *
+     * @param f the real data array to be transformed
+     * @return the real transformed array
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    protected double[] fst(double f[]) throws IllegalArgumentException {
+
+        final double transformed[] = new double[f.length];
+
+        FastFourierTransformer.verifyDataSet(f);
+        if (f[0] != 0.0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.FIRST_ELEMENT_NOT_ZERO,
+                    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 = new FastFourierTransformer();
+        Complex y[] = transformer.transform(x);
+
+        // 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/math/transform/RealTransformer.java b/src/main/java/org/apache/commons/math/transform/RealTransformer.java
new file mode 100644
index 0000000..c91061b
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/transform/RealTransformer.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.math.transform;
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+
+/**
+ * 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.math.complex.Complex complex}
+ * results instead of real ones.
+ * </p>
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ * @since 2.0
+ */
+public interface RealTransformer  {
+
+    /**
+     * Transform the given real data set.
+     * @param f the real data array to be transformed (signal)
+     * @return the real transformed array (spectrum)
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    double[] transform(double f[])
+        throws IllegalArgumentException;
+
+    /**
+     * Transform the given real function, sampled on the given interval.
+     * @param f the function to be sampled and transformed
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @param n the number of sample points
+     * @return the real transformed array
+     * @throws FunctionEvaluationException if function cannot be evaluated at some point
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    double[] transform(UnivariateRealFunction f, double min, double max, int n)
+        throws FunctionEvaluationException, IllegalArgumentException;
+
+    /**
+     * Inversely transform the given real data set.
+     * @param f the real data array to be inversely transformed (spectrum)
+     * @return the real inversely transformed array (signal)
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    double[] inversetransform(double f[])
+        throws IllegalArgumentException;
+
+    /**
+     * Inversely transform the given real function, sampled on the given interval.
+     * @param f the function to be sampled and inversely transformed
+     * @param min the lower bound for the interval
+     * @param max the upper bound for the interval
+     * @param n the number of sample points
+     * @return the real inversely transformed array
+     * @throws FunctionEvaluationException if function cannot be evaluated at some point
+     * @throws IllegalArgumentException if any parameters are invalid
+     */
+    double[] inversetransform(UnivariateRealFunction f, double min, double max, int n)
+        throws FunctionEvaluationException, IllegalArgumentException;
+
+}
diff --git a/src/main/java/org/apache/commons/math/transform/package.html b/src/main/java/org/apache/commons/math/transform/package.html
new file mode 100644
index 0000000..7377906
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/transform/package.html
@@ -0,0 +1,22 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 618423 $ $Date: 2008-02-04 21:29:08 +0100 (lun. 04 févr. 2008) $ -->
+    <body>
+     Implementations of transform methods, including Fast Fourier transforms.
+    </body>
+</html>
diff --git a/src/main/java/org/apache/commons/math/util/BigReal.java b/src/main/java/org/apache/commons/math/util/BigReal.java
new file mode 100644
index 0000000..e7789a4
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/util/BigReal.java
@@ -0,0 +1,292 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.util;
+
+
+import java.io.Serializable;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.math.MathContext;
+import java.math.RoundingMode;
+
+import org.apache.commons.math.Field;
+import org.apache.commons.math.FieldElement;
+
+/**
+ * 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.
+ * </p>
+ * @since 2.0
+ * @version $Revision: 925812 $ $Date: 2010-03-21 16:49:31 +0100 (dim. 21 mars 2010) $
+ */
+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 divide(BigReal a) throws ArithmeticException {
+        return new BigReal(d.divide(a.d, scale, roundingMode));
+    }
+
+    /** {@inheritDoc} */
+    public BigReal multiply(BigReal a) {
+        return new BigReal(d.multiply(a.d));
+    }
+
+    /** {@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/math/util/BigRealField.java b/src/main/java/org/apache/commons/math/util/BigRealField.java
new file mode 100644
index 0000000..02361bc
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/util/BigRealField.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.math.util;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.Field;
+
+/**
+ * Representation of real numbers with arbitrary precision field.
+ * <p>
+ * This class is a singleton.
+ * </p>
+ * @see BigReal
+ * @version $Revision: 811827 $ $Date: 2009-09-06 17:32:50 +0200 (dim. 06 sept. 2009) $
+ * @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;
+    }
+
+    // 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 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/math/util/CompositeFormat.java b/src/main/java/org/apache/commons/math/util/CompositeFormat.java
new file mode 100644
index 0000000..99d18ab
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/util/CompositeFormat.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.math.util;
+
+import java.text.FieldPosition;
+import java.text.Format;
+import java.text.NumberFormat;
+import java.text.ParsePosition;
+import java.util.Locale;
+
+/**
+ * Base class for formatters of composite objects (complex numbers, vectors ...).
+ *
+ * @version $Revision: 1042376 $ $Date: 2010-12-05 16:54:55 +0100 (dim. 05 déc. 2010) $
+ */
+public abstract class CompositeFormat extends Format {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 5358685519349262494L;
+
+    /**
+     * 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 2.
+     * @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#getInstance(java.util.Locale)} with the only
+     * customizing that the maximum number of fraction digits is set to 2.
+     * @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.getInstance(locale);
+        nf.setMaximumFractionDigits(2);
+        return nf;
+    }
+
+    /**
+     * Parses <code>source</code> until a non-whitespace character is found.
+     *
+     * @param source the string to parse
+     * @param pos input/ouput parsing parameter.  On output, <code>pos</code>
+     *        holds the index of the next non-whitespace character.
+     */
+    protected 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/ouput parsing parameter.
+     * @return the first non-whitespace character.
+     */
+    protected 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/ouput parsing parameter.
+     * @return the special number.
+     */
+    private 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()) {
+            if (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/ouput parsing parameter.
+     * @return the parsed number.
+     */
+    protected 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/ouput parsing parameter.
+     * @return true if the expected string was there
+     */
+    protected 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>
+     * <li>Positive infinity is formatted as '(Infinity)'</li>
+     * <li>Negative infinity is formatted as '(-Infinity)'</li>
+     * </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.
+     */
+    protected 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/math/util/ContinuedFraction.java b/src/main/java/org/apache/commons/math/util/ContinuedFraction.java
new file mode 100644
index 0000000..80df5d8
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/util/ContinuedFraction.java
@@ -0,0 +1,208 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.util;
+
+import org.apache.commons.math.ConvergenceException;
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.MaxIterationsExceededException;
+import org.apache.commons.math.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></li>
+ * </ul>
+ * </p>
+ *
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ */
+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 MathException if the algorithm fails to converge.
+     */
+    public double evaluate(double x) throws MathException {
+        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 MathException if the algorithm fails to converge.
+     */
+    public double evaluate(double x, double epsilon) throws MathException {
+        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 MathException if the algorithm fails to converge.
+     */
+    public double evaluate(double x, int maxIterations) throws MathException {
+        return evaluate(x, DEFAULT_EPSILON, maxIterations);
+    }
+
+    /**
+     * <p>
+     * Evaluates the continued fraction at the value x.
+     * </p>
+     *
+     * <p>
+     * The implementation of this method is based on equations 14-17 of:
+     * <ul>
+     * <li>
+     *   Eric W. Weisstein. "Continued Fraction." From MathWorld--A Wolfram Web
+     *   Resource. <a target="_blank"
+     *   href="http://mathworld.wolfram.com/ContinuedFraction.html">
+     *   http://mathworld.wolfram.com/ContinuedFraction.html</a>
+     * </li>
+     * </ul>
+     * The recurrence relationship defined in those equations can result in
+     * very large intermediate results which can result in numerical overflow.
+     * As a means to combat these overflow conditions, the intermediate results
+     * are scaled whenever they threaten to become numerically unstable.</p>
+     *
+     * @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 MathException if the algorithm fails to converge.
+     */
+    public double evaluate(double x, double epsilon, int maxIterations)
+        throws MathException
+    {
+        double p0 = 1.0;
+        double p1 = getA(0, x);
+        double q0 = 0.0;
+        double q1 = 1.0;
+        double c = p1 / q1;
+        int n = 0;
+        double relativeError = Double.MAX_VALUE;
+        while (n < maxIterations && relativeError > epsilon) {
+            ++n;
+            double a = getA(n, x);
+            double b = getB(n, x);
+            double p2 = a * p1 + b * p0;
+            double q2 = a * q1 + b * q0;
+            boolean infinite = false;
+            if (Double.isInfinite(p2) || Double.isInfinite(q2)) {
+                /*
+                 * Need to scale. Try successive powers of the larger of a or b
+                 * up to 5th power. Throw ConvergenceException if one or both
+                 * of p2, q2 still overflow.
+                 */
+                double scaleFactor = 1d;
+                double lastScaleFactor = 1d;
+                final int maxPower = 5;
+                final double scale = FastMath.max(a,b);
+                if (scale <= 0) {  // Can't scale
+                    throw new ConvergenceException(
+                            LocalizedFormats.CONTINUED_FRACTION_INFINITY_DIVERGENCE,
+                             x);
+                }
+                infinite = true;
+                for (int i = 0; i < maxPower; i++) {
+                    lastScaleFactor = scaleFactor;
+                    scaleFactor *= scale;
+                    if (a != 0.0 && a > b) {
+                        p2 = p1 / lastScaleFactor + (b / scaleFactor * p0);
+                        q2 = q1 / lastScaleFactor + (b / scaleFactor * q0);
+                    } else if (b != 0) {
+                        p2 = (a / scaleFactor * p1) + p0 / lastScaleFactor;
+                        q2 = (a / scaleFactor * q1) + q0 / lastScaleFactor;
+                    }
+                    infinite = Double.isInfinite(p2) || Double.isInfinite(q2);
+                    if (!infinite) {
+                        break;
+                    }
+                }
+            }
+
+            if (infinite) {
+               // Scaling failed
+               throw new ConvergenceException(
+                 LocalizedFormats.CONTINUED_FRACTION_INFINITY_DIVERGENCE,
+                  x);
+            }
+
+            double r = p2 / q2;
+
+            if (Double.isNaN(r)) {
+                throw new ConvergenceException(
+                  LocalizedFormats.CONTINUED_FRACTION_NAN_DIVERGENCE,
+                  x);
+            }
+            relativeError = FastMath.abs(r / c - 1.0);
+
+            // prepare for next iteration
+            c = p2 / q2;
+            p0 = p1;
+            p1 = p2;
+            q0 = q1;
+            q1 = q2;
+        }
+
+        if (n >= maxIterations) {
+            throw new MaxIterationsExceededException(maxIterations,
+                LocalizedFormats.NON_CONVERGENT_CONTINUED_FRACTION,
+                x);
+        }
+
+        return c;
+    }
+}
diff --git a/src/main/java/org/apache/commons/math/util/DefaultTransformer.java b/src/main/java/org/apache/commons/math/util/DefaultTransformer.java
new file mode 100644
index 0000000..e4579b2
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/util/DefaultTransformer.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.math.util;
+
+import java.io.Serializable;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * 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.
+ *
+ * @version $Revision: 1073658 $ $Date: 2011-02-23 10:45:42 +0100 (mer. 23 févr. 2011) $
+ */
+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 MathException if it 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 MathException {
+        if (o == null) {
+            throw new MathException(LocalizedFormats.OBJECT_TRANSFORMATION);
+        }
+
+        if (o instanceof Number) {
+            return ((Number)o).doubleValue();
+        }
+
+        try {
+            return Double.valueOf(o.toString()).doubleValue();
+        } catch (NumberFormatException e) {
+            throw new MathException(e,
+                                    LocalizedFormats.CANNOT_TRANSFORM_TO_DOUBLE, e.getMessage());
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public boolean equals(Object other) {
+        if (this == other) {
+            return true;
+        }
+        if (other == null) {
+            return false;
+        }
+        return other instanceof DefaultTransformer;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int hashCode() {
+        // some arbitrary number ...
+        return 401993047;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/util/DoubleArray.java b/src/main/java/org/apache/commons/math/util/DoubleArray.java
new file mode 100644
index 0000000..c213b84
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/util/DoubleArray.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.math.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".
+ *
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+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);
+
+    /**
+     * <p>
+     * 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>
+     * <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.
+     * </p>
+     *
+     * @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/math/util/FastMath.java b/src/main/java/org/apache/commons/math/util/FastMath.java
new file mode 100644
index 0000000..1907b32
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/util/FastMath.java
@@ -0,0 +1,4047 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.util;
+
+/**
+ * Faster, more accurate, portable alternative to {@link StrictMath}.
+ * <p>
+ * Additionally implements the following methods not found in StrictMath:
+ * <ul>
+ * <li>{@link #asinh(double)}</li>
+ * <li>{@link #acosh(double)}</li>
+ * <li>{@link #atanh(double)}</li>
+ * </ul>
+ * The following methods are found in StrictMath since 1.6 only
+ * <ul>
+ * <li>{@link #copySign(double, double)}</li>
+ * <li>{@link #getExponent(double)}</li>
+ * <li>{@link #nextAfter(double,double)}</li>
+ * <li>{@link #nextUp(double)}</li>
+ * <li>{@link #scalb(double, int)}</li>
+ * <li>{@link #copySign(float, float)}</li>
+ * <li>{@link #getExponent(float)}</li>
+ * <li>{@link #nextAfter(float,double)}</li>
+ * <li>{@link #nextUp(float)}</li>
+ * <li>{@link #scalb(float, int)}</li>
+ * </ul>
+ * @version $Revision: 1074294 $ $Date: 2011-02-24 22:18:59 +0100 (jeu. 24 févr. 2011) $
+ * @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;
+
+    /** Exponential evaluated at integer values,
+     * exp(x) =  expIntTableA[x + 750] + expIntTableB[x+750].
+     */
+    private static final double EXP_INT_TABLE_A[] = new double[1500];
+
+    /** Exponential evaluated at integer values,
+     * exp(x) =  expIntTableA[x + 750] + expIntTableB[x+750]
+     */
+    private static final double EXP_INT_TABLE_B[] = new double[1500];
+
+    /** 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_A[] = new double[1025];
+
+    /** 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[] = new double[1025];
+
+    /** Factorial table, for Taylor series expansions. */
+    private static final double FACT[] = new double[20];
+
+    /** Extended precision logarithm table over the range 1 - 2 in increments of 2^-10. */
+    private static final double LN_MANT[][] = new double[1024][];
+
+    /** 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 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},
+    };
+
+    /** 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 table (high bits). */
+    private static final double SINE_TABLE_A[] = new double[14];
+
+    /** Sine table (low bits). */
+    private static final double SINE_TABLE_B[] = new double[14];
+
+    /** Cosine table (high bits). */
+    private static final double COSINE_TABLE_A[] = new double[14];
+
+    /** Cosine table (low bits). */
+    private static final double COSINE_TABLE_B[] = new double[14];
+
+    /** Tangent table, used by atan() (high bits). */
+    private static final double TANGENT_TABLE_A[] = new double[14];
+
+    /** Tangent table, used by atan() (low bits). */
+    private static final double TANGENT_TABLE_B[] = new double[14];
+
+    /** 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;
+
+    /** 2^52 - double numbers this large must be integral (no fraction) or NaN or Infinite */
+    private static final double TWO_POWER_52 = 4503599627370496.0;
+
+    // Initialize tables
+    static {
+        int i;
+
+        // Generate an array of factorials
+        FACT[0] = 1.0;
+        for (i = 1; i < FACT.length; i++) {
+            FACT[i] = FACT[i-1] * i;
+        }
+
+        double tmp[] = new double[2];
+        double recip[] = new double[2];
+
+        // Populate expIntTable
+        for (i = 0; i < 750; i++) {
+            expint(i, tmp);
+            EXP_INT_TABLE_A[i+750] = tmp[0];
+            EXP_INT_TABLE_B[i+750] = tmp[1];
+
+            if (i != 0) {
+                // Negative integer powers
+                splitReciprocal(tmp, recip);
+                EXP_INT_TABLE_A[750-i] = recip[0];
+                EXP_INT_TABLE_B[750-i] = recip[1];
+            }
+        }
+
+        // Populate expFracTable
+        for (i = 0; i < EXP_FRAC_TABLE_A.length; i++) {
+            slowexp(i/1024.0, tmp);
+            EXP_FRAC_TABLE_A[i] = tmp[0];
+            EXP_FRAC_TABLE_B[i] = tmp[1];
+        }
+
+        // Populate lnMant table
+        for (i = 0; i < LN_MANT.length; i++) {
+            double d = Double.longBitsToDouble( (((long) i) << 42) | 0x3ff0000000000000L );
+            LN_MANT[i] = slowLog(d);
+        }
+
+        // Build the sine and cosine tables
+        buildSinCosTables();
+    }
+
+    /**
+     * 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 > -MathUtils.SAFE_MIN && d < MathUtils.SAFE_MIN){
+            return d; // These are un-normalised - don't try to convert
+        }
+        long xl = Double.doubleToLongBits(d);
+        xl = 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;
+      }
+
+      if (x > 20.0) {
+          return exp(x)/2.0;
+      }
+
+      if (x < -20) {
+          return exp(-x)/2.0;
+      }
+
+      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;
+      }
+
+      if (x > 20.0) {
+          return exp(x)/2.0;
+      }
+
+      if (x < -20) {
+          return -exp(-x)/2.0;
+      }
+
+      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;
+      }
+
+      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 * (1 / 3.0 - a2 * (1 / 5.0 - a2 * (1 / 7.0 - a2 * (1 / 9.0 - a2 * (1.0 / 11.0 - a2 * (1.0 / 13.0 - a2 * (1.0 / 15.0 - a2 * (1.0 / 17.0) * 15.0 / 16.0) * 13.0 / 14.0) * 11.0 / 12.0) * 9.0 / 10.0) * 7.0 / 8.0) * 5.0 / 6.0) * 3.0 / 4.0) / 2.0);
+            } else if (a > 0.036) {
+                absAsinh = a * (1 - a2 * (1 / 3.0 - a2 * (1 / 5.0 - a2 * (1 / 7.0 - a2 * (1 / 9.0 - a2 * (1.0 / 11.0 - a2 * (1.0 / 13.0) * 11.0 / 12.0) * 9.0 / 10.0) * 7.0 / 8.0) * 5.0 / 6.0) * 3.0 / 4.0) / 2.0);
+            } else if (a > 0.0036) {
+                absAsinh = a * (1 - a2 * (1 / 3.0 - a2 * (1 / 5.0 - a2 * (1 / 7.0 - a2 * (1 / 9.0) * 7.0 / 8.0) * 5.0 / 6.0) * 3.0 / 4.0) / 2.0);
+            } else {
+                absAsinh = a * (1 - a2 * (1 / 3.0 - a2 * (1 / 5.0) * 3.0 / 4.0) / 2.0);
+            }
+        }
+
+        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 * (1.0 / 3.0 + a2 * (1.0 / 5.0 + a2 * (1.0 / 7.0 + a2 * (1.0 / 9.0 + a2 * (1.0 / 11.0 + a2 * (1.0 / 13.0 + a2 * (1.0 / 15.0 + a2 * (1.0 / 17.0)))))))));
+            } else if (a > 0.031) {
+                absAtanh = a * (1 + a2 * (1.0 / 3.0 + a2 * (1.0 / 5.0 + a2 * (1.0 / 7.0 + a2 * (1.0 / 9.0 + a2 * (1.0 / 11.0 + a2 * (1.0 / 13.0)))))));
+            } else if (a > 0.003) {
+                absAtanh = a * (1 + a2 * (1.0 / 3.0 + a2 * (1.0 / 5.0 + a2 * (1.0 / 7.0 + a2 * (1.0 / 9.0)))));
+            } else {
+                absAtanh = a * (1 + a2 * (1.0 / 3.0 + a2 * (1.0 / 5.0)));
+            }
+        }
+
+        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);
+    }
+
+    /** 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.
+     *
+     * 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 UPL error.
+     *
+     * 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)
+     *
+     * 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;
+
+        /* Lookup exp(floor(x)).
+         * intPartA will have the upper 22 bits, intPartB will have the lower
+         * 52 bits.
+         */
+        if (x < 0.0) {
+            intVal = (int) -x;
+
+            if (intVal > 746) {
+                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++;
+
+            intPartA = EXP_INT_TABLE_A[750-intVal];
+            intPartB = EXP_INT_TABLE_B[750-intVal];
+
+            intVal = -intVal;
+        } else {
+            intVal = (int) x;
+
+            if (intVal > 709) {
+                if (hiPrec != null) {
+                    hiPrec[0] = Double.POSITIVE_INFINITY;
+                    hiPrec[1] = 0.0;
+                }
+                return Double.POSITIVE_INFINITY;
+            }
+
+            intPartA = EXP_INT_TABLE_A[750+intVal];
+            intPartB = EXP_INT_TABLE_B[750+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 = EXP_FRAC_TABLE_A[intFrac];
+        final double fracPartB = 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 percison.
+         */
+        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 contant 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;
+        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 = EXP_FRAC_TABLE_A[intFrac] - 1.0;
+            double tempB = 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 = zb * epsilon;
+        zb = 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;
+    }
+
+    /**
+     *  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)
+     */
+    private 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 = 19; 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) {
+            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.
+     *
+     *  Set b = 1/(2^22),  a = 1 - b.  Thus (a+b) = 1.
+     *  Use following identity to compute (a+b)/(c+d)
+     *
+     *  (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
+     */
+    private 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 = 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] = result[1] - (tmp - result[0] - zs[0]);
+        result[0] = tmp;
+        tmp = result[0] + zs[1];
+        result[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] = result[1] - (tmp - result[0] - zs[0]);
+        result[0] = tmp;
+        tmp = result[0] + zs[1];
+        result[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] = result[1] - (tmp - result[0] - zs[0]);
+        result[0] = tmp;
+        tmp = result[0] + zs[1];
+        result[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])
+     */
+    private 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];
+    }
+
+
+    /**
+     * 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.doubleToLongBits(x);
+
+        /* Handle special cases of negative input, and NaN */
+        if ((bits & 0x8000000000000000L) != 0 || x != x) {
+            if (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) {
+            if (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;
+
+                double ya = LN_QUICK_COEF[LN_QUICK_COEF.length-1][0];
+                double yb = LN_QUICK_COEF[LN_QUICK_COEF.length-1][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 */
+                    aa = ya + LN_QUICK_COEF[i][0];
+                    ab = yb + LN_QUICK_COEF[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)
+        double lnm[] = 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;
+        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. */
+            double numer = bits & 0x3ffffffffffL;
+            double denom = TWO_POWER_52 + (bits & 0x000ffc0000000000L);
+            aa = numer - xa*denom - xb * denom;
+            xb += aa / denom;
+
+            /* Remez polynomial evaluation */
+            double ya = LN_HI_PREC_COEF[LN_HI_PREC_COEF.length-1][0];
+            double yb = LN_HI_PREC_COEF[LN_HI_PREC_COEF.length-1][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 */
+                aa = ya + LN_HI_PREC_COEF[i][0];
+                ab = yb + LN_HI_PREC_COEF[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 = 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 = b + d;
+
+        c = a + lnza;
+        d = -(c - a - lnza);
+        a = c;
+        b = b + d;
+
+        c = a + LN_2_B*exp;
+        d = -(c - a - LN_2_B*exp);
+        a = c;
+        b = b + d;
+
+        c = a + lnm[1];
+        d = -(c - a - lnm[1]);
+        a = c;
+        b = b + d;
+
+        c = a + lnzb;
+        d = -(c - a - lnzb);
+        a = c;
+        b = b + d;
+
+        if (hiPrec != null) {
+            hiPrec[0] = a;
+            hiPrec[1] = b;
+        }
+
+        return a + b;
+    }
+
+    /** Compute log(1 + x).
+     * @param x a number
+     * @return log(1 + x)
+     */
+    public static double log1p(final double x) {
+        double xpa = 1.0 + x;
+        double xpb = -(xpa - 1.0 - x);
+
+        if (x == -1) {
+            return x/0.0;   // -Infinity
+        }
+
+        if (x > 0 && 1/x == 0) { // x = Infinity
+            return x;
+        }
+
+        if (x>1e-6 || x<-1e-6) {
+            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 */
+            double fx1 = xpb/xpa;
+
+            double epsilon = 0.5 * fx1 + 1.0;
+            epsilon = epsilon * fx1;
+
+            return epsilon + hiPrec[1] + hiPrec[0];
+        }
+
+        /* Value is small |x| < 1e6, do a Taylor series centered on 1.0 */
+        double y = x * 0.333333333333333 - 0.5;
+        y = y * x + 1.0;
+        y = y * x;
+
+        return y;
+    }
+
+    /** 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;
+    }
+
+    /**
+     * Power function.  Compute x^y.
+     *
+     * @param x   a double
+     * @param y   a double
+     * @return double
+     */
+    public static double pow(double x, double y) {
+        final double lns[] = new double[2];
+
+        if (y == 0.0) {
+            return 1.0;
+        }
+
+        if (x != x) { // X is NaN
+            return x;
+        }
+
+
+        if (x == 0) {
+            long bits = Double.doubleToLongBits(x);
+            if ((bits & 0x8000000000000000L) != 0) {
+                // -zero
+                long yi = (long) y;
+
+                if (y < 0 && y == yi && (yi & 1) == 1) {
+                    return Double.NEGATIVE_INFINITY;
+                }
+
+                if (y < 0 && y == yi && (yi & 1) == 1) {
+                    return -0.0;
+                }
+
+                if (y > 0 && y == yi && (yi & 1) == 1) {
+                    return -0.0;
+                }
+            }
+
+            if (y < 0) {
+                return Double.POSITIVE_INFINITY;
+            }
+            if (y > 0) {
+                return 0.0;
+            }
+
+            return Double.NaN;
+        }
+
+        if (x == Double.POSITIVE_INFINITY) {
+            if (y != y) { // y is NaN
+                return y;
+            }
+            if (y < 0.0) {
+                return 0.0;
+            } else {
+                return Double.POSITIVE_INFINITY;
+            }
+        }
+
+        if (y == Double.POSITIVE_INFINITY) {
+            if (x * x == 1.0)
+              return Double.NaN;
+
+            if (x * x > 1.0) {
+                return Double.POSITIVE_INFINITY;
+            } else {
+                return 0.0;
+            }
+        }
+
+        if (x == Double.NEGATIVE_INFINITY) {
+            if (y != y) { // y is NaN
+                return y;
+            }
+
+            if (y < 0) {
+                long yi = (long) y;
+                if (y == yi && (yi & 1) == 1) {
+                    return -0.0;
+                }
+
+                return 0.0;
+            }
+
+            if (y > 0)  {
+                long yi = (long) y;
+                if (y == yi && (yi & 1) == 1) {
+                    return Double.NEGATIVE_INFINITY;
+                }
+
+                return Double.POSITIVE_INFINITY;
+            }
+        }
+
+        if (y == Double.NEGATIVE_INFINITY) {
+
+            if (x * x == 1.0) {
+                return Double.NaN;
+            }
+
+            if (x * x < 1.0) {
+                return Double.POSITIVE_INFINITY;
+            } else {
+                return 0.0;
+            }
+        }
+
+        /* Handle special case x<0 */
+        if (x < 0) {
+            // y is an even integer in this case
+            if (y >= TWO_POWER_52 || y <= -TWO_POWER_52) {
+                return pow(-x, y);
+            }
+
+            if (y == (long) y) {
+                // If y is an integer
+                return ((long)y & 1) == 0 ? pow(-x, y) : -pow(-x, y);
+            } else {
+                return Double.NaN;
+            }
+        }
+
+        /* Split y into ya and yb such that y = ya+yb */
+        double ya;
+        double yb;
+        if (y < 8e298 && y > -8e298) {
+            double tmp1 = y * HEX_40000000;
+            ya = y + tmp1 - tmp1;
+            yb = y - ya;
+        } else {
+            double tmp1 = y * 9.31322574615478515625E-10;
+            double tmp2 = tmp1 * 9.31322574615478515625E-10;
+            ya = (tmp1 + tmp2 - tmp1) * HEX_40000000 * HEX_40000000;
+            yb = y - ya;
+        }
+
+        /* Compute ln(x) */
+        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 */
+        double tmp1 = lna * HEX_40000000;
+        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 = z * lnb;
+
+        final double result = exp(lna, z, null);
+        //result = result + result * z;
+        return result;
+    }
+
+    /** xi in the range of [1, 2].
+     *                                3        5        7
+     *      x+1           /          x        x        x          \
+     *  ln ----- =   2 *  |  x  +   ----  +  ----  +  ---- + ...  |
+     *      1-x           \          3        5        7          /
+     *
+     * So, compute a Remez approximation of the following function
+     *
+     *  ln ((sqrt(x)+1)/(1-sqrt(x)))  /  x
+     *
+     * This will be an even function with only positive coefficents.
+     * x is in the range [0 - 1/3].
+     *
+     * 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)
+     */
+    private 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;
+    }
+
+    /**
+     * For x between 0 and pi/4 compute sine.
+     * @param x number from which sine is requested
+     * @param result placeholder where to put the result in extended precision
+     * @return sin(x)
+     */
+    private 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 = 19; i >= 0; i--) {
+            splitMult(xs, ys, as);
+            ys[0] = as[0]; ys[1] = as[1];
+
+            if ( (i & 1) == 0) {
+                continue;
+            }
+
+            split(FACT[i], as);
+            splitReciprocal(as, facts);
+
+            if ( (i & 2) != 0 ) {
+                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 cosine
+     * @param x number from which cosine is requested
+     * @param result placeholder where to put the result in extended precision
+     * @return cos(x)
+     */
+    private 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 = 19; i >= 0; i--) {
+            splitMult(xs, ys, as);
+            ys[0] = as[0]; ys[1] = as[1];
+
+            if ( (i & 1) != 0) {
+                continue;
+            }
+
+            split(FACT[i], as);
+            splitReciprocal(as, facts);
+
+            if ( (i & 2) != 0 ) {
+                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];
+    }
+
+    /** Build the sine and cosine tables.
+     */
+    private static void buildSinCosTables() {
+        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 < 14; 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 < 14; 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];
+        }
+
+    }
+
+    /**
+     *  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 = b + d;
+
+        t = costA * sinEpsA;
+        c = a + t;
+        d = -(c - a - t);
+        a = c;
+        b = 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 = 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 = b + d;
+
+        t = costA*sinEpsA;
+        c = a + t;
+        d = -(c - a - t);
+        a = c;
+        b = b + d;
+
+        b = b + sintA*cosEpsB + costA*sinEpsB;
+        b = 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 = b + d;
+
+        t = -sintA*sinEpsA;
+        c = a + t;
+        d = -(c - a - t);
+        a = c;
+        b = b + d;
+
+        b = b + costB*cosEpsA + costA*cosEpsB + costB*cosEpsB;
+        b = 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.doubleToLongBits(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 = prodB + (bc << 32);
+        prodA = 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 = 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 = prod2B + (bc << 32);
+        prod2A = 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 = 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 = 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 a number
+     *  @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.doubleToLongBits(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) {
+            /* Inline the Cody/Waite reduction for performance */
+
+            // 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.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--;
+            }
+            quadrant = k & 3;
+            xa = remA;
+            xb = remB;
+        }
+
+        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 a number
+     *  @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) {
+            /* Inline the Cody/Waite reduction for performance */
+
+            // 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.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--;
+            }
+            quadrant = k & 3;
+            xa = remA;
+            xb = remB;
+        }
+
+        //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 a number
+     *  @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.doubleToLongBits(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) {
+            /* Inline the Cody/Waite reduction for performance */
+
+            // 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.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--;
+            }
+            quadrant = k & 3;
+            xa = remA;
+            xb = remB;
+        }
+
+        if (xa > 1.5) {
+            // Accurracy 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) {
+        boolean negate = false;
+        int idx;
+
+        if (xa == 0.0) { // Matches +/- 0.0; return correct sign
+            return leftPlane ? copySign(Math.PI, xa) : xa;
+        }
+
+        if (xa < 0) {
+            // negative
+            xa = -xa;
+            xb = -xb;
+            negate = true;
+        }
+
+        if (xa > 1.633123935319537E16) { // Very large input
+            return (negate ^ leftPlane) ? (-Math.PI/2.0) : (Math.PI/2.0);
+        }
+
+        /* Estimate the closest tabulated arctan value, compute eps = xa-tangentTable */
+        if (xa < 1.0) {
+            idx = (int) (((-1.7168146928204136 * xa * xa + 8.0) * xa) + 0.5);
+        } else {
+            double temp = 1.0/xa;
+            idx = (int) (-((-1.7168146928204136 * temp * temp + 8.0) * temp) + 13.07);
+        }
+        double epsA = xa - TANGENT_TABLE_A[idx];
+        double epsB = -(epsA - xa + TANGENT_TABLE_A[idx]);
+        epsB += xb - TANGENT_TABLE_B[idx];
+
+        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]);
+            double denom = 1.0 / (1.0 + (xa + xb) * (TANGENT_TABLE_A[idx] + TANGENT_TABLE_B[idx]));
+            //double denom = 1.0 / (1.0 + xa*tangentTableA[idx]);
+            ya = epsA * denom;
+            yb = epsB * denom;
+        } else {
+            double temp2 = xa * TANGENT_TABLE_A[idx];
+            double za = 1.0 + temp2;
+            double zb = -(za - 1.0 - temp2);
+            temp2 = xb * TANGENT_TABLE_A[idx] + xa * TANGENT_TABLE_B[idx];
+            temp = za + temp2;
+            zb += -(temp - za - temp2);
+            za = temp;
+
+            zb += xb * TANGENT_TABLE_B[idx];
+            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 */
+        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 / (1.0 + epsA * epsA);
+
+        double result;
+        double resultb;
+
+        //result = yb + eighths[idx] + ya;
+        double za = EIGHTHS[idx] + ya;
+        double zb = -(za - EIGHTHS[idx] - ya);
+        temp = za + yb;
+        zb += -(temp - za - yb);
+        za = temp;
+
+        result = za + zb;
+        resultb = -(result - za - zb);
+
+        if (leftPlane) {
+            // Result is in the left plane
+            final double pia = 1.5707963267948966*2.0;
+            final double pib = 6.123233995736766E-17*2.0;
+
+            za = pia - result;
+            zb = -(za - pia + result);
+            zb += pib - resultb;
+
+            result = za + zb;
+            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.0) {
+            double result = x*y;
+            double invx = 1.0/x;
+            double invy = 1.0/y;
+
+            if (invx == 0.0) { // X is infinite
+                if (x > 0) {
+                    return y; // return +/- 0.0
+                } else {
+                    return copySign(Math.PI, y);
+                }
+            }
+
+            if (x < 0.0 || invx < 0.0) {
+                if (y < 0.0 || invy < 0.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/4.0;
+            }
+
+            if (x == Double.NEGATIVE_INFINITY) {
+                return Math.PI*3.0/4.0;
+            }
+
+            return Math.PI/2.0;
+        }
+
+        if (y == Double.NEGATIVE_INFINITY) {
+            if (x == Double.POSITIVE_INFINITY) {
+                return -Math.PI/4.0;
+            }
+
+            if (x == Double.NEGATIVE_INFINITY) {
+                return -Math.PI*3.0/4.0;
+            }
+
+            return -Math.PI/2.0;
+        }
+
+        if (x == Double.POSITIVE_INFINITY) {
+            if (y > 0.0 || 1/y > 0.0) {
+                return 0.0;
+            }
+
+            if (y < 0.0 || 1/y < 0.0) {
+                return -0.0;
+            }
+        }
+
+        if (x == Double.NEGATIVE_INFINITY)
+        {
+            if (y > 0.0 || 1/y > 0.0) {
+                return Math.PI;
+            }
+
+            if (y < 0.0 || 1/y < 0.0) {
+                return -Math.PI;
+            }
+        }
+
+        // Neither y nor x can be infinite or NAN here
+
+        if (x == 0) {
+            if (y > 0.0 || 1/y > 0.0) {
+                return Math.PI/2.0;
+            }
+
+            if (y < 0.0 || 1/y < 0.0) {
+                return -Math.PI/2.0;
+            }
+        }
+
+        // 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;
+
+        double temp = ra + rb;
+        rb = -(temp - ra - rb);
+        ra = temp;
+
+        if (ra == 0) { // Fix up the sign so atan works correctly
+            ra = copySign(0.0, y);
+        }
+
+        // Call atan
+        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.doubleToLongBits(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.doubleToLongBits(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 = 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 = 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) {
+        return (x < 0) ? -x : x;
+    }
+
+    /**
+     * Absolute value.
+     * @param x number from which absolute value is requested
+     * @return abs(x)
+     */
+    public static long abs(final long x) {
+        return (x < 0l) ? -x : x;
+    }
+
+    /**
+     * Absolute value.
+     * @param x number from which absolute value is requested
+     * @return abs(x)
+     */
+    public static float abs(final float x) {
+        return (x < 0.0f) ? -x : (x == 0.0f) ? 0.0f : x; // -0.0 => +0.0
+    }
+
+    /**
+     * Absolute value.
+     * @param x number from which absolute value is requested
+     * @return abs(x)
+     */
+    public static double abs(double x) {
+        return (x < 0.0) ? -x : (x == 0.0) ? 0.0 : x; // -0.0 => +0.0
+    }
+
+    /**
+     * 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.doubleToLongBits(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.doubleToLongBits(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 = mantissa | (1L << 52);
+
+                // scales down complete mantissa, hence losing least significant bits
+                final long mostSignificantLostBit = mantissa & (1L << (-scaledExponent));
+                mantissa = 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 = mantissa << 1;
+                    --scaledExponent;
+                }
+                ++scaledExponent;
+                mantissa = 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 = mantissa | (1 << 23);
+
+                // scales down complete mantissa, hence losing least significant bits
+                final int mostSignificantLostBit = mantissa & (1 << (-scaledExponent));
+                mantissa = 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 = mantissa << 1;
+                    --scaledExponent;
+                }
+                ++scaledExponent;
+                mantissa = 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>
+     * <li>-MAX_VALUE</li>
+     * <li>-MIN_VALUE</li>
+     * <li>-0.0</li>
+     * <li>+0.0</li>
+     * <li>+MIN_VALUE</li>
+     * <li>+MAX_VALUE</li>
+     * <li>+INFINITY</li>
+     * <li></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>
+     * <p>
+     * If {@code d} is infinite and direction does not
+     * bring it back to finite numbers, it is returned unchanged.</p>
+     *
+     * @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
+
+        final long bits = Double.doubleToLongBits(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>
+     * <li>-MAX_VALUE</li>
+     * <li>-MIN_VALUE</li>
+     * <li>-0.0</li>
+     * <li>+0.0</li>
+     * <li>+MIN_VALUE</li>
+     * <li>+MAX_VALUE</li>
+     * <li>+INFINITY</li>
+     * <li></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>
+     * <p>
+     * If {@code f} is infinite and direction does not
+     * bring it back to finite numbers, it is returned unchanged.</p>
+     *
+     * @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>
+     * <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 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>
+     * <li>If the result is not NaN, the sign of the result equals the sign of the dividend.</li>
+     * <li>If the dividend is an infinity, or the divisor is a zero, or both, the result is NaN.</li>
+     * <li>If the dividend is finite and the divisor is an infinity, the result equals the dividend.</li>
+     * <li>If the dividend is a zero and the divisor is finite, the result equals the dividend.</li>
+     * </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
+    }
+
+    /**
+     * 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){
+        long m = Double.doubleToLongBits(magnitude);
+        long s = Double.doubleToLongBits(sign);
+        if ((m >= 0 && s >= 0) || (m < 0 && s < 0)) { // Sign is currently OK
+            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){
+        int m = Float.floatToIntBits(magnitude);
+        int s = Float.floatToIntBits(sign);
+        if ((m >= 0 && s >= 0) || (m < 0 && s < 0)) { // Sign is currently OK
+            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.
+     * </p>
+     * @param d number from which exponent is requested
+     * @return exponent for d in IEEE754 representation, without bias
+     */
+    public static int getExponent(final double d) {
+        return (int) ((Double.doubleToLongBits(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.
+     * </p>
+     * @param f number from which exponent is requested
+     * @return exponent for d in IEEE754 representation, without bias
+     */
+    public static int getExponent(final float f) {
+        return ((Float.floatToIntBits(f) >>> 23) & 0xff) - 127;
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/util/MathUtils.java b/src/main/java/org/apache/commons/math/util/MathUtils.java
new file mode 100644
index 0000000..c1999bd
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/util/MathUtils.java
@@ -0,0 +1,2265 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.util;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Arrays;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.Localizable;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.exception.NonMonotonousSequenceException;
+
+/**
+ * Some useful additions to the built-in functions in {@link Math}.
+ * @version $Revision: 1073472 $ $Date: 2011-02-22 20:49:07 +0100 (mar. 22 févr. 2011) $
+ */
+public final class MathUtils {
+
+    /** Smallest positive number such that 1 - EPSILON is not numerically equal to 1. */
+    public static final double EPSILON = 0x1.0p-53;
+
+    /** Safe minimum, such that 1 / SAFE_MIN does not overflow.
+     * <p>In IEEE 754 arithmetic, this is also the smallest normalized
+     * number 2<sup>-1022</sup>.</p>
+     */
+    public static final double SAFE_MIN = 0x1.0p-1022;
+
+    /**
+     * 2 &pi;.
+     * @since 2.1
+     */
+    public static final double TWO_PI = 2 * FastMath.PI;
+
+    /** -1.0 cast as a byte. */
+    private static final byte  NB = (byte)-1;
+
+    /** -1.0 cast as a short. */
+    private static final short NS = (short)-1;
+
+    /** 1.0 cast as a byte. */
+    private static final byte  PB = (byte)1;
+
+    /** 1.0 cast as a short. */
+    private static final short PS = (short)1;
+
+    /** 0.0 cast as a byte. */
+    private static final byte  ZB = (byte)0;
+
+    /** 0.0 cast as a short. */
+    private static final short ZS = (short)0;
+
+    /** Gap between NaN and regular numbers. */
+    private static final int NAN_GAP = 4 * 1024 * 1024;
+
+    /** 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;
+
+    /** All long-representable factorials */
+    private 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 };
+
+    /**
+     * Private Constructor
+     */
+    private MathUtils() {
+        super();
+    }
+
+    /**
+     * Add two integers, checking for overflow.
+     *
+     * @param x an addend
+     * @param y an addend
+     * @return the sum <code>x+y</code>
+     * @throws ArithmeticException if the result can not be represented as an
+     *         int
+     * @since 1.1
+     */
+    public static int addAndCheck(int x, int y) {
+        long s = (long)x + (long)y;
+        if (s < Integer.MIN_VALUE || s > Integer.MAX_VALUE) {
+            throw MathRuntimeException.createArithmeticException(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</code>
+     * @throws ArithmeticException if the result can not be represented as an
+     *         long
+     * @since 1.2
+     */
+    public static long addAndCheck(long a, long b) {
+        return addAndCheck(a, b, LocalizedFormats.OVERFLOW_IN_ADDITION);
+    }
+
+    /**
+     * Add two long integers, checking for overflow.
+     *
+     * @param a an addend
+     * @param b an addend
+     * @param pattern the pattern to use for any thrown exception.
+     * @return the sum <code>a+b</code>
+     * @throws ArithmeticException if the result can not be represented as an
+     *         long
+     * @since 1.2
+     */
+    private static long addAndCheck(long a, long b, Localizable pattern) {
+        long ret;
+        if (a > b) {
+            // use symmetry to reduce boundary cases
+            ret = addAndCheck(b, a, pattern);
+        } else {
+            // assert a <= b
+
+            if (a < 0) {
+                if (b < 0) {
+                    // check for negative overflow
+                    if (Long.MIN_VALUE - b <= a) {
+                        ret = a + b;
+                    } else {
+                        throw MathRuntimeException.createArithmeticException(pattern, a, b);
+                    }
+                } else {
+                    // opposite sign addition is always safe
+                    ret = a + b;
+                }
+            } else {
+                // assert a >= 0
+                // assert b >= 0
+
+                // check for positive overflow
+                if (a <= Long.MAX_VALUE - b) {
+                    ret = a + b;
+                } else {
+                    throw MathRuntimeException.createArithmeticException(pattern, a, b);
+                }
+            }
+        }
+        return ret;
+    }
+
+    /**
+     * Returns an exact representation of the <a
+     * href="http://mathworld.wolfram.com/BinomialCoefficient.html"> Binomial
+     * Coefficient</a>, "<code>n choose k</code>", the number of
+     * <code>k</code>-element subsets that can be selected from an
+     * <code>n</code>-element set.
+     * <p>
+     * <Strong>Preconditions</strong>:
+     * <ul>
+     * <li> <code>0 <= k <= n </code> (otherwise
+     * <code>IllegalArgumentException</code> is thrown)</li>
+     * <li> The result is small enough to fit into a <code>long</code>. The
+     * largest value of <code>n</code> for which all coefficients are
+     * <code> < Long.MAX_VALUE</code> is 66. If the computed value exceeds
+     * <code>Long.MAX_VALUE</code> an <code>ArithMeticException</code> is
+     * thrown.</li>
+     * </ul></p>
+     *
+     * @param n the size of the set
+     * @param k the size of the subsets to be counted
+     * @return <code>n choose k</code>
+     * @throws IllegalArgumentException if preconditions are not met.
+     * @throws ArithmeticException if the result is too large to be represented
+     *         by a long integer.
+     */
+    public static long binomialCoefficient(final int n, final int k) {
+        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 = 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 = gcd(i, j);
+                result = mulAndCheck(result / (j / d), i / d);
+                i++;
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Returns a <code>double</code> representation of the <a
+     * href="http://mathworld.wolfram.com/BinomialCoefficient.html"> Binomial
+     * Coefficient</a>, "<code>n choose k</code>", the number of
+     * <code>k</code>-element subsets that can be selected from an
+     * <code>n</code>-element set.
+     * <p>
+     * <Strong>Preconditions</strong>:
+     * <ul>
+     * <li> <code>0 <= k <= n </code> (otherwise
+     * <code>IllegalArgumentException</code> is thrown)</li>
+     * <li> The result is small enough to fit into a <code>double</code>. The
+     * largest value of <code>n</code> for which all coefficients are <
+     * Double.MAX_VALUE is 1029. If the computed value exceeds Double.MAX_VALUE,
+     * Double.POSITIVE_INFINITY is returned</li>
+     * </ul></p>
+     *
+     * @param n the size of the set
+     * @param k the size of the subsets to be counted
+     * @return <code>n choose k</code>
+     * @throws IllegalArgumentException if preconditions are not met.
+     */
+    public static double binomialCoefficientDouble(final int n, final int k) {
+        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</code> of the <a
+     * href="http://mathworld.wolfram.com/BinomialCoefficient.html"> Binomial
+     * Coefficient</a>, "<code>n choose k</code>", the number of
+     * <code>k</code>-element subsets that can be selected from an
+     * <code>n</code>-element set.
+     * <p>
+     * <Strong>Preconditions</strong>:
+     * <ul>
+     * <li> <code>0 <= k <= n </code> (otherwise
+     * <code>IllegalArgumentException</code> is thrown)</li>
+     * </ul></p>
+     *
+     * @param n the size of the set
+     * @param k the size of the subsets to be counted
+     * @return <code>n choose k</code>
+     * @throws IllegalArgumentException if preconditions are not met.
+     */
+    public static double binomialCoefficientLog(final int n, final int k) {
+        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;
+    }
+
+    /**
+     * Check binomial preconditions.
+     * @param n the size of the set
+     * @param k the size of the subsets to be counted
+     * @exception IllegalArgumentException if preconditions are not met.
+     */
+    private static void checkBinomial(final int n, final int k)
+        throws IllegalArgumentException {
+        if (n < k) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                LocalizedFormats.BINOMIAL_INVALID_PARAMETERS_ORDER,
+                n, k);
+        }
+        if (n < 0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.BINOMIAL_NEGATIVE_PARAMETER,
+                  n);
+        }
+    }
+
+    /**
+     * 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>
+     *       <li>&lt; 0 if !{@link #equals(double, double, double) equals(x, y, eps)} &amp;&amp; x &lt; y</li>
+     *       <li>> 0 if !{@link #equals(double, double, double) equals(x, y, eps)} &amp;&amp; x > y</li></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;
+    }
+
+    /**
+     * Returns the <a href="http://mathworld.wolfram.com/HyperbolicCosine.html">
+     * hyperbolic cosine</a> of x.
+     *
+     * @param x double value for which to find the hyperbolic cosine
+     * @return hyperbolic cosine of x
+     */
+    public static double cosh(double x) {
+        return (FastMath.exp(x) + FastMath.exp(-x)) / 2.0;
+    }
+
+    /**
+     * Returns true iff they are strictly equal.
+     *
+     * @param x first value
+     * @param y second value
+     * @return {@code true} if the values are equal.
+     * @deprecated as of 2.2 his method considers that {@code NaN == NaN}. In release
+     * 3.0, the semantics will change in order to comply with IEEE754 where it
+     * is specified that {@code NaN != NaN}.
+     * New methods have been added for those cases wher the old semantics is
+     * useful (see e.g. {@link #equalsIncludingNaN(float,float)
+     * equalsIncludingNaN}.
+     */
+    @Deprecated
+    public static boolean equals(float x, float y) {
+        return (Float.isNaN(x) && Float.isNaN(y)) || x == y;
+    }
+
+    /**
+     * Returns true if both arguments are NaN or neither is NaN and 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 or both are NaN.
+     * @since 2.2
+     */
+    public static boolean equalsIncludingNaN(float x, float y) {
+        return (Float.isNaN(x) && Float.isNaN(y)) || equals(x, y, 1);
+    }
+
+    /**
+     * Returns true if both arguments are equal or 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.
+     * @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 both arguments are NaN or are equal or 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 both 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://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm">
+     * Bruce Dawson</a>
+     *
+     * @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(float x, float y, int maxUlps) {
+        // Check that "maxUlps" is non-negative and small enough so that
+        // NaN won't compare as equal to anything (except another NaN).
+        assert maxUlps > 0 && maxUlps < NAN_GAP;
+
+        int xInt = Float.floatToIntBits(x);
+        int yInt = Float.floatToIntBits(y);
+
+        // Make lexicographically ordered as a two's-complement integer.
+        if (xInt < 0) {
+            xInt = SGN_MASK_FLOAT - xInt;
+        }
+        if (yInt < 0) {
+            yInt = SGN_MASK_FLOAT - yInt;
+        }
+
+        final boolean isEqual = FastMath.abs(xInt - yInt) <= maxUlps;
+
+        return isEqual && !Float.isNaN(x) && !Float.isNaN(y);
+    }
+
+    /**
+     * Returns true if both arguments are 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 (Float.isNaN(x) && Float.isNaN(y)) || equals(x, y, maxUlps);
+    }
+
+    /**
+     * Returns true iff both arguments are null or have same dimensions and all
+     * their elements are equal as defined by
+     * {@link #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.
+     * @deprecated as of 2.2 this method considers that {@code NaN == NaN}. In release
+     * 3.0, the semantics will change in order to comply with IEEE754 where it
+     * is specified that {@code NaN != NaN}.
+     * New methods have been added for those cases where the old semantics is
+     * useful (see e.g. {@link #equalsIncludingNaN(float[],float[])
+     * equalsIncludingNaN}.
+     */
+    @Deprecated
+    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 (!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 #equalsIncludingNaN(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
+     * @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 (!equalsIncludingNaN(x[i], y[i])) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Returns true iff both arguments are NaN or neither is NaN and they are
+     * equal
+     *
+     * <p>This method considers that {@code NaN == NaN}. In release
+     * 3.0, the semantics will change in order to comply with IEEE754 where it
+     * is specified that {@code NaN != NaN}.
+     * New methods have been added for those cases where the old semantics
+     * (w.r.t. NaN) is useful (see e.g.
+     * {@link #equalsIncludingNaN(double,double, double) equalsIncludingNaN}.
+     * </p>
+     *
+     * @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 (Double.isNaN(x) && Double.isNaN(y)) || x == y;
+    }
+
+    /**
+     * Returns true if both arguments are NaN or neither is NaN and 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 or both are NaN.
+     * @since 2.2
+     */
+    public static boolean equalsIncludingNaN(double x, double y) {
+        return (Double.isNaN(x) && Double.isNaN(y)) || equals(x, y, 1);
+    }
+
+    /**
+     * Returns true if both arguments are equal or within the range of allowed
+     * error (inclusive).
+     * <p>
+     * Two NaNs are considered equals, as are two infinities with same sign.
+     * </p>
+     * <p>This method considers that {@code NaN == NaN}. In release
+     * 3.0, the semantics will change in order to comply with IEEE754 where it
+     * is specified that {@code NaN != NaN}.
+     * New methods have been added for those cases where the old semantics
+     * (w.r.t. NaN) is useful (see e.g.
+     * {@link #equalsIncludingNaN(double,double, double) equalsIncludingNaN}.
+     * </p>
+     * @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.
+     */
+    public static boolean equals(double x, double y, double eps) {
+        return equals(x, y) || FastMath.abs(y - x) <= eps;
+    }
+
+    /**
+     * Returns true if both arguments are NaN or are equal or 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 both 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://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm">
+     * Bruce Dawson</a>
+     *
+     * <p>This method considers that {@code NaN == NaN}. In release
+     * 3.0, the semantics will change in order to comply with IEEE754 where it
+     * is specified that {@code NaN != NaN}.
+     * New methods have been added for those cases where the old semantics
+     * (w.r.t. NaN) is useful (see e.g.
+     * {@link #equalsIncludingNaN(double,double, int) equalsIncludingNaN}.
+     * </p>
+     *
+     * @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(double x, double y, int maxUlps) {
+        // Check that "maxUlps" is non-negative and small enough so that
+        // NaN won't compare as equal to anything (except another NaN).
+        assert maxUlps > 0 && maxUlps < NAN_GAP;
+
+        long xInt = Double.doubleToLongBits(x);
+        long yInt = Double.doubleToLongBits(y);
+
+        // Make lexicographically ordered as a two's-complement integer.
+        if (xInt < 0) {
+            xInt = SGN_MASK - xInt;
+        }
+        if (yInt < 0) {
+            yInt = SGN_MASK - yInt;
+        }
+
+        return FastMath.abs(xInt - yInt) <= maxUlps;
+    }
+
+    /**
+     * 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 (Double.isNaN(x) && Double.isNaN(y)) || equals(x, y, maxUlps);
+    }
+
+    /**
+     * Returns true iff both arguments are null or have same dimensions and all
+     * their elements are equal as defined by
+     * {@link #equals(double,double)}.
+     *
+     * <p>This method considers that {@code NaN == NaN}. In release
+     * 3.0, the semantics will change in order to comply with IEEE754 where it
+     * is specified that {@code NaN != NaN}.
+     * New methods have been added for those cases wher the old semantics is
+     * useful (see e.g. {@link #equalsIncludingNaN(double[],double[])
+     * equalsIncludingNaN}.
+     * </p>
+     * @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(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 (!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 #equalsIncludingNaN(double,double)}.
+     *
+     * @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(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 (!equalsIncludingNaN(x[i], y[i])) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Returns n!. Shorthand for <code>n</code> <a
+     * href="http://mathworld.wolfram.com/Factorial.html"> Factorial</a>, the
+     * product of the numbers <code>1,...,n</code>.
+     * <p>
+     * <Strong>Preconditions</strong>:
+     * <ul>
+     * <li> <code>n >= 0</code> (otherwise
+     * <code>IllegalArgumentException</code> is thrown)</li>
+     * <li> The result is small enough to fit into a <code>long</code>. The
+     * largest value of <code>n</code> for which <code>n!</code> <
+     * Long.MAX_VALUE</code> is 20. If the computed value exceeds <code>Long.MAX_VALUE</code>
+     * an <code>ArithMeticException </code> is thrown.</li>
+     * </ul>
+     * </p>
+     *
+     * @param n argument
+     * @return <code>n!</code>
+     * @throws ArithmeticException if the result is too large to be represented
+     *         by a long integer.
+     * @throws IllegalArgumentException if n < 0
+     */
+    public static long factorial(final int n) {
+        if (n < 0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.FACTORIAL_NEGATIVE_PARAMETER,
+                  n);
+        }
+        if (n > 20) {
+            throw new ArithmeticException(
+                    "factorial value is too large to fit in a long");
+        }
+        return FACTORIALS[n];
+    }
+
+    /**
+     * Returns n!. Shorthand for <code>n</code> <a
+     * href="http://mathworld.wolfram.com/Factorial.html"> Factorial</a>, the
+     * product of the numbers <code>1,...,n</code> as a <code>double</code>.
+     * <p>
+     * <Strong>Preconditions</strong>:
+     * <ul>
+     * <li> <code>n >= 0</code> (otherwise
+     * <code>IllegalArgumentException</code> is thrown)</li>
+     * <li> The result is small enough to fit into a <code>double</code>. The
+     * largest value of <code>n</code> for which <code>n!</code> <
+     * Double.MAX_VALUE</code> is 170. If the computed value exceeds
+     * Double.MAX_VALUE, Double.POSITIVE_INFINITY is returned</li>
+     * </ul>
+     * </p>
+     *
+     * @param n argument
+     * @return <code>n!</code>
+     * @throws IllegalArgumentException if n < 0
+     */
+    public static double factorialDouble(final int n) {
+        if (n < 0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.FACTORIAL_NEGATIVE_PARAMETER,
+                  n);
+        }
+        if (n < 21) {
+            return factorial(n);
+        }
+        return FastMath.floor(FastMath.exp(factorialLog(n)) + 0.5);
+    }
+
+    /**
+     * Returns the natural logarithm of n!.
+     * <p>
+     * <Strong>Preconditions</strong>:
+     * <ul>
+     * <li> <code>n >= 0</code> (otherwise
+     * <code>IllegalArgumentException</code> is thrown)</li>
+     * </ul></p>
+     *
+     * @param n argument
+     * @return <code>n!</code>
+     * @throws IllegalArgumentException if preconditions are not met.
+     */
+    public static double factorialLog(final int n) {
+        if (n < 0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                  LocalizedFormats.FACTORIAL_NEGATIVE_PARAMETER,
+                  n);
+        }
+        if (n < 21) {
+            return FastMath.log(factorial(n));
+        }
+        double logSum = 0;
+        for (int i = 2; i <= n; i++) {
+            logSum += FastMath.log(i);
+        }
+        return logSum;
+    }
+
+    /**
+     * <p>
+     * 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).
+     * </p>
+     * Special cases:
+     * <ul>
+     * <li>The invocations
+     * <code>gcd(Integer.MIN_VALUE, Integer.MIN_VALUE)</code>,
+     * <code>gcd(Integer.MIN_VALUE, 0)</code> and
+     * <code>gcd(0, Integer.MIN_VALUE)</code> throw an
+     * <code>ArithmeticException</code>, because the result would be 2^31, which
+     * is too large for an int value.</li>
+     * <li>The result of <code>gcd(x, x)</code>, <code>gcd(0, x)</code> and
+     * <code>gcd(x, 0)</code> is the absolute value of <code>x</code>, except
+     * for the special cases above.
+     * <li>The invocation <code>gcd(0, 0)</code> is the only one which returns
+     * <code>0</code>.</li>
+     * </ul>
+     *
+     * @param p any number
+     * @param q any number
+     * @return the greatest common divisor, never negative
+     * @throws ArithmeticException if the result cannot be represented as a
+     * nonnegative int value
+     * @since 1.1
+     */
+    public static int gcd(final int p, final int q) {
+        int u = p;
+        int v = q;
+        if ((u == 0) || (v == 0)) {
+            if ((u == Integer.MIN_VALUE) || (v == Integer.MIN_VALUE)) {
+                throw MathRuntimeException.createArithmeticException(
+                        LocalizedFormats.GCD_OVERFLOW_32_BITS,
+                        p, q);
+            }
+            return FastMath.abs(u) + FastMath.abs(v);
+        }
+        // keep u and v negative, as negative integers range down to
+        // -2^31, while positive numbers can only be as large as 2^31-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 < 31) { // while u and v are
+                                                            // both even...
+            u /= 2;
+            v /= 2;
+            k++; // cast out twos.
+        }
+        if (k == 31) {
+            throw MathRuntimeException.createArithmeticException(
+                    LocalizedFormats.GCD_OVERFLOW_32_BITS,
+                    p, q);
+        }
+        // B2. Initialize: u and v have been divided by 2^k and at least
+        // one is odd.
+        int 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 * (1 << k); // gcd is u*2^k
+    }
+
+    /**
+     * <p>
+     * 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).
+     * </p>
+     * Special cases:
+     * <ul>
+     * <li>The invocations
+     * <code>gcd(Long.MIN_VALUE, Long.MIN_VALUE)</code>,
+     * <code>gcd(Long.MIN_VALUE, 0L)</code> and
+     * <code>gcd(0L, Long.MIN_VALUE)</code> throw an
+     * <code>ArithmeticException</code>, because the result would be 2^63, which
+     * is too large for a long value.</li>
+     * <li>The result of <code>gcd(x, x)</code>, <code>gcd(0L, x)</code> and
+     * <code>gcd(x, 0L)</code> is the absolute value of <code>x</code>, except
+     * for the special cases above.
+     * <li>The invocation <code>gcd(0L, 0L)</code> is the only one which returns
+     * <code>0L</code>.</li>
+     * </ul>
+     *
+     * @param p any number
+     * @param q any number
+     * @return the greatest common divisor, never negative
+     * @throws ArithmeticException if the result cannot be represented as a nonnegative long
+     * value
+     * @since 2.1
+     */
+    public static long gcd(final long p, final long q) {
+        long u = p;
+        long v = q;
+        if ((u == 0) || (v == 0)) {
+            if ((u == Long.MIN_VALUE) || (v == Long.MIN_VALUE)){
+                throw MathRuntimeException.createArithmeticException(
+                        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 MathRuntimeException.createArithmeticException(
+                    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 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 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);
+    }
+
+    /**
+     * For a byte value x, this method returns (byte)(+1) if x >= 0 and
+     * (byte)(-1) if x < 0.
+     *
+     * @param x the value, a byte
+     * @return (byte)(+1) or (byte)(-1), depending on the sign of x
+     */
+    public static byte indicator(final byte x) {
+        return (x >= ZB) ? PB : NB;
+    }
+
+    /**
+     * For a double precision value x, this method returns +1.0 if x >= 0 and
+     * -1.0 if x < 0. Returns <code>NaN</code> if <code>x</code> is
+     * <code>NaN</code>.
+     *
+     * @param x the value, a double
+     * @return +1.0 or -1.0, depending on the sign of x
+     */
+    public static double indicator(final double x) {
+        if (Double.isNaN(x)) {
+            return Double.NaN;
+        }
+        return (x >= 0.0) ? 1.0 : -1.0;
+    }
+
+    /**
+     * For a float value x, this method returns +1.0F if x >= 0 and -1.0F if x <
+     * 0. Returns <code>NaN</code> if <code>x</code> is <code>NaN</code>.
+     *
+     * @param x the value, a float
+     * @return +1.0F or -1.0F, depending on the sign of x
+     */
+    public static float indicator(final float x) {
+        if (Float.isNaN(x)) {
+            return Float.NaN;
+        }
+        return (x >= 0.0F) ? 1.0F : -1.0F;
+    }
+
+    /**
+     * For an int value x, this method returns +1 if x >= 0 and -1 if x < 0.
+     *
+     * @param x the value, an int
+     * @return +1 or -1, depending on the sign of x
+     */
+    public static int indicator(final int x) {
+        return (x >= 0) ? 1 : -1;
+    }
+
+    /**
+     * For a long value x, this method returns +1L if x >= 0 and -1L if x < 0.
+     *
+     * @param x the value, a long
+     * @return +1L or -1L, depending on the sign of x
+     */
+    public static long indicator(final long x) {
+        return (x >= 0L) ? 1L : -1L;
+    }
+
+    /**
+     * For a short value x, this method returns (short)(+1) if x >= 0 and
+     * (short)(-1) if x < 0.
+     *
+     * @param x the value, a short
+     * @return (short)(+1) or (short)(-1), depending on the sign of x
+     */
+    public static short indicator(final short x) {
+        return (x >= ZS) ? PS : NS;
+    }
+
+    /**
+     * <p>
+     * Returns the least common multiple of the absolute value of two numbers,
+     * using the formula <code>lcm(a,b) = (a / gcd(a,b)) * b</code>.
+     * </p>
+     * Special cases:
+     * <ul>
+     * <li>The invocations <code>lcm(Integer.MIN_VALUE, n)</code> and
+     * <code>lcm(n, Integer.MIN_VALUE)</code>, where <code>abs(n)</code> is a
+     * power of 2, throw an <code>ArithmeticException</code>, because the result
+     * would be 2^31, which is too large for an int value.</li>
+     * <li>The result of <code>lcm(0, x)</code> and <code>lcm(x, 0)</code> is
+     * <code>0</code> for any <code>x</code>.
+     * </ul>
+     *
+     * @param a any number
+     * @param b any number
+     * @return the least common multiple, never negative
+     * @throws ArithmeticException
+     *             if the result cannot be represented as a nonnegative int
+     *             value
+     * @since 1.1
+     */
+    public static int lcm(int a, int b) {
+        if (a==0 || b==0){
+            return 0;
+        }
+        int lcm = FastMath.abs(mulAndCheck(a / gcd(a, b), b));
+        if (lcm == Integer.MIN_VALUE) {
+            throw MathRuntimeException.createArithmeticException(
+                LocalizedFormats.LCM_OVERFLOW_32_BITS,
+                a, b);
+        }
+        return lcm;
+    }
+
+    /**
+     * <p>
+     * Returns the least common multiple of the absolute value of two numbers,
+     * using the formula <code>lcm(a,b) = (a / gcd(a,b)) * b</code>.
+     * </p>
+     * Special cases:
+     * <ul>
+     * <li>The invocations <code>lcm(Long.MIN_VALUE, n)</code> and
+     * <code>lcm(n, Long.MIN_VALUE)</code>, where <code>abs(n)</code> is a
+     * power of 2, throw an <code>ArithmeticException</code>, because the result
+     * would be 2^63, which is too large for an int value.</li>
+     * <li>The result of <code>lcm(0L, x)</code> and <code>lcm(x, 0L)</code> is
+     * <code>0L</code> for any <code>x</code>.
+     * </ul>
+     *
+     * @param a any number
+     * @param b any number
+     * @return the least common multiple, never negative
+     * @throws ArithmeticException if the result cannot be represented as a nonnegative long
+     * value
+     * @since 2.1
+     */
+    public static long lcm(long a, long b) {
+        if (a==0 || b==0){
+            return 0;
+        }
+        long lcm = FastMath.abs(mulAndCheck(a / gcd(a, b), b));
+        if (lcm == Long.MIN_VALUE){
+            throw MathRuntimeException.createArithmeticException(
+                LocalizedFormats.LCM_OVERFLOW_64_BITS,
+                a, b);
+        }
+        return lcm;
+    }
+
+    /**
+     * <p>Returns the
+     * <a href="http://mathworld.wolfram.com/Logarithm.html">logarithm</a>
+     * for base <code>b</code> of <code>x</code>.
+     * </p>
+     * <p>Returns <code>NaN<code> if either argument is negative.  If
+     * <code>base</code> is 0 and <code>x</code> is positive, 0 is returned.
+     * If <code>base</code> is positive and <code>x</code> is 0,
+     * <code>Double.NEGATIVE_INFINITY</code> is returned.  If both arguments
+     * are 0, the result is <code>NaN</code>.</p>
+     *
+     * @param base the base of the logarithm, must be greater than 0
+     * @param x argument, must be greater than 0
+     * @return the value of the logarithm - the number y such that base^y = x.
+     * @since 1.2
+     */
+    public static double log(double base, double x) {
+        return FastMath.log(x)/FastMath.log(base);
+    }
+
+    /**
+     * Multiply two integers, checking for overflow.
+     *
+     * @param x a factor
+     * @param y a factor
+     * @return the product <code>x*y</code>
+     * @throws ArithmeticException if the result can not be represented as an
+     *         int
+     * @since 1.1
+     */
+    public static int mulAndCheck(int x, int y) {
+        long m = ((long)x) * ((long)y);
+        if (m < Integer.MIN_VALUE || m > Integer.MAX_VALUE) {
+            throw new ArithmeticException("overflow: mul");
+        }
+        return (int)m;
+    }
+
+    /**
+     * Multiply two long integers, checking for overflow.
+     *
+     * @param a first value
+     * @param b second value
+     * @return the product <code>a * b</code>
+     * @throws ArithmeticException if the result can not be represented as an
+     *         long
+     * @since 1.2
+     */
+    public static long mulAndCheck(long a, long b) {
+        long ret;
+        String msg = "overflow: multiply";
+        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 ArithmeticException(msg);
+                    }
+                } 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 ArithmeticException(msg);
+
+                    }
+                } 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 ArithmeticException(msg);
+                }
+            } else {
+                // assert a == 0
+                ret = 0;
+            }
+        }
+        return ret;
+    }
+
+    /**
+     * Get the next machine representable number after a number, moving
+     * in the direction of another number.
+     * <p>
+     * If <code>direction</code> is greater than or equal to<code>d</code>,
+     * the smallest machine representable number strictly greater than
+     * <code>d</code> is returned; otherwise the largest representable number
+     * strictly less than <code>d</code> is returned.</p>
+     * <p>
+     * If <code>d</code> is NaN or Infinite, it is returned unchanged.</p>
+     *
+     * @param d base number
+     * @param direction (the only important thing is whether
+     * direction is greater or smaller than d)
+     * @return the next machine representable number in the specified direction
+     * @since 1.2
+     * @deprecated as of 2.2, replaced by {@link FastMath#nextAfter(double, double)}
+     * which handles Infinities differently, and returns direction if d and direction compare equal.
+     */
+    @Deprecated
+    public static double nextAfter(double d, double direction) {
+
+        // handling of some important special cases
+        if (Double.isNaN(d) || Double.isInfinite(d)) {
+                return d;
+        } 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
+
+        // split the double in raw components
+        long bits     = Double.doubleToLongBits(d);
+        long sign     = bits & 0x8000000000000000L;
+        long exponent = bits & 0x7ff0000000000000L;
+        long mantissa = bits & 0x000fffffffffffffL;
+
+        if (d * (direction - d) >= 0) {
+                // we should increase the mantissa
+                if (mantissa == 0x000fffffffffffffL) {
+                        return Double.longBitsToDouble(sign |
+                                        (exponent + 0x0010000000000000L));
+                } else {
+                        return Double.longBitsToDouble(sign |
+                                        exponent | (mantissa + 1));
+                }
+        } else {
+                // we should decrease the mantissa
+                if (mantissa == 0L) {
+                        return Double.longBitsToDouble(sign |
+                                        (exponent - 0x0010000000000000L) |
+                                        0x000fffffffffffffL);
+                } else {
+                        return Double.longBitsToDouble(sign |
+                                        exponent | (mantissa - 1));
+                }
+        }
+    }
+
+    /**
+     * Scale a number by 2<sup>scaleFactor</sup>.
+     * <p>If <code>d</code> is 0 or NaN or Infinite, it is returned unchanged.</p>
+     *
+     * @param d base number
+     * @param scaleFactor power of two by which d should be multiplied
+     * @return d &times; 2<sup>scaleFactor</sup>
+     * @since 2.0
+     * @deprecated as of 2.2, replaced by {@link FastMath#scalb(double, int)}
+     */
+    @Deprecated
+    public static double scalb(final double d, final int scaleFactor) {
+        return FastMath.scalb(d, scaleFactor);
+    }
+
+    /**
+     * Normalize an angle in a 2&pi wide interval around a center value.
+     * <p>This method has three main uses:</p>
+     * <ul>
+     *   <li>normalize an angle between 0 and 2&pi;:<br/>
+     *       <code>a = MathUtils.normalizeAngle(a, FastMath.PI);</code></li>
+     *   <li>normalize an angle between -&pi; and +&pi;<br/>
+     *       <code>a = MathUtils.normalizeAngle(a, 0.0);</code></li>
+     *   <li>compute the angle between two defining angular positions:<br>
+     *       <code>angle = MathUtils.normalizeAngle(end, start) - start;</code></li>
+     * </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.</p>
+     * @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);
+     }
+
+     /**
+      * <p>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>
+      *
+      * <p>Throws IllegalArgumentException if <code>normalizedSum</code> is infinite
+      * or NaN and ArithmeticException if the input array contains any infinite elements
+      * or sums to 0</p>
+      *
+      * <p>Ignores (i.e., copies unchanged to the output array) NaNs in the input array.</p>
+      *
+      * @param values input array to be normalized
+      * @param normalizedSum target sum for the normalized array
+      * @return normalized array
+      * @throws ArithmeticException if the input array contains infinite elements or sums to zero
+      * @throws IllegalArgumentException if the target sum is infinite or NaN
+      * @since 2.1
+      */
+     public static double[] normalizeArray(double[] values, double normalizedSum)
+       throws ArithmeticException, IllegalArgumentException {
+         if (Double.isInfinite(normalizedSum)) {
+             throw MathRuntimeException.createIllegalArgumentException(
+                     LocalizedFormats.NORMALIZE_INFINITE);
+         }
+         if (Double.isNaN(normalizedSum)) {
+             throw MathRuntimeException.createIllegalArgumentException(
+                     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 MathRuntimeException.createArithmeticException(
+                         LocalizedFormats.INFINITE_ARRAY_ELEMENT, values[i], i);
+             }
+             if (!Double.isNaN(values[i])) {
+                 sum += values[i];
+             }
+         }
+         if (sum == 0) {
+             throw MathRuntimeException.createArithmeticException(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;
+     }
+
+    /**
+     * Round the given value to the specified number of decimal places. The
+     * value is rounded using the {@link BigDecimal#ROUND_HALF_UP} method.
+     *
+     * @param x the value to round.
+     * @param scale the number of digits to the right of the decimal point.
+     * @return the rounded value.
+     * @since 1.1
+     */
+    public static double round(double x, int scale) {
+        return round(x, scale, BigDecimal.ROUND_HALF_UP);
+    }
+
+    /**
+     * Round 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 the value to round.
+     * @param scale the number of digits to the right of the decimal point.
+     * @param roundingMethod the rounding method as defined in
+     *        {@link BigDecimal}.
+     * @return the rounded value.
+     * @since 1.1
+     */
+    public static double round(double x, int scale, int roundingMethod) {
+        try {
+            return (new BigDecimal
+                   (Double.toString(x))
+                   .setScale(scale, roundingMethod))
+                   .doubleValue();
+        } catch (NumberFormatException ex) {
+            if (Double.isInfinite(x)) {
+                return x;
+            } else {
+                return Double.NaN;
+            }
+        }
+    }
+
+    /**
+     * Round the given value to the specified number of decimal places. The
+     * value is rounding using the {@link BigDecimal#ROUND_HALF_UP} method.
+     *
+     * @param x the value to round.
+     * @param scale the number of digits to the right of the decimal point.
+     * @return the rounded value.
+     * @since 1.1
+     */
+    public static float round(float x, int scale) {
+        return round(x, scale, BigDecimal.ROUND_HALF_UP);
+    }
+
+    /**
+     * Round 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 the value to round.
+     * @param scale the number of digits to the right of the decimal point.
+     * @param roundingMethod the rounding method as defined in
+     *        {@link BigDecimal}.
+     * @return the rounded value.
+     * @since 1.1
+     */
+    public static float round(float x, int scale, int roundingMethod) {
+        float sign = indicator(x);
+        float factor = (float)FastMath.pow(10.0f, scale) * sign;
+        return (float)roundUnscaled(x * factor, sign, roundingMethod) / factor;
+    }
+
+    /**
+     * Round 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 the value to round.
+     * @param sign the sign of the original, scaled value.
+     * @param roundingMethod the rounding method as defined in
+     *        {@link BigDecimal}.
+     * @return the rounded value.
+     * @since 1.1
+     */
+    private static double roundUnscaled(double unscaled, double sign,
+        int roundingMethod) {
+        switch (roundingMethod) {
+        case BigDecimal.ROUND_CEILING :
+            if (sign == -1) {
+                unscaled = FastMath.floor(nextAfter(unscaled, Double.NEGATIVE_INFINITY));
+            } else {
+                unscaled = FastMath.ceil(nextAfter(unscaled, Double.POSITIVE_INFINITY));
+            }
+            break;
+        case BigDecimal.ROUND_DOWN :
+            unscaled = FastMath.floor(nextAfter(unscaled, Double.NEGATIVE_INFINITY));
+            break;
+        case BigDecimal.ROUND_FLOOR :
+            if (sign == -1) {
+                unscaled = FastMath.ceil(nextAfter(unscaled, Double.POSITIVE_INFINITY));
+            } else {
+                unscaled = FastMath.floor(nextAfter(unscaled, Double.NEGATIVE_INFINITY));
+            }
+            break;
+        case BigDecimal.ROUND_HALF_DOWN : {
+            unscaled = 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(Math
+                    .floor(unscaled) / 2.0)) { // even
+                    unscaled = FastMath.floor(unscaled);
+                } else { // odd
+                    unscaled = FastMath.ceil(unscaled);
+                }
+            }
+            break;
+        }
+        case BigDecimal.ROUND_HALF_UP : {
+            unscaled = 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 ArithmeticException("Inexact result from rounding");
+            }
+            break;
+        case BigDecimal.ROUND_UP :
+            unscaled = FastMath.ceil(nextAfter(unscaled,  Double.POSITIVE_INFINITY));
+            break;
+        default :
+            throw MathRuntimeException.createIllegalArgumentException(
+                  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;
+    }
+
+    /**
+     * Returns the <a href="http://mathworld.wolfram.com/Sign.html"> sign</a>
+     * for byte value <code>x</code>.
+     * <p>
+     * For a byte value x, this method returns (byte)(+1) if x > 0, (byte)(0) if
+     * x = 0, and (byte)(-1) if x < 0.</p>
+     *
+     * @param x the value, a byte
+     * @return (byte)(+1), (byte)(0), or (byte)(-1), depending on the sign of x
+     */
+    public static byte sign(final byte x) {
+        return (x == ZB) ? ZB : (x > ZB) ? PB : NB;
+    }
+
+    /**
+     * Returns the <a href="http://mathworld.wolfram.com/Sign.html"> sign</a>
+     * for double precision <code>x</code>.
+     * <p>
+     * For a double value <code>x</code>, this method returns
+     * <code>+1.0</code> if <code>x > 0</code>, <code>0.0</code> if
+     * <code>x = 0.0</code>, and <code>-1.0</code> if <code>x < 0</code>.
+     * Returns <code>NaN</code> if <code>x</code> is <code>NaN</code>.</p>
+     *
+     * @param x the value, a double
+     * @return +1.0, 0.0, or -1.0, depending on the sign of x
+     */
+    public static double sign(final double x) {
+        if (Double.isNaN(x)) {
+            return Double.NaN;
+        }
+        return (x == 0.0) ? 0.0 : (x > 0.0) ? 1.0 : -1.0;
+    }
+
+    /**
+     * Returns the <a href="http://mathworld.wolfram.com/Sign.html"> sign</a>
+     * for float value <code>x</code>.
+     * <p>
+     * For a float value x, this method returns +1.0F if x > 0, 0.0F if x =
+     * 0.0F, and -1.0F if x < 0. Returns <code>NaN</code> if <code>x</code>
+     * is <code>NaN</code>.</p>
+     *
+     * @param x the value, a float
+     * @return +1.0F, 0.0F, or -1.0F, depending on the sign of x
+     */
+    public static float sign(final float x) {
+        if (Float.isNaN(x)) {
+            return Float.NaN;
+        }
+        return (x == 0.0F) ? 0.0F : (x > 0.0F) ? 1.0F : -1.0F;
+    }
+
+    /**
+     * Returns the <a href="http://mathworld.wolfram.com/Sign.html"> sign</a>
+     * for int value <code>x</code>.
+     * <p>
+     * For an int value x, this method returns +1 if x > 0, 0 if x = 0, and -1
+     * if x < 0.</p>
+     *
+     * @param x the value, an int
+     * @return +1, 0, or -1, depending on the sign of x
+     */
+    public static int sign(final int x) {
+        return (x == 0) ? 0 : (x > 0) ? 1 : -1;
+    }
+
+    /**
+     * Returns the <a href="http://mathworld.wolfram.com/Sign.html"> sign</a>
+     * for long value <code>x</code>.
+     * <p>
+     * For a long value x, this method returns +1L if x > 0, 0L if x = 0, and
+     * -1L if x < 0.</p>
+     *
+     * @param x the value, a long
+     * @return +1L, 0L, or -1L, depending on the sign of x
+     */
+    public static long sign(final long x) {
+        return (x == 0L) ? 0L : (x > 0L) ? 1L : -1L;
+    }
+
+    /**
+     * Returns the <a href="http://mathworld.wolfram.com/Sign.html"> sign</a>
+     * for short value <code>x</code>.
+     * <p>
+     * For a short value x, this method returns (short)(+1) if x > 0, (short)(0)
+     * if x = 0, and (short)(-1) if x < 0.</p>
+     *
+     * @param x the value, a short
+     * @return (short)(+1), (short)(0), or (short)(-1), depending on the sign of
+     *         x
+     */
+    public static short sign(final short x) {
+        return (x == ZS) ? ZS : (x > ZS) ? PS : NS;
+    }
+
+    /**
+     * Returns the <a href="http://mathworld.wolfram.com/HyperbolicSine.html">
+     * hyperbolic sine</a> of x.
+     *
+     * @param x double value for which to find the hyperbolic sine
+     * @return hyperbolic sine of x
+     */
+    public static double sinh(double x) {
+        return (FastMath.exp(x) - FastMath.exp(-x)) / 2.0;
+    }
+
+    /**
+     * Subtract two integers, checking for overflow.
+     *
+     * @param x the minuend
+     * @param y the subtrahend
+     * @return the difference <code>x-y</code>
+     * @throws ArithmeticException if the result can not be represented as an
+     *         int
+     * @since 1.1
+     */
+    public static int subAndCheck(int x, int y) {
+        long s = (long)x - (long)y;
+        if (s < Integer.MIN_VALUE || s > Integer.MAX_VALUE) {
+            throw MathRuntimeException.createArithmeticException(LocalizedFormats.OVERFLOW_IN_SUBTRACTION, x, y);
+        }
+        return (int)s;
+    }
+
+    /**
+     * Subtract two long integers, checking for overflow.
+     *
+     * @param a first value
+     * @param b second value
+     * @return the difference <code>a-b</code>
+     * @throws ArithmeticException if the result can not be represented as an
+     *         long
+     * @since 1.2
+     */
+    public static long subAndCheck(long a, long b) {
+        long ret;
+        String msg = "overflow: subtract";
+        if (b == Long.MIN_VALUE) {
+            if (a < 0) {
+                ret = a - b;
+            } else {
+                throw new ArithmeticException(msg);
+            }
+        } 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 null)
+     * @return k<sup>e</sup>
+     * @exception IllegalArgumentException if e is negative
+     */
+    public static int pow(final int k, int e)
+        throws IllegalArgumentException {
+
+        if (e < 0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                LocalizedFormats.POWER_NEGATIVE_PARAMETERS,
+                k, e);
+        }
+
+        int result = 1;
+        int k2p    = k;
+        while (e != 0) {
+            if ((e & 0x1) != 0) {
+                result *= k2p;
+            }
+            k2p *= k2p;
+            e = e >> 1;
+        }
+
+        return result;
+
+    }
+
+    /**
+     * Raise an int to a long power.
+     * @param k number to raise
+     * @param e exponent (must be positive or null)
+     * @return k<sup>e</sup>
+     * @exception IllegalArgumentException if e is negative
+     */
+    public static int pow(final int k, long e)
+        throws IllegalArgumentException {
+
+        if (e < 0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                LocalizedFormats.POWER_NEGATIVE_PARAMETERS,
+                k, e);
+        }
+
+        int result = 1;
+        int k2p    = k;
+        while (e != 0) {
+            if ((e & 0x1) != 0) {
+                result *= k2p;
+            }
+            k2p *= k2p;
+            e = e >> 1;
+        }
+
+        return result;
+
+    }
+
+    /**
+     * Raise a long to an int power.
+     * @param k number to raise
+     * @param e exponent (must be positive or null)
+     * @return k<sup>e</sup>
+     * @exception IllegalArgumentException if e is negative
+     */
+    public static long pow(final long k, int e)
+        throws IllegalArgumentException {
+
+        if (e < 0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                LocalizedFormats.POWER_NEGATIVE_PARAMETERS,
+                k, e);
+        }
+
+        long result = 1l;
+        long k2p    = k;
+        while (e != 0) {
+            if ((e & 0x1) != 0) {
+                result *= k2p;
+            }
+            k2p *= k2p;
+            e = e >> 1;
+        }
+
+        return result;
+
+    }
+
+    /**
+     * Raise a long to a long power.
+     * @param k number to raise
+     * @param e exponent (must be positive or null)
+     * @return k<sup>e</sup>
+     * @exception IllegalArgumentException if e is negative
+     */
+    public static long pow(final long k, long e)
+        throws IllegalArgumentException {
+
+        if (e < 0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                LocalizedFormats.POWER_NEGATIVE_PARAMETERS,
+                k, e);
+        }
+
+        long result = 1l;
+        long k2p    = k;
+        while (e != 0) {
+            if ((e & 0x1) != 0) {
+                result *= k2p;
+            }
+            k2p *= k2p;
+            e = e >> 1;
+        }
+
+        return result;
+
+    }
+
+    /**
+     * Raise a BigInteger to an int power.
+     * @param k number to raise
+     * @param e exponent (must be positive or null)
+     * @return k<sup>e</sup>
+     * @exception IllegalArgumentException if e is negative
+     */
+    public static BigInteger pow(final BigInteger k, int e)
+        throws IllegalArgumentException {
+
+        if (e < 0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                LocalizedFormats.POWER_NEGATIVE_PARAMETERS,
+                k, e);
+        }
+
+        return k.pow(e);
+
+    }
+
+    /**
+     * Raise a BigInteger to a long power.
+     * @param k number to raise
+     * @param e exponent (must be positive or null)
+     * @return k<sup>e</sup>
+     * @exception IllegalArgumentException if e is negative
+     */
+    public static BigInteger pow(final BigInteger k, long e)
+        throws IllegalArgumentException {
+
+        if (e < 0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                LocalizedFormats.POWER_NEGATIVE_PARAMETERS,
+                k, e);
+        }
+
+        BigInteger result = BigInteger.ONE;
+        BigInteger k2p    = k;
+        while (e != 0) {
+            if ((e & 0x1) != 0) {
+                result = result.multiply(k2p);
+            }
+            k2p = k2p.multiply(k2p);
+            e = e >> 1;
+        }
+
+        return result;
+
+    }
+
+    /**
+     * Raise a BigInteger to a BigInteger power.
+     * @param k number to raise
+     * @param e exponent (must be positive or null)
+     * @return k<sup>e</sup>
+     * @exception IllegalArgumentException if e is negative
+     */
+    public static BigInteger pow(final BigInteger k, BigInteger e)
+        throws IllegalArgumentException {
+
+        if (e.compareTo(BigInteger.ZERO) < 0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                LocalizedFormats.POWER_NEGATIVE_PARAMETERS,
+                k, 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;
+
+    }
+
+    /**
+     * 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
+     */
+    public static double distance1(double[] p1, double[] 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
+     */
+    public static int distance1(int[] p1, int[] 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
+     */
+    public static double distance(double[] p1, double[] 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>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
+     */
+    public static double distance(int[] p1, int[] 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
+     */
+    public static double distanceInf(double[] p1, double[] 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
+     */
+    public static int distanceInf(int[] p1, int[] 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 static enum OrderDirection {
+        /** Constant for increasing direction. */
+        INCREASING,
+        /** Constant for decreasing direction. */
+        DECREASING
+    }
+
+    /**
+     * Checks that the given array is sorted.
+     *
+     * @param val Values.
+     * @param dir Ordering direction.
+     * @param strict Whether the order should be strict.
+     * @throws NonMonotonousSequenceException if the array is not sorted.
+     * @since 2.2
+     */
+    public static void checkOrder(double[] val, OrderDirection dir, boolean strict) {
+        double previous = val[0];
+        boolean ok = true;
+
+        int max = val.length;
+        for (int i = 1; i < max; i++) {
+            switch (dir) {
+            case INCREASING:
+                if (strict) {
+                    if (val[i] <= previous) {
+                        ok = false;
+                    }
+                } else {
+                    if (val[i] < previous) {
+                        ok = false;
+                    }
+                }
+                break;
+            case DECREASING:
+                if (strict) {
+                    if (val[i] >= previous) {
+                        ok = false;
+                    }
+                } else {
+                    if (val[i] > previous) {
+                        ok = false;
+                    }
+                }
+                break;
+            default:
+                // Should never happen.
+                throw new IllegalArgumentException();
+            }
+
+            if (!ok) {
+                throw new NonMonotonousSequenceException(val[i], previous, i, dir, strict);
+            }
+            previous = val[i];
+        }
+    }
+
+    /**
+     * Checks that the given array is sorted in strictly increasing order.
+     *
+     * @param val Values.
+     * @throws NonMonotonousSequenceException if the array is not sorted.
+     * @since 2.2
+     */
+    public static void checkOrder(double[] val) {
+        checkOrder(val, OrderDirection.INCREASING, true);
+    }
+
+    /**
+     * Checks that the given array is sorted.
+     *
+     * @param val Values
+     * @param dir Order direction (-1 for decreasing, 1 for increasing)
+     * @param strict Whether the order should be strict
+     * @throws NonMonotonousSequenceException if the array is not sorted.
+     * @deprecated as of 2.2 (please use the new {@link #checkOrder(double[],OrderDirection,boolean)
+     * checkOrder} method). To be removed in 3.0.
+     */
+    @Deprecated
+    public static void checkOrder(double[] val, int dir, boolean strict) {
+        if (dir > 0) {
+            checkOrder(val, OrderDirection.INCREASING, strict);
+        } else {
+            checkOrder(val, OrderDirection.DECREASING, strict);
+        }
+    }
+
+    /**
+     * Returns the Cartesian norm (2-norm), handling both overflow and underflow.
+     * Translation of the minpack enorm subroutine.
+     *
+     * 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>
+     *
+     * @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.0;
+    double s2=0.0;
+    double s3=0.0;
+    double x1max = 0.0;
+    double x3max = 0.0;
+    double floatn = (double)v.length;
+    double agiant = rgiant/floatn;
+    for (int i=0;i<v.length;i++) {
+        double xabs = Math.abs(v[i]);
+        if (xabs<rdwarf || xabs>agiant) {
+            if (xabs>rdwarf) {
+                if (xabs>x1max) {
+                    double r=x1max/xabs;
+                    s1=1.0+s1*r*r;
+                    x1max=xabs;
+                } else {
+                    double r=xabs/x1max;
+                    s1+=r*r;
+                }
+            } else {
+                if (xabs>x3max) {
+                 double r=x3max/xabs;
+                 s3=1.0+s3*r*r;
+                 x3max=xabs;
+                } else {
+                    if (xabs!=0.0) {
+                        double r=xabs/x3max;
+                        s3+=r*r;
+                    }
+                }
+            }
+        } else {
+         s2+=xabs*xabs;
+        }
+    }
+    double norm;
+    if (s1!=0.0) {
+        norm = x1max*Math.sqrt(s1+(s2/x1max)/x1max);
+    } else {
+        if (s2==0.0) {
+            norm = x3max*Math.sqrt(s3);
+        } else {
+            if (s2>=x3max) {
+                norm = Math.sqrt(s2*(1.0+(x3max/s2)*(x3max*s3)));
+            } else {
+                norm = Math.sqrt(x3max*((s2/x3max)+(x3max*s3)));
+            }
+        }
+    }
+    return norm;
+}
+
+}
diff --git a/src/main/java/org/apache/commons/math/util/MultidimensionalCounter.java b/src/main/java/org/apache/commons/math/util/MultidimensionalCounter.java
new file mode 100644
index 0000000..752fb3d
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/util/MultidimensionalCounter.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.math.util;
+
+import org.apache.commons.math.exception.DimensionMismatchException;
+import org.apache.commons.math.exception.OutOfRangeException;
+import org.apache.commons.math.exception.NotStrictlyPositiveException;
+
+/**
+ * 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>
+ *  <li>(0, 0, 1) corresponds to 1</li>
+ *  <li>(0, 0, 2) corresponds to 2</li>
+ *  <li>(0, 1, 0) corresponds to 3</li>
+ *  <li>...</li>
+ *  <li>(1, 0, 0) corresponds to 12</li>
+ *  <li>...</li>
+ *  <li>(1, 3, 2) corresponds to 23</li>
+ * </ul>
+ * @version $Revision$ $Date$
+ * @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;
+
+        /**
+         * Create an iterator
+         * @see #iterator()
+         */
+        Iterator() {
+            counter[last] = -1;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public boolean hasNext() {
+            for (int i = 0; i < dimension; i++) {
+                if (counter[i] != size[i] - 1) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        /**
+         * @return the unidimensional count after the counter has been
+         * incremented by {@code 1}.
+         */
+        public Integer next() {
+            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 /* Arrays.*/ copyOf(counter, dimension); // Java 1.5 does not support Arrays.copyOf()
+        }
+
+        /**
+         * 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) {
+        dimension = size.length;
+        this.size = /* Arrays.*/ copyOf(size, dimension); // Java 1.5 does not support Arrays.copyOf()
+
+        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) {
+        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;
+        }
+
+        int idx = 1;
+        while (count < index) {
+            count += idx;
+            ++idx;
+        }
+        --idx;
+        indices[last] = idx;
+
+        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 {
+        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 /* Arrays.*/ copyOf(size, dimension); // Java 1.5 does not support Arrays.copyOf()
+    }
+
+    /**
+     * {@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();
+    }
+
+    /**
+     * Java 1.5 does not support Arrays.copyOf()
+     *
+     * @param source the array to be copied
+     * @param newLen the length of the copy to be returned
+     * @return the copied array, truncated or padded as necessary.
+     */
+     private int[] copyOf(int[] source, int newLen) {
+         int[] output = new int[newLen];
+         System.arraycopy(source, 0, output, 0, Math.min(source.length, newLen));
+         return output;
+     }
+
+}
diff --git a/src/main/java/org/apache/commons/math/util/NumberTransformer.java b/src/main/java/org/apache/commons/math/util/NumberTransformer.java
new file mode 100644
index 0000000..07d04db
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/util/NumberTransformer.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.math.util;
+
+import org.apache.commons.math.MathException;
+
+/**
+ * Subclasses implementing this interface can transform Objects to doubles.
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ *
+ * 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 MathException if the Object can not be transformed into a Double.
+     */
+    double transform(Object o) throws MathException;
+}
diff --git a/src/main/java/org/apache/commons/math/util/OpenIntToDoubleHashMap.java b/src/main/java/org/apache/commons/math/util/OpenIntToDoubleHashMap.java
new file mode 100644
index 0000000..400735f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/util/OpenIntToDoubleHashMap.java
@@ -0,0 +1,600 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.util;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+import java.util.ConcurrentModificationException;
+import java.util.NoSuchElementException;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * 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>
+ * <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.</p>
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ * @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. </p>
+     */
+    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. </p>
+     */
+    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.</p>
+     * @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) {
+                // 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 MathRuntimeException.createConcurrentModificationException(LocalizedFormats.MAP_MODIFIED_WHILE_ITERATING);
+            }
+            if (current < 0) {
+                throw MathRuntimeException.createNoSuchElementException(LocalizedFormats.ITERATOR_EXHAUSTED);
+            }
+            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 MathRuntimeException.createConcurrentModificationException(LocalizedFormats.MAP_MODIFIED_WHILE_ITERATING);
+            }
+            if (current < 0) {
+                throw MathRuntimeException.createNoSuchElementException(LocalizedFormats.ITERATOR_EXHAUSTED);
+            }
+            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 MathRuntimeException.createConcurrentModificationException(LocalizedFormats.MAP_MODIFIED_WHILE_ITERATING);
+            }
+
+            // advance on step
+            current = next;
+
+            // prepare next step
+            try {
+                while (states[++next] != FULL) {
+                    // nothing to do
+                }
+            } catch (ArrayIndexOutOfBoundsException e) {
+                next = -2;
+                if (current < 0) {
+                    throw MathRuntimeException.createNoSuchElementException(LocalizedFormats.ITERATOR_EXHAUSTED);
+                }
+            }
+
+        }
+
+    }
+
+    /**
+     * 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/math/util/OpenIntToFieldHashMap.java b/src/main/java/org/apache/commons/math/util/OpenIntToFieldHashMap.java
new file mode 100644
index 0000000..297ab44
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/util/OpenIntToFieldHashMap.java
@@ -0,0 +1,620 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.util;
+
+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;
+
+import org.apache.commons.math.Field;
+import org.apache.commons.math.FieldElement;
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * 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>
+ * <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.</p>
+ * @param <T> the type of the field elements
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ * @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. </p>
+     */
+    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. </p>
+     */
+    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.</p>
+     * @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) {
+                // 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 MathRuntimeException.createConcurrentModificationException(LocalizedFormats.MAP_MODIFIED_WHILE_ITERATING);
+            }
+            if (current < 0) {
+                throw MathRuntimeException.createNoSuchElementException(LocalizedFormats.ITERATOR_EXHAUSTED);
+            }
+            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 MathRuntimeException.createConcurrentModificationException(LocalizedFormats.MAP_MODIFIED_WHILE_ITERATING);
+            }
+            if (current < 0) {
+                throw MathRuntimeException.createNoSuchElementException(LocalizedFormats.ITERATOR_EXHAUSTED);
+            }
+            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 MathRuntimeException.createConcurrentModificationException(LocalizedFormats.MAP_MODIFIED_WHILE_ITERATING);
+            }
+
+            // advance on step
+            current = next;
+
+            // prepare next step
+            try {
+                while (states[++next] != FULL) {
+                    // nothing to do
+                }
+            } catch (ArrayIndexOutOfBoundsException e) {
+                next = -2;
+                if (current < 0) {
+                    throw MathRuntimeException.createNoSuchElementException(LocalizedFormats.ITERATOR_EXHAUSTED);
+                }
+            }
+
+        }
+
+    }
+
+    /**
+     * 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.getZero().getClass(), length);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/util/ResizableDoubleArray.java b/src/main/java/org/apache/commons/math/util/ResizableDoubleArray.java
new file mode 100644
index 0000000..b33f288
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/util/ResizableDoubleArray.java
@@ -0,0 +1,936 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.util;
+
+import java.io.Serializable;
+import java.util.Arrays;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * <p>
+ * A variable length {@link DoubleArray} implementation that automatically
+ * handles expanding and contracting its internal storage array as elements
+ * are added and removed.
+ * </p>
+ * <p>
+ *  The internal storage array starts with capacity determined by the
+ * <code>initialCapacity</code> 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</code> and <code>expansionFactor</code> properties.
+ * 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.
+ * </p>
+ * <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</code> property) and if the difference
+ * is too large, the internal array is contracted to size
+ * <code>numElements + 1.</code>  The determination of when the internal
+ * storage array is "too large" depends on the <code>expansionMode</code> and
+ * <code>contractionFactor</code> properties.  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>
+ * </p>
+ * <p>
+ * To avoid cycles of expansions and contractions, the
+ * <code>expansionFactor</code> must not exceed the
+ * <code>contractionFactor.</code> Constructors and mutators for both of these
+ * properties enforce this requirement, throwing IllegalArgumentException if it
+ * is violated.
+ * </p>
+ * @version $Revision: 1073474 $ $Date: 2011-02-22 20:52:17 +0100 (mar. 22 févr. 2011) $
+ */
+public class ResizableDoubleArray implements DoubleArray, Serializable {
+
+    /** additive expansion mode */
+    public static final int ADDITIVE_MODE = 1;
+
+    /** multiplicative expansion mode */
+    public static final int MULTIPLICATIVE_MODE = 0;
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -3485529955529426875L;
+
+    /**
+     * The contraction criteria determines when the internal array will be
+     * contracted to fit the number of elements contained in the element
+     *  array + 1.
+     */
+    protected float contractionCriteria = 2.5f;
+
+    /**
+     * The expansion factor of the array.  When the array needs to be expanded,
+     * the new array size will be
+     * <code>internalArray.length * expansionFactor</code>
+     * if <code>expansionMode</code> is set to MULTIPLICATIVE_MODE, or
+     * <code>internalArray.length + expansionFactor</code> if
+     * <code>expansionMode</code> is set to ADDITIVE_MODE.
+     */
+    protected float expansionFactor = 2.0f;
+
+    /**
+     * Determines whether array expansion by <code>expansionFactor</code>
+     * is additive or multiplicative.
+     */
+    protected int expansionMode = MULTIPLICATIVE_MODE;
+
+    /**
+     * The initial capacity of the array.  Initial capacity is not exposed as a
+     * property as it is only meaningful when passed to a constructor.
+     */
+    protected int initialCapacity = 16;
+
+    /**
+     * The internal storage array.
+     */
+    protected 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.
+     */
+    protected 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]
+     * </code>
+     */
+    protected int startIndex = 0;
+
+    /**
+     * Create a ResizableArray with default properties.
+     * <ul>
+     * <li><code>initialCapacity = 16</code></li>
+     * <li><code>expansionMode = MULTIPLICATIVE_MODE</code></li>
+     * <li><code>expansionFactor = 2.5</code></li>
+     * <li><code>contractionFactor = 2.0</code></li>
+     * </ul>
+     */
+    public ResizableDoubleArray() {
+        internalArray = new double[initialCapacity];
+    }
+
+    /**
+     * Create a ResizableArray with the specified initial capacity.  Other
+     * properties take default values:
+      * <ul>
+     * <li><code>expansionMode = MULTIPLICATIVE_MODE</code></li>
+     * <li><code>expansionFactor = 2.5</code></li>
+     * <li><code>contractionFactor = 2.0</code></li>
+     * </ul>
+     * @param initialCapacity The initial size of the internal storage array
+     * @throws IllegalArgumentException if initialCapacity is not > 0
+     */
+    public ResizableDoubleArray(int initialCapacity) {
+        setInitialCapacity(initialCapacity);
+        internalArray = new double[this.initialCapacity];
+    }
+
+    /**
+     * Create a ResizableArray from an existing double[] with the
+     * initial capacity and numElements corresponding to the size of
+     * the supplied 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</code></li>
+     * <li><code>expansionMode = MULTIPLICATIVE_MODE</code></li>
+     * <li><code>expansionFactor = 2.5</code></li>
+     * <li><code>contractionFactor = 2.0</code></li>
+     * </ul>
+     *
+     * @param initialArray initial array
+     * @since 2.2
+     */
+    public ResizableDoubleArray(double[] initialArray) {
+        if (initialArray == null) {
+            this.internalArray = new double[initialCapacity];
+        } else {
+            this.internalArray = new double[initialArray.length];
+            System.arraycopy(initialArray, 0, this.internalArray, 0, initialArray.length);
+            initialCapacity = initialArray.length;
+            numElements = initialArray.length;
+        }
+    }
+
+    /**
+     * <p>
+     * Create a ResizableArray with the specified initial capacity
+     * and expansion factor.  The remaining properties take default
+     * values:
+     * <ul>
+     * <li><code>expansionMode = MULTIPLICATIVE_MODE</code></li>
+     * <li><code>contractionFactor = 0.5 + expansionFactor</code></li>
+     * </ul></p>
+     * <p>
+     * Throws IllegalArgumentException if the following conditions are
+     * not met:
+     * <ul>
+     * <li><code>initialCapacity > 0</code></li>
+     * <li><code>expansionFactor > 1</code></li>
+     * </ul></p>
+     *
+     * @param initialCapacity The initial size of the internal storage array
+     * @param expansionFactor the array will be expanded based on this
+     *                        parameter
+     * @throws IllegalArgumentException if parameters are not valid
+     */
+    public ResizableDoubleArray(int initialCapacity, float expansionFactor) {
+        this.expansionFactor = expansionFactor;
+        setInitialCapacity(initialCapacity);
+        internalArray = new double[initialCapacity];
+        setContractionCriteria(expansionFactor +0.5f);
+    }
+
+    /**
+     * <p>
+     * Create a ResizableArray with the specified initialCapacity,
+     * expansionFactor, and contractionCriteria. The <code>expansionMode</code>
+     * will default to <code>MULTIPLICATIVE_MODE.</code></p>
+     * <p>
+     * Throws IllegalArgumentException if the following conditions are
+     * not met:
+     * <ul>
+     * <li><code>initialCapacity > 0</code></li>
+     * <li><code>expansionFactor > 1</code></li>
+     * <li><code>contractionFactor >= expansionFactor</code></li>
+     * </ul></p>
+     * @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.
+     * @throws IllegalArgumentException if parameters are not valid
+     */
+    public ResizableDoubleArray(int initialCapacity, float expansionFactor,
+        float contractionCriteria) {
+        this.expansionFactor = expansionFactor;
+        setContractionCriteria(contractionCriteria);
+        setInitialCapacity(initialCapacity);
+        internalArray = new double[initialCapacity];
+    }
+
+    /**
+     * <p>
+     * Create a ResizableArray with the specified properties.</p>
+    * <p>
+     * Throws IllegalArgumentException if the following conditions are
+     * not met:
+     * <ul>
+     * <li><code>initialCapacity > 0</code></li>
+     * <li><code>expansionFactor > 1</code></li>
+     * <li><code>contractionFactor >= expansionFactor</code></li>
+     * <li><code>expansionMode in {MULTIPLICATIVE_MODE, ADDITIVE_MODE}</code>
+     * </li>
+     * </ul></p>
+     *
+     * @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 IllegalArgumentException if parameters are not valid
+     */
+    public ResizableDoubleArray(int initialCapacity, float expansionFactor,
+            float contractionCriteria, int expansionMode) {
+        this.expansionFactor = expansionFactor;
+        setContractionCriteria(contractionCriteria);
+        setInitialCapacity(initialCapacity);
+        setExpansionMode(expansionMode);
+        internalArray = new double[initialCapacity];
+    }
+
+    /**
+     * 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 NullPointerException
+     * is thrown.
+     *
+     * @param original array to copy
+     * @since 2.0
+     */
+    public ResizableDoubleArray(ResizableDoubleArray original) {
+        copy(original, this);
+    }
+
+    /**
+     * Adds an element to the end of this expandable array.
+     *
+     * @param value to be added to end of array
+     */
+    public synchronized void addElement(double value) {
+        numElements++;
+        if ((startIndex + numElements) > internalArray.length) {
+            expand();
+        }
+        internalArray[startIndex + (numElements - 1)] = value;
+        if (shouldContract()) {
+            contract();
+        }
+    }
+
+    /**
+     * Adds several element to the end of this expandable array.
+     *
+     * @param 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;
+    }
+
+    /**
+     * <p>
+     * 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>
+     * <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.
+     * </p>
+     *
+     * @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
+     */
+    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 criteria
+        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), a MathRuntimeException is thrown.
+     *
+     * @param value new value to substitute for the most recently added value
+     * @return value that has been replaced in the array
+     * @since 2.0
+     */
+    public synchronized double substituteMostRecentElement(double value) {
+        if (numElements < 1) {
+            throw MathRuntimeException.createArrayIndexOutOfBoundsException(
+                    LocalizedFormats.CANNOT_SUBSTITUTE_ELEMENT_FROM_EMPTY_ARRAY);
+        }
+
+        double discarded = internalArray[startIndex + (numElements - 1)];
+
+        internalArray[startIndex + (numElements - 1)] = value;
+
+        return discarded;
+    }
+
+
+    /**
+     * Checks the expansion factor and the contraction criteria 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 IllegalArgumentException if the contractionCriteria is less than
+     *         the expansionCriteria.
+     */
+    protected void checkContractExpand(float contraction, float expansion) {
+
+        if (contraction < expansion) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.CONTRACTION_CRITERIA_SMALLER_THAN_EXPANSION_FACTOR,
+                    contraction, expansion);
+        }
+
+        if (contraction <= 1.0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.CONTRACTION_CRITERIA_SMALLER_THAN_ONE,
+                    contraction);
+        }
+
+        if (expansion <= 1.0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.EXPANSION_FACTOR_SMALLER_THAN_ONE,
+                    expansion);
+        }
+    }
+
+    /**
+     * Clear the array, reset the size to the initialCapacity and the number
+     * of elements to zero.
+     */
+    public synchronized void clear() {
+        numElements = 0;
+        startIndex = 0;
+        internalArray = new double[initialCapacity];
+    }
+
+    /**
+     * 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() {
+        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 IllegalArgumentException if i is greater than numElements.
+     * @since 2.0
+     */
+    public synchronized void discardFrontElements(int i) {
+
+        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 IllegalArgumentException if i is greater than numElements.
+     * @since 2.0
+     */
+    public synchronized void discardMostRecentElements(int i) {
+
+        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 IllegalArgumentException if i is greater than numElements.
+     * @since 2.0
+     */
+    private synchronized void discardExtremeElements(int i,boolean front) {
+        if (i > numElements) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.TOO_MANY_ELEMENTS_TO_DISCARD_FROM_ARRAY,
+                    i, numElements);
+       } else if (i < 0) {
+           throw MathRuntimeException.createIllegalArgumentException(
+                   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>
+     * </p>
+     */
+    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 == MULTIPLICATIVE_MODE) {
+            newSize = (int) FastMath.ceil(internalArray.length * expansionFactor);
+        } else {
+            newSize = internalArray.length + FastMath.round(expansionFactor);
+        }
+        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) {
+        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.
+     */
+    public float getContractionCriteria() {
+        return contractionCriteria;
+    }
+
+    /**
+     * 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 MathRuntimeException.createArrayIndexOutOfBoundsException(
+                    LocalizedFormats.INDEX_LARGER_THAN_MAX,
+                    index, numElements - 1);
+        } else if (index >= 0) {
+            return internalArray[startIndex + index];
+        } else {
+            throw MathRuntimeException.createArrayIndexOutOfBoundsException(
+                    LocalizedFormats.CANNOT_RETRIEVE_AT_NEGATIVE_INDEX,
+                    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() {
+        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
+     */
+    public float getExpansionFactor() {
+        return expansionFactor;
+    }
+
+    /**
+     * The <code>expansionMode</code> determines whether the internal storage
+     * array grows additively (ADDITIVE_MODE) or multiplicatively
+     * (MULTIPLICATIVE_MODE) when it is expanded.
+     *
+     * @return Returns the expansionMode.
+     */
+    public int getExpansionMode() {
+        return expansionMode;
+    }
+
+    /**
+     * 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.
+     */
+    synchronized int getInternalLength() {
+        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 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
+     * @deprecated replaced by {@link #getInternalValues()} as of 2.0
+     */
+    @Deprecated
+    public synchronized double[] getValues() {
+        return internalArray;
+    }
+
+    /**
+     * 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
+     */
+    public synchronized double[] getInternalValues() {
+        return internalArray;
+    }
+
+    /**
+     * Sets the contraction criteria for this ExpandContractDoubleArray.
+     *
+     * @param contractionCriteria contraction criteria
+     */
+    public void setContractionCriteria(float contractionCriteria) {
+        checkContractExpand(contractionCriteria, getExpansionFactor());
+        synchronized(this) {
+            this.contractionCriteria = contractionCriteria;
+        }
+    }
+
+
+    /**
+     * 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.
+     */
+    public synchronized void setElement(int index, double value) {
+        if (index < 0) {
+            throw MathRuntimeException.createArrayIndexOutOfBoundsException(
+                    LocalizedFormats.CANNOT_SET_AT_NEGATIVE_INDEX,
+                    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>
+     * <li><code>contractionFactor >= expansionFactor</code></li>
+     * </ul>
+     * @param expansionFactor the new expansion factor value.
+     * @throws IllegalArgumentException if expansionFactor is <= 1 or greater
+     * than contractionFactor
+     */
+    public void setExpansionFactor(float expansionFactor) {
+        checkContractExpand(getContractionCriteria(), 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 IllegalArgumentException if the specified mode value is not valid
+     */
+    public void setExpansionMode(int expansionMode) {
+        if (expansionMode != MULTIPLICATIVE_MODE &&
+                expansionMode != ADDITIVE_MODE) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.UNSUPPORTED_EXPANSION_MODE,
+                    expansionMode, MULTIPLICATIVE_MODE, "MULTIPLICATIVE_MODE",
+                    ADDITIVE_MODE, "ADDITIVE_MODE");
+        }
+        synchronized(this) {
+            this.expansionMode = expansionMode;
+        }
+    }
+
+    /**
+     * Sets the initial capacity.  Should only be invoked by constructors.
+     *
+     * @param initialCapacity of the array
+     * @throws IllegalArgumentException if <code>initialCapacity</code> is not
+     *         positive.
+     */
+    protected void setInitialCapacity(int initialCapacity) {
+        if (initialCapacity > 0) {
+            synchronized(this) {
+                this.initialCapacity = initialCapacity;
+            }
+        } else {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    LocalizedFormats.INITIAL_CAPACITY_NOT_POSITIVE,
+                    initialCapacity);
+        }
+    }
+
+    /**
+     * 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 IllegalArgumentException if <code>i</code> is negative.
+     */
+    public synchronized void setNumElements(int i) {
+
+        // If index is negative thrown an error
+        if (i < 0) {
+            throw MathRuntimeException.createIllegalArgumentException(
+                    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
+        if ((startIndex + i) > internalArray.length) {
+            expandTo(startIndex + i);
+        }
+
+        // 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 == MULTIPLICATIVE_MODE) {
+            return (internalArray.length / ((float) numElements)) > contractionCriteria;
+        } else {
+            return (internalArray.length - numElements) > contractionCriteria;
+        }
+    }
+
+    /**
+     * 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 starting index
+     */
+    public synchronized int start() {
+        return startIndex;
+    }
+
+    /**
+     * <p>Copies source to dest, copying the underlying data, so dest is
+     * a new, independent copy of source.  Does not contract before
+     * the copy.</p>
+     *
+     * <p>Obtains synchronization locks on both source and dest
+     * (in that order) before performing the copy.</p>
+     *
+     * <p>Neither source nor dest may be null; otherwise a NullPointerException
+     * is thrown</p>
+     *
+     * @param source ResizableDoubleArray to copy
+     * @param dest ResizableArray to replace with a copy of the source array
+     * @since 2.0
+     *
+     */
+    public static void copy(ResizableDoubleArray source, ResizableDoubleArray dest) {
+       synchronized(source) {
+           synchronized(dest) {
+               dest.initialCapacity = source.initialCapacity;
+               dest.contractionCriteria = source.contractionCriteria;
+               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() {
+        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;
+               ResizableDoubleArray other = (ResizableDoubleArray) object;
+               result = result && (other.initialCapacity == initialCapacity);
+               result = result && (other.contractionCriteria == contractionCriteria);
+               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 hash code representing this ResizableDoubleArray
+     * @since 2.0
+     */
+    @Override
+    public synchronized int hashCode() {
+        int[] hashData = new int[7];
+        hashData[0] = new Float(expansionFactor).hashCode();
+        hashData[1] = new Float(contractionCriteria).hashCode();
+        hashData[2] = expansionMode;
+            hashData[3] = Arrays.hashCode(internalArray);
+            hashData[4] = initialCapacity;
+            hashData[5] = numElements;
+            hashData[6] = startIndex;
+        return Arrays.hashCode(hashData);
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/math/util/TransformerMap.java b/src/main/java/org/apache/commons/math/util/TransformerMap.java
new file mode 100644
index 0000000..53d0b3a
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/util/TransformerMap.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.math.util;
+
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.math.MathException;
+
+/**
+ * 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.
+ * @version $Revision: 922713 $ $Date: 2010-03-14 02:26:13 +0100 (dim. 14 mars 2010) $
+ */
+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 MathException if the Object can not be transformed into a Double.
+     * @see org.apache.commons.math.util.NumberTransformer#transform(java.lang.Object)
+     */
+    public double transform(Object o) throws MathException {
+        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/math/util/package.html b/src/main/java/org/apache/commons/math/util/package.html
new file mode 100644
index 0000000..407b17f
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/util/package.html
@@ -0,0 +1,20 @@
+<html>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+    <!-- $Revision: 480440 $ $Date: 2006-11-29 08:14:12 +0100 (mer. 29 nov. 2006) $ -->
+    <body>Convenience routines and common data structures used throughout the commons-math library.</body>
+</html>