[commons-math] 01/06: Imported Upstream version 2.2

Markus Koschany apo at moszumanska.debian.org
Fri Aug 26 14:33:48 UTC 2016


This is an automated email from the git hooks/post-receive script.

apo pushed a commit to branch master
in repository commons-math.

commit 7d1f80ad1e18d8bf2615504d0a40fba545101f48
Author: Markus Koschany <apo at debian.org>
Date:   Fri Aug 26 16:12:24 2016 +0200

    Imported Upstream version 2.2
---
 LICENSE.txt                                        |  376 ++
 NOTICE.txt                                         |   56 +
 RELEASE-NOTES.txt                                  |  157 +
 build.xml                                          |  358 ++
 checkstyle.xml                                     |  178 +
 findbugs-exclude-filter.xml                        |  254 +
 license-header.txt                                 |   16 +
 pom.xml                                            |  364 ++
 src/main/assembly/bin.xml                          |   52 +
 src/main/assembly/src.xml                          |   39 +
 .../math/ArgumentOutsideDomainException.java       |   44 +
 .../apache/commons/math/ConvergenceException.java  |   99 +
 .../apache/commons/math/ConvergingAlgorithm.java   |  141 +
 .../commons/math/ConvergingAlgorithmImpl.java      |  155 +
 .../commons/math/DimensionMismatchException.java   |   67 +
 .../math/DuplicateSampleAbscissaException.java     |   51 +
 src/main/java/org/apache/commons/math/Field.java   |   51 +
 .../java/org/apache/commons/math/FieldElement.java |   60 +
 .../commons/math/FunctionEvaluationException.java  |  211 +
 .../commons/math/MathConfigurationException.java   |   94 +
 .../org/apache/commons/math/MathException.java     |  217 +
 .../apache/commons/math/MathRuntimeException.java  |  717 +++
 .../math/MaxEvaluationsExceededException.java      |   84 +
 .../math/MaxIterationsExceededException.java       |   84 +
 .../commons/math/analysis/BinaryFunction.java      |  120 +
 .../math/analysis/BivariateRealFunction.java       |   40 +
 .../commons/math/analysis/ComposableFunction.java  |  506 ++
 .../DifferentiableMultivariateRealFunction.java    |   51 +
 ...ifferentiableMultivariateVectorialFunction.java |   36 +
 .../DifferentiableUnivariateMatrixFunction.java    |   35 +
 .../DifferentiableUnivariateRealFunction.java      |   34 +
 .../DifferentiableUnivariateVectorialFunction.java |   35 +
 .../math/analysis/MultivariateMatrixFunction.java  |   40 +
 .../math/analysis/MultivariateRealFunction.java    |   39 +
 .../analysis/MultivariateVectorialFunction.java    |   39 +
 .../math/analysis/TrivariateRealFunction.java      |   40 +
 .../math/analysis/UnivariateMatrixFunction.java    |   37 +
 .../math/analysis/UnivariateRealFunction.java      |   36 +
 .../math/analysis/UnivariateVectorialFunction.java |   37 +
 .../integration/LegendreGaussIntegrator.java       |  236 +
 .../analysis/integration/RombergIntegrator.java    |  121 +
 .../analysis/integration/SimpsonIntegrator.java    |  112 +
 .../analysis/integration/TrapezoidIntegrator.java  |  142 +
 .../integration/UnivariateRealIntegrator.java      |  106 +
 .../integration/UnivariateRealIntegratorImpl.java  |  183 +
 .../commons/math/analysis/integration/package.html |   22 +
 .../BicubicSplineInterpolatingFunction.java        |  558 +++
 .../interpolation/BicubicSplineInterpolator.java   |  145 +
 .../BivariateRealGridInterpolator.java             |   44 +
 .../DividedDifferenceInterpolator.java             |  117 +
 .../analysis/interpolation/LinearInterpolator.java |   75 +
 .../analysis/interpolation/LoessInterpolator.java  |  463 ++
 .../MicrosphereInterpolatingFunction.java          |  244 +
 .../interpolation/MicrosphereInterpolator.java     |  118 +
 .../MultivariateRealInterpolator.java              |   46 +
 .../interpolation/NevilleInterpolator.java         |   54 +
 .../SmoothingBicubicSplineInterpolator.java        |  178 +
 ...oothingPolynomialBicubicSplineInterpolator.java |  143 +
 .../analysis/interpolation/SplineInterpolator.java |  127 +
 .../TricubicSplineInterpolatingFunction.java       |  483 ++
 .../interpolation/TricubicSplineInterpolator.java  |  198 +
 .../TrivariateRealGridInterpolator.java            |   49 +
 .../interpolation/UnivariateRealInterpolator.java  |   39 +
 .../math/analysis/interpolation/package.html       |   22 +
 .../org/apache/commons/math/analysis/package.html  |   33 +
 .../analysis/polynomials/PolynomialFunction.java   |  350 ++
 .../PolynomialFunctionLagrangeForm.java            |  305 ++
 .../polynomials/PolynomialFunctionNewtonForm.java  |  221 +
 .../polynomials/PolynomialSplineFunction.java      |  224 +
 .../analysis/polynomials/PolynomialsUtils.java     |  281 ++
 .../commons/math/analysis/polynomials/package.html |   23 +
 .../math/analysis/solvers/BisectionSolver.java     |  133 +
 .../commons/math/analysis/solvers/BrentSolver.java |  399 ++
 .../math/analysis/solvers/LaguerreSolver.java      |  434 ++
 .../math/analysis/solvers/MullerSolver.java        |  415 ++
 .../math/analysis/solvers/NewtonSolver.java        |  183 +
 .../math/analysis/solvers/RiddersSolver.java       |  247 +
 .../math/analysis/solvers/SecantSolver.java        |  230 +
 .../analysis/solvers/UnivariateRealSolver.java     |  165 +
 .../solvers/UnivariateRealSolverFactory.java       |   90 +
 .../solvers/UnivariateRealSolverFactoryImpl.java   |   64 +
 .../analysis/solvers/UnivariateRealSolverImpl.java |  304 ++
 .../solvers/UnivariateRealSolverUtils.java         |  240 +
 .../commons/math/analysis/solvers/package.html     |   22 +
 .../org/apache/commons/math/complex/Complex.java   | 1007 ++++
 .../apache/commons/math/complex/ComplexField.java  |   78 +
 .../apache/commons/math/complex/ComplexFormat.java |  383 ++
 .../apache/commons/math/complex/ComplexUtils.java  |   72 +
 .../org/apache/commons/math/complex/package.html   |   23 +
 src/main/java/org/apache/commons/math/dfp/Dfp.java | 2399 ++++++++++
 .../java/org/apache/commons/math/dfp/DfpDec.java   |  369 ++
 .../java/org/apache/commons/math/dfp/DfpField.java |  750 +++
 .../java/org/apache/commons/math/dfp/DfpMath.java  |  969 ++++
 .../java/org/apache/commons/math/dfp/package.html  |   88 +
 .../AbstractContinuousDistribution.java            |  231 +
 .../math/distribution/AbstractDistribution.java    |   69 +
 .../distribution/AbstractIntegerDistribution.java  |  319 ++
 .../math/distribution/BetaDistribution.java        |   65 +
 .../math/distribution/BetaDistributionImpl.java    |  284 ++
 .../math/distribution/BinomialDistribution.java    |   60 +
 .../distribution/BinomialDistributionImpl.java     |  279 ++
 .../math/distribution/CauchyDistribution.java      |   63 +
 .../math/distribution/CauchyDistributionImpl.java  |  320 ++
 .../math/distribution/ChiSquaredDistribution.java  |   53 +
 .../distribution/ChiSquaredDistributionImpl.java   |  324 ++
 .../math/distribution/ContinuousDistribution.java  |   43 +
 .../math/distribution/DiscreteDistribution.java    |   35 +
 .../commons/math/distribution/Distribution.java    |   56 +
 .../math/distribution/ExponentialDistribution.java |   53 +
 .../distribution/ExponentialDistributionImpl.java  |  319 ++
 .../commons/math/distribution/FDistribution.java   |   60 +
 .../math/distribution/FDistributionImpl.java       |  360 ++
 .../math/distribution/GammaDistribution.java       |   67 +
 .../math/distribution/GammaDistributionImpl.java   |  355 ++
 .../commons/math/distribution/HasDensity.java      |   45 +
 .../distribution/HypergeometricDistribution.java   |   76 +
 .../HypergeometricDistributionImpl.java            |  421 ++
 .../math/distribution/IntegerDistribution.java     |   84 +
 .../math/distribution/NormalDistribution.java      |   65 +
 .../math/distribution/NormalDistributionImpl.java  |  349 ++
 .../math/distribution/PascalDistribution.java      |   73 +
 .../math/distribution/PascalDistributionImpl.java  |  276 ++
 .../math/distribution/PoissonDistribution.java     |   64 +
 .../math/distribution/PoissonDistributionImpl.java |  343 ++
 .../math/distribution/SaddlePointExpansion.java    |  201 +
 .../commons/math/distribution/TDistribution.java   |   46 +
 .../math/distribution/TDistributionImpl.java       |  303 ++
 .../math/distribution/WeibullDistribution.java     |   66 +
 .../math/distribution/WeibullDistributionImpl.java |  378 ++
 .../math/distribution/ZipfDistribution.java        |   71 +
 .../math/distribution/ZipfDistributionImpl.java    |  286 ++
 .../apache/commons/math/distribution/package.html  |   20 +
 .../commons/math/estimation/AbstractEstimator.java |  318 ++
 .../math/estimation/EstimatedParameter.java        |  126 +
 .../math/estimation/EstimationException.java       |   61 +
 .../commons/math/estimation/EstimationProblem.java |   68 +
 .../apache/commons/math/estimation/Estimator.java  |   90 +
 .../math/estimation/GaussNewtonEstimator.java      |  231 +
 .../estimation/LevenbergMarquardtEstimator.java    |  897 ++++
 .../math/estimation/SimpleEstimationProblem.java   |  111 +
 .../math/estimation/WeightedMeasurement.java       |  172 +
 .../apache/commons/math/estimation/package.html    |   25 +
 .../math/exception/ConvergenceException.java       |   61 +
 .../math/exception/DimensionMismatchException.java |   53 +
 .../exception/MathIllegalArgumentException.java    |  112 +
 .../math/exception/MathIllegalNumberException.java |   74 +
 .../math/exception/MathIllegalStateException.java  |  140 +
 .../commons/math/exception/MathInternalError.java  |   50 +
 .../commons/math/exception/MathThrowable.java      |   62 +
 .../MathUnsupportedOperationException.java         |  106 +
 .../commons/math/exception/NoDataException.java    |   47 +
 .../exception/NonMonotonousSequenceException.java  |  123 +
 .../math/exception/NotPositiveException.java       |   50 +
 .../exception/NotStrictlyPositiveException.java    |   50 +
 .../math/exception/NullArgumentException.java      |   50 +
 .../math/exception/NumberIsTooLargeException.java  |   89 +
 .../math/exception/NumberIsTooSmallException.java  |   90 +
 .../math/exception/OutOfRangeException.java        |   63 +
 .../commons/math/exception/ZeroException.java      |   48 +
 .../org/apache/commons/math/exception/package.html |   23 +
 .../commons/math/exception/util/ArgUtils.java      |   58 +
 .../math/exception/util/DummyLocalizable.java      |   58 +
 .../commons/math/exception/util/Localizable.java   |   43 +
 .../math/exception/util/LocalizedFormats.java      |  345 ++
 .../math/exception/util/MessageFactory.java        |   78 +
 .../commons/math/exception/util/package.html       |   22 +
 .../commons/math/fraction/AbstractFormat.java      |  210 +
 .../apache/commons/math/fraction/BigFraction.java  | 1129 +++++
 .../commons/math/fraction/BigFractionField.java    |   78 +
 .../commons/math/fraction/BigFractionFormat.java   |  291 ++
 .../org/apache/commons/math/fraction/Fraction.java |  655 +++
 .../math/fraction/FractionConversionException.java |   56 +
 .../commons/math/fraction/FractionField.java       |   78 +
 .../commons/math/fraction/FractionFormat.java      |  274 ++
 .../math/fraction/ProperBigFractionFormat.java     |  239 +
 .../math/fraction/ProperFractionFormat.java        |  232 +
 .../org/apache/commons/math/fraction/package.html  |   22 +
 .../math/genetics/AbstractListChromosome.java      |  104 +
 .../commons/math/genetics/BinaryChromosome.java    |   92 +
 .../commons/math/genetics/BinaryMutation.java      |   52 +
 .../apache/commons/math/genetics/Chromosome.java   |  111 +
 .../commons/math/genetics/ChromosomePair.java      |   69 +
 .../commons/math/genetics/CrossoverPolicy.java     |   35 +
 .../math/genetics/ElitisticListPopulation.java     |  110 +
 .../org/apache/commons/math/genetics/Fitness.java  |   35 +
 .../math/genetics/FixedGenerationCount.java        |   70 +
 .../commons/math/genetics/GeneticAlgorithm.java    |  228 +
 .../genetics/InvalidRepresentationException.java   |   63 +
 .../commons/math/genetics/ListPopulation.java      |  155 +
 .../commons/math/genetics/MutationPolicy.java      |   33 +
 .../commons/math/genetics/OnePointCrossover.java   |  117 +
 .../math/genetics/PermutationChromosome.java       |   44 +
 .../apache/commons/math/genetics/Population.java   |   55 +
 .../apache/commons/math/genetics/RandomKey.java    |  290 ++
 .../commons/math/genetics/RandomKeyMutation.java   |   57 +
 .../commons/math/genetics/SelectionPolicy.java     |   32 +
 .../commons/math/genetics/StoppingCondition.java   |   35 +
 .../commons/math/genetics/TournamentSelection.java |  114 +
 .../org/apache/commons/math/genetics/package.html  |   24 +
 .../geometry/CardanEulerSingularityException.java  |   45 +
 .../math/geometry/NotARotationMatrixException.java |   60 +
 .../org/apache/commons/math/geometry/Rotation.java | 1072 +++++
 .../commons/math/geometry/RotationOrder.java       |  175 +
 .../org/apache/commons/math/geometry/Vector3D.java |  534 +++
 .../commons/math/geometry/Vector3DFormat.java      |  343 ++
 .../org/apache/commons/math/geometry/package.html  |   24 +
 .../commons/math/linear/AbstractFieldMatrix.java   | 1139 +++++
 .../commons/math/linear/AbstractRealMatrix.java    | 1071 +++++
 .../commons/math/linear/AbstractRealVector.java    |  932 ++++
 .../org/apache/commons/math/linear/AnyMatrix.java  |   48 +
 .../commons/math/linear/Array2DRowFieldMatrix.java |  613 +++
 .../commons/math/linear/Array2DRowRealMatrix.java  |  621 +++
 .../commons/math/linear/ArrayFieldVector.java      |  869 ++++
 .../commons/math/linear/ArrayRealVector.java       | 1228 +++++
 .../commons/math/linear/BiDiagonalTransformer.java |  381 ++
 .../org/apache/commons/math/linear/BigMatrix.java  |  331 ++
 .../apache/commons/math/linear/BigMatrixImpl.java  | 1505 ++++++
 .../commons/math/linear/BlockFieldMatrix.java      | 1670 +++++++
 .../commons/math/linear/BlockRealMatrix.java       | 1690 +++++++
 .../commons/math/linear/CholeskyDecomposition.java |   71 +
 .../math/linear/CholeskyDecompositionImpl.java     |  356 ++
 .../commons/math/linear/DecompositionSolver.java   |   84 +
 .../linear/DefaultFieldMatrixChangingVisitor.java  |   62 +
 .../DefaultFieldMatrixPreservingVisitor.java       |   62 +
 .../linear/DefaultRealMatrixChangingVisitor.java   |   49 +
 .../linear/DefaultRealMatrixPreservingVisitor.java |   49 +
 .../commons/math/linear/EigenDecomposition.java    |  137 +
 .../math/linear/EigenDecompositionImpl.java        |  619 +++
 .../math/linear/FieldDecompositionSolver.java      |   86 +
 .../commons/math/linear/FieldLUDecomposition.java  |   94 +
 .../math/linear/FieldLUDecompositionImpl.java      |  434 ++
 .../apache/commons/math/linear/FieldMatrix.java    |  798 +++
 .../math/linear/FieldMatrixChangingVisitor.java    |   63 +
 .../math/linear/FieldMatrixPreservingVisitor.java  |   62 +
 .../apache/commons/math/linear/FieldVector.java    |  358 ++
 .../math/linear/InvalidMatrixException.java        |   67 +
 .../commons/math/linear/LUDecomposition.java       |   92 +
 .../commons/math/linear/LUDecompositionImpl.java   |  423 ++
 .../commons/math/linear/MatrixIndexException.java  |   55 +
 .../apache/commons/math/linear/MatrixUtils.java    |  957 ++++
 .../math/linear/MatrixVisitorException.java        |   52 +
 .../math/linear/NonSquareMatrixException.java      |   42 +
 .../linear/NotPositiveDefiniteMatrixException.java |   43 +
 .../math/linear/NotSymmetricMatrixException.java   |   43 +
 .../commons/math/linear/OpenMapRealMatrix.java     |  292 ++
 .../commons/math/linear/OpenMapRealVector.java     |  915 ++++
 .../commons/math/linear/QRDecomposition.java       |   77 +
 .../commons/math/linear/QRDecompositionImpl.java   |  453 ++
 .../org/apache/commons/math/linear/RealMatrix.java |  871 ++++
 .../math/linear/RealMatrixChangingVisitor.java     |   62 +
 .../apache/commons/math/linear/RealMatrixImpl.java |  629 +++
 .../math/linear/RealMatrixPreservingVisitor.java   |   61 +
 .../org/apache/commons/math/linear/RealVector.java | 1005 ++++
 .../commons/math/linear/RealVectorFormat.java      |  341 ++
 .../math/linear/SingularMatrixException.java       |   40 +
 .../math/linear/SingularValueDecomposition.java    |  147 +
 .../linear/SingularValueDecompositionImpl.java     |  379 ++
 .../commons/math/linear/SparseFieldMatrix.java     |  190 +
 .../commons/math/linear/SparseFieldVector.java     |  657 +++
 .../commons/math/linear/SparseRealMatrix.java      |   29 +
 .../commons/math/linear/SparseRealVector.java      |   27 +
 .../math/linear/TriDiagonalTransformer.java        |  270 ++
 .../org/apache/commons/math/linear/package.html    |   20 +
 .../commons/math/ode/AbstractIntegrator.java       |  440 ++
 .../commons/math/ode/ContinuousOutputModel.java    |  379 ++
 .../commons/math/ode/DerivativeException.java      |   62 +
 .../ExtendedFirstOrderDifferentialEquations.java   |   66 +
 .../commons/math/ode/FirstOrderConverter.java      |  119 +
 .../math/ode/FirstOrderDifferentialEquations.java  |   66 +
 .../commons/math/ode/FirstOrderIntegrator.java     |   61 +
 .../commons/math/ode/IntegratorException.java      |   64 +
 .../commons/math/ode/MultistepIntegrator.java      |  412 ++
 .../org/apache/commons/math/ode/ODEIntegrator.java |  138 +
 .../math/ode/SecondOrderDifferentialEquations.java |   69 +
 .../commons/math/ode/SecondOrderIntegrator.java    |   60 +
 .../math/ode/events/CombinedEventsManager.java     |  249 +
 .../commons/math/ode/events/EventException.java    |   63 +
 .../commons/math/ode/events/EventHandler.java      |  183 +
 .../apache/commons/math/ode/events/EventState.java |  431 ++
 .../apache/commons/math/ode/events/package.html    |   96 +
 .../ode/jacobians/EventHandlerWithJacobians.java   |  226 +
 .../FirstOrderIntegratorWithJacobians.java         |  899 ++++
 .../math/ode/jacobians/ODEWithJacobians.java       |   54 +
 .../math/ode/jacobians/ParameterizedODE.java       |   48 +
 .../ode/jacobians/StepHandlerWithJacobians.java    |   97 +
 .../jacobians/StepInterpolatorWithJacobians.java   |  188 +
 .../apache/commons/math/ode/jacobians/package.html |   28 +
 .../ode/nonstiff/AdamsBashforthIntegrator.java     |  317 ++
 .../commons/math/ode/nonstiff/AdamsIntegrator.java |  131 +
 .../math/ode/nonstiff/AdamsMoultonIntegrator.java  |  414 ++
 .../ode/nonstiff/AdamsNordsieckTransformer.java    |  312 ++
 .../ode/nonstiff/AdaptiveStepsizeIntegrator.java   |  349 ++
 .../nonstiff/ClassicalRungeKuttaIntegrator.java    |   75 +
 .../ClassicalRungeKuttaStepInterpolator.java       |  110 +
 .../ode/nonstiff/DormandPrince54Integrator.java    |  158 +
 .../nonstiff/DormandPrince54StepInterpolator.java  |  214 +
 .../ode/nonstiff/DormandPrince853Integrator.java   |  283 ++
 .../nonstiff/DormandPrince853StepInterpolator.java |  480 ++
 .../ode/nonstiff/EmbeddedRungeKuttaIntegrator.java |  379 ++
 .../commons/math/ode/nonstiff/EulerIntegrator.java |   72 +
 .../math/ode/nonstiff/EulerStepInterpolator.java   |   91 +
 .../commons/math/ode/nonstiff/GillIntegrator.java  |   74 +
 .../math/ode/nonstiff/GillStepInterpolator.java    |  125 +
 .../ode/nonstiff/GraggBulirschStoerIntegrator.java |  963 ++++
 .../GraggBulirschStoerStepInterpolator.java        |  401 ++
 .../math/ode/nonstiff/HighamHall54Integrator.java  |  132 +
 .../ode/nonstiff/HighamHall54StepInterpolator.java |  103 +
 .../math/ode/nonstiff/MidpointIntegrator.java      |   68 +
 .../ode/nonstiff/MidpointStepInterpolator.java     |  100 +
 .../math/ode/nonstiff/RungeKuttaIntegrator.java    |  197 +
 .../ode/nonstiff/RungeKuttaStepInterpolator.java   |  183 +
 .../math/ode/nonstiff/ThreeEighthesIntegrator.java |   72 +
 .../nonstiff/ThreeEighthesStepInterpolator.java    |  116 +
 .../apache/commons/math/ode/nonstiff/package.html  |   25 +
 .../java/org/apache/commons/math/ode/package.html  |  167 +
 .../ode/sampling/AbstractStepInterpolator.java     |  519 ++
 .../math/ode/sampling/DummyStepHandler.java        |  101 +
 .../math/ode/sampling/DummyStepInterpolator.java   |  150 +
 .../math/ode/sampling/FixedStepHandler.java        |   62 +
 .../ode/sampling/NordsieckStepInterpolator.java    |  291 ++
 .../commons/math/ode/sampling/StepHandler.java     |   78 +
 .../math/ode/sampling/StepInterpolator.java        |  132 +
 .../commons/math/ode/sampling/StepNormalizer.java  |  161 +
 .../apache/commons/math/ode/sampling/package.html  |   60 +
 .../DifferentiableMultivariateRealOptimizer.java   |  112 +
 ...fferentiableMultivariateVectorialOptimizer.java |  114 +
 .../apache/commons/math/optimization/GoalType.java |   35 +
 .../math/optimization/LeastSquaresConverter.java   |  193 +
 ...artDifferentiableMultivariateRealOptimizer.java |  228 +
 ...fferentiableMultivariateVectorialOptimizer.java |  238 +
 .../MultiStartMultivariateRealOptimizer.java       |  216 +
 .../MultiStartUnivariateRealOptimizer.java         |  318 ++
 .../optimization/MultivariateRealOptimizer.java    |  101 +
 .../math/optimization/OptimizationException.java   |   68 +
 .../math/optimization/RealConvergenceChecker.java  |   55 +
 .../math/optimization/RealPointValuePair.java      |   88 +
 .../math/optimization/SimpleRealPointChecker.java  |   87 +
 .../optimization/SimpleScalarValueChecker.java     |   81 +
 .../optimization/SimpleVectorialPointChecker.java  |   90 +
 .../optimization/SimpleVectorialValueChecker.java  |   90 +
 .../math/optimization/UnivariateRealOptimizer.java |  116 +
 .../optimization/VectorialConvergenceChecker.java  |   55 +
 .../math/optimization/VectorialPointValuePair.java |  100 +
 .../optimization/direct/DirectSearchOptimizer.java |  418 ++
 .../math/optimization/direct/MultiDirectional.java |  144 +
 .../math/optimization/direct/NelderMead.java       |  181 +
 .../math/optimization/direct/PowellOptimizer.java  |  298 ++
 .../commons/math/optimization/direct/package.html  |   24 +
 .../math/optimization/fitting/CurveFitter.java     |  197 +
 .../fitting/GaussianDerivativeFunction.java        |  104 +
 .../math/optimization/fitting/GaussianFitter.java  |  117 +
 .../optimization/fitting/GaussianFunction.java     |  159 +
 .../fitting/GaussianParametersGuesser.java         |  271 ++
 .../fitting/HarmonicCoefficientsGuesser.java       |  300 ++
 .../math/optimization/fitting/HarmonicFitter.java  |  133 +
 .../optimization/fitting/HarmonicFunction.java     |   80 +
 .../fitting/ParametricGaussianFunction.java        |  165 +
 .../fitting/ParametricRealFunction.java            |   50 +
 .../optimization/fitting/PolynomialFitter.java     |  108 +
 .../fitting/WeightedObservedPoint.java             |   75 +
 .../commons/math/optimization/fitting/package.html |   30 +
 .../general/AbstractLeastSquaresOptimizer.java     |  374 ++
 .../AbstractScalarDifferentiableOptimizer.java     |  207 +
 .../general/ConjugateGradientFormula.java          |   49 +
 .../optimization/general/GaussNewtonOptimizer.java |  135 +
 .../general/LevenbergMarquardtOptimizer.java       |  888 ++++
 .../NonLinearConjugateGradientOptimizer.java       |  294 ++
 .../math/optimization/general/Preconditioner.java  |   52 +
 .../commons/math/optimization/general/package.html |   22 +
 .../linear/AbstractLinearOptimizer.java            |  130 +
 .../math/optimization/linear/LinearConstraint.java |  233 +
 .../linear/LinearObjectiveFunction.java            |  147 +
 .../math/optimization/linear/LinearOptimizer.java  |   89 +
 .../linear/NoFeasibleSolutionException.java        |   41 +
 .../math/optimization/linear/Relationship.java     |   67 +
 .../math/optimization/linear/SimplexSolver.java    |  185 +
 .../math/optimization/linear/SimplexTableau.java   |  589 +++
 .../linear/UnboundedSolutionException.java         |   41 +
 .../commons/math/optimization/linear/package.html  |   22 +
 .../apache/commons/math/optimization/package.html  |   72 +
 .../AbstractUnivariateRealOptimizer.java           |  275 ++
 .../optimization/univariate/BracketFinder.java     |  295 ++
 .../optimization/univariate/BrentOptimizer.java    |  227 +
 .../math/optimization/univariate/package.html      |   22 +
 src/main/java/org/apache/commons/math/package.html |   20 +
 .../math/random/AbstractRandomGenerator.java       |  272 ++
 .../apache/commons/math/random/AbstractWell.java   |  186 +
 .../commons/math/random/BitsStreamGenerator.java   |  153 +
 .../random/CorrelatedRandomVectorGenerator.java    |  304 ++
 .../commons/math/random/EmpiricalDistribution.java |  133 +
 .../math/random/EmpiricalDistributionImpl.java     |  479 ++
 .../math/random/GaussianRandomGenerator.java       |   47 +
 .../commons/math/random/JDKRandomGenerator.java    |   50 +
 .../commons/math/random/MersenneTwister.java       |  259 +
 .../math/random/NormalizedRandomGenerator.java     |   38 +
 .../apache/commons/math/random/RandomAdaptor.java  |  198 +
 .../org/apache/commons/math/random/RandomData.java |  272 ++
 .../apache/commons/math/random/RandomDataImpl.java |  966 ++++
 .../commons/math/random/RandomGenerator.java       |  148 +
 .../commons/math/random/RandomVectorGenerator.java |   35 +
 .../random/UncorrelatedRandomVectorGenerator.java  |   93 +
 .../math/random/UniformRandomGenerator.java        |   61 +
 .../random/UnitSphereRandomVectorGenerator.java    |   84 +
 .../apache/commons/math/random/ValueServer.java    |  385 ++
 .../org/apache/commons/math/random/Well1024a.java  |  106 +
 .../org/apache/commons/math/random/Well19937a.java |  108 +
 .../org/apache/commons/math/random/Well19937c.java |  115 +
 .../org/apache/commons/math/random/Well44497a.java |  111 +
 .../org/apache/commons/math/random/Well44497b.java |  119 +
 .../org/apache/commons/math/random/Well512a.java   |  107 +
 .../org/apache/commons/math/random/package.html    |  132 +
 .../java/org/apache/commons/math/special/Beta.java |  202 +
 .../java/org/apache/commons/math/special/Erf.java  |   92 +
 .../org/apache/commons/math/special/Gamma.java     |  339 ++
 .../org/apache/commons/math/special/package.html   |   20 +
 .../org/apache/commons/math/stat/Frequency.java    |  603 +++
 .../org/apache/commons/math/stat/StatUtils.java    |  663 +++
 .../commons/math/stat/clustering/Cluster.java      |   74 +
 .../commons/math/stat/clustering/Clusterable.java  |   46 +
 .../stat/clustering/EuclideanIntegerPoint.java     |  120 +
 .../stat/clustering/KMeansPlusPlusClusterer.java   |  333 ++
 .../commons/math/stat/clustering/package.html      |   20 +
 .../commons/math/stat/correlation/Covariance.java  |  274 ++
 .../math/stat/correlation/PearsonsCorrelation.java |  285 ++
 .../stat/correlation/SpearmansCorrelation.java     |  172 +
 .../commons/math/stat/correlation/package.html     |   22 +
 .../AbstractStorelessUnivariateStatistic.java      |  183 +
 .../descriptive/AbstractUnivariateStatistic.java   |  232 +
 .../descriptive/AggregateSummaryStatistics.java    |  416 ++
 .../stat/descriptive/DescriptiveStatistics.java    |  721 +++
 .../descriptive/MultivariateSummaryStatistics.java |  637 +++
 .../StatisticalMultivariateSummary.java            |  120 +
 .../math/stat/descriptive/StatisticalSummary.java  |   65 +
 .../stat/descriptive/StatisticalSummaryValues.java |  186 +
 .../descriptive/StorelessUnivariateStatistic.java  |   86 +
 .../math/stat/descriptive/SummaryStatistics.java   |  717 +++
 .../SynchronizedDescriptiveStatistics.java         |  172 +
 .../SynchronizedMultivariateSummaryStatistics.java |  299 ++
 .../descriptive/SynchronizedSummaryStatistics.java |  333 ++
 .../math/stat/descriptive/UnivariateStatistic.java |   53 +
 .../math/stat/descriptive/WeightedEvaluation.java  |   49 +
 .../math/stat/descriptive/moment/FirstMoment.java  |  160 +
 .../math/stat/descriptive/moment/FourthMoment.java |  142 +
 .../stat/descriptive/moment/GeometricMean.java     |  205 +
 .../math/stat/descriptive/moment/Kurtosis.java     |  222 +
 .../commons/math/stat/descriptive/moment/Mean.java |  272 ++
 .../math/stat/descriptive/moment/SecondMoment.java |  124 +
 .../math/stat/descriptive/moment/SemiVariance.java |  379 ++
 .../math/stat/descriptive/moment/Skewness.java     |  213 +
 .../stat/descriptive/moment/StandardDeviation.java |  271 ++
 .../math/stat/descriptive/moment/ThirdMoment.java  |  139 +
 .../math/stat/descriptive/moment/Variance.java     |  610 +++
 .../descriptive/moment/VectorialCovariance.java    |  152 +
 .../stat/descriptive/moment/VectorialMean.java     |  103 +
 .../math/stat/descriptive/moment/package.html      |   20 +
 .../commons/math/stat/descriptive/package.html     |   41 +
 .../commons/math/stat/descriptive/rank/Max.java    |  163 +
 .../commons/math/stat/descriptive/rank/Median.java |   55 +
 .../commons/math/stat/descriptive/rank/Min.java    |  163 +
 .../math/stat/descriptive/rank/Percentile.java     |  497 ++
 .../math/stat/descriptive/rank/package.html        |   20 +
 .../math/stat/descriptive/summary/Product.java     |  224 +
 .../commons/math/stat/descriptive/summary/Sum.java |  220 +
 .../math/stat/descriptive/summary/SumOfLogs.java   |  165 +
 .../stat/descriptive/summary/SumOfSquares.java     |  154 +
 .../math/stat/descriptive/summary/package.html     |   20 +
 .../commons/math/stat/inference/ChiSquareTest.java |  222 +
 .../math/stat/inference/ChiSquareTestImpl.java     |  424 ++
 .../commons/math/stat/inference/OneWayAnova.java   |  103 +
 .../math/stat/inference/OneWayAnovaImpl.java       |  210 +
 .../apache/commons/math/stat/inference/TTest.java  |  771 +++
 .../commons/math/stat/inference/TTestImpl.java     | 1069 +++++
 .../commons/math/stat/inference/TestUtils.java     |  436 ++
 .../UnknownDistributionChiSquareTest.java          |  144 +
 .../commons/math/stat/inference/package.html       |   23 +
 .../java/org/apache/commons/math/stat/package.html |   20 +
 .../commons/math/stat/ranking/NaNStrategy.java     |   49 +
 .../commons/math/stat/ranking/NaturalRanking.java  |  464 ++
 .../math/stat/ranking/RankingAlgorithm.java        |   41 +
 .../commons/math/stat/ranking/TiesStrategy.java    |   55 +
 .../apache/commons/math/stat/ranking/package.html  |   22 +
 .../AbstractMultipleLinearRegression.java          |  366 ++
 .../regression/GLSMultipleLinearRegression.java    |  136 +
 .../stat/regression/MultipleLinearRegression.java  |   70 +
 .../regression/OLSMultipleLinearRegression.java    |  233 +
 .../math/stat/regression/SimpleRegression.java     |  639 +++
 .../commons/math/stat/regression/package.html      |   22 +
 .../math/transform/FastCosineTransformer.java      |  262 +
 .../math/transform/FastFourierTransformer.java     |  912 ++++
 .../math/transform/FastHadamardTransformer.java    |  252 +
 .../math/transform/FastSineTransformer.java        |  252 +
 .../commons/math/transform/RealTransformer.java    |   80 +
 .../org/apache/commons/math/transform/package.html |   22 +
 .../java/org/apache/commons/math/util/BigReal.java |  292 ++
 .../org/apache/commons/math/util/BigRealField.java |   78 +
 .../apache/commons/math/util/CompositeFormat.java  |  220 +
 .../commons/math/util/ContinuedFraction.java       |  208 +
 .../commons/math/util/DefaultTransformer.java      |   80 +
 .../org/apache/commons/math/util/DoubleArray.java  |  104 +
 .../org/apache/commons/math/util/FastMath.java     | 4047 ++++++++++++++++
 .../org/apache/commons/math/util/MathUtils.java    | 2265 +++++++++
 .../commons/math/util/MultidimensionalCounter.java |  316 ++
 .../commons/math/util/NumberTransformer.java       |   39 +
 .../commons/math/util/OpenIntToDoubleHashMap.java  |  600 +++
 .../commons/math/util/OpenIntToFieldHashMap.java   |  620 +++
 .../commons/math/util/ResizableDoubleArray.java    |  936 ++++
 .../apache/commons/math/util/TransformerMap.java   |  189 +
 .../java/org/apache/commons/math/util/package.html |   20 +
 .../localization/LocalizedFormats_fr.properties    |  274 ++
 src/main/resources/templates/math-release-notes.vm |  121 +
 src/site/resources/images/math.gif                 |  Bin 0 -> 15169 bytes
 src/site/resources/style/project.css               |    1 +
 .../userguide/TrajectoryDeterminationProblem.java  |  187 +
 .../userguide/estimation-class-diagram.png         |  Bin 0 -> 20220 bytes
 .../userguide/estimation-sequence-diagram.png      |  Bin 0 -> 36021 bytes
 src/site/site.xml                                  |   71 +
 src/site/xdoc/changes.xml                          | 1312 +++++
 src/site/xdoc/developers.xml                       |  311 ++
 src/site/xdoc/download_math.xml                    |  138 +
 src/site/xdoc/index.xml                            |   86 +
 src/site/xdoc/issue-tracking.xml                   |  102 +
 src/site/xdoc/mail-lists.xml                       |  202 +
 src/site/xdoc/proposal.xml                         |  132 +
 src/site/xdoc/userguide/analysis.xml               |  413 ++
 src/site/xdoc/userguide/complex.xml                |  150 +
 src/site/xdoc/userguide/distribution.xml           |   95 +
 src/site/xdoc/userguide/fraction.xml               |  110 +
 src/site/xdoc/userguide/genetics.xml               |  135 +
 src/site/xdoc/userguide/geometry.xml               |  141 +
 src/site/xdoc/userguide/index.xml                  |  147 +
 src/site/xdoc/userguide/linear.xml                 |  205 +
 src/site/xdoc/userguide/ode.xml                    |  440 ++
 src/site/xdoc/userguide/optimization.xml           |  271 ++
 src/site/xdoc/userguide/overview.xml               |  163 +
 src/site/xdoc/userguide/random.xml                 |  538 +++
 src/site/xdoc/userguide/special.xml                |   70 +
 src/site/xdoc/userguide/stat.xml                   | 1061 ++++
 src/site/xdoc/userguide/transform.xml              |   46 +
 src/site/xdoc/userguide/utilities.xml              |  195 +
 src/site/xdoc/userguide/xdoc.xsl                   |   68 +
 src/test/R/ChiSquareDistributionTestCases.R        |   97 +
 src/test/R/FDistributionTestCases.R                |   92 +
 src/test/R/GammaDistributionTestCases.R            |   92 +
 src/test/R/README.txt                              |  168 +
 src/test/R/TDistributionTestCases.R                |  100 +
 src/test/R/TTestCases                              |  106 +
 src/test/R/WeibullDistributionTestCases.R          |   92 +
 src/test/R/anovaTestCases                          |   72 +
 src/test/R/binomialTestCases                       |  127 +
 src/test/R/cauchyTestCases.R                       |   97 +
 src/test/R/chiSquareTestCases                      |  101 +
 src/test/R/correlationTestCases                    |  225 +
 src/test/R/covarianceTestCases                     |  146 +
 src/test/R/descriptiveTestCases                    |   83 +
 src/test/R/exponentialTestCases                    |  103 +
 src/test/R/hypergeometricTestCases                 |  136 +
 src/test/R/multipleOLSRegressionTestCases          |  309 ++
 src/test/R/normalTestCases                         |  111 +
 src/test/R/pascalTestCases                         |  135 +
 src/test/R/poissonTestCases                        |  116 +
 src/test/R/regressionTestCases                     |  159 +
 src/test/R/testAll                                 |   67 +
 src/test/R/testFunctions                           |   86 +
 .../math/ArgumentOutsideDomainExceptionTest.java   |   40 +
 .../commons/math/ConvergenceExceptionTest.java     |   76 +
 .../math/DuplicateSampleAbscissaExceptionTest.java |   38 +
 .../math/FunctionEvaluationExceptionTest.java      |  140 +
 .../math/MathConfigurationExceptionTest.java       |   75 +
 .../org/apache/commons/math/MathExceptionTest.java |  142 +
 .../math/MaxIterationsExceededExceptionTest.java   |   53 +
 .../org/apache/commons/math/RetryTestCase.java     |   56 +
 .../java/org/apache/commons/math/TestUtils.java    |  516 ++
 .../commons/math/analysis/BinaryFunctionTest.java  |   77 +
 .../math/analysis/ComposableFunctionTest.java      |  147 +
 .../commons/math/analysis/Expm1Function.java       |   39 +
 .../commons/math/analysis/MonitoredFunction.java   |   48 +
 .../commons/math/analysis/QuinticFunction.java     |   40 +
 .../apache/commons/math/analysis/SinFunction.java  |   50 +
 .../apache/commons/math/analysis/SincFunction.java |   50 +
 .../commons/math/analysis/SumSincFunction.java     |   84 +
 .../integration/LegendreGaussIntegratorTest.java   |  114 +
 .../integration/RombergIntegratorTest.java         |  114 +
 .../integration/SimpsonIntegratorTest.java         |  113 +
 .../integration/TrapezoidIntegratorTest.java       |  113 +
 .../BicubicSplineInterpolatingFunctionTest.java    |  448 ++
 .../BicubicSplineInterpolatorTest.java             |  170 +
 .../DividedDifferenceInterpolatorTest.java         |  143 +
 .../interpolation/LinearInterpolatorTest.java      |  148 +
 .../interpolation/LoessInterpolatorTest.java       |  255 +
 .../interpolation/MicrosphereInterpolatorTest.java |  133 +
 .../interpolation/NevilleInterpolatorTest.java     |  144 +
 .../SmoothingBicubicSplineInterpolatorTest.java    |  179 +
 ...ingPolynomialBicubicSplineInterpolatorTest.java |  180 +
 .../interpolation/SplineInterpolatorTest.java      |  221 +
 .../TricubicSplineInterpolatingFunctionTest.java   |  546 +++
 .../TricubicSplineInterpolatorTest.java            |  212 +
 .../PolynomialFunctionLagrangeFormTest.java        |  150 +
 .../PolynomialFunctionNewtonFormTest.java          |  149 +
 .../polynomials/PolynomialFunctionTest.java        |  271 ++
 .../polynomials/PolynomialSplineFunctionTest.java  |  148 +
 .../analysis/polynomials/PolynomialsUtilsTest.java |  218 +
 .../math/analysis/solvers/BisectionSolverTest.java |  194 +
 .../math/analysis/solvers/BrentSolverTest.java     |  389 ++
 .../math/analysis/solvers/LaguerreSolverTest.java  |  199 +
 .../math/analysis/solvers/MullerSolverTest.java    |  271 ++
 .../math/analysis/solvers/NewtonSolverTest.java    |  109 +
 .../math/analysis/solvers/RiddersSolverTest.java   |  160 +
 .../UnivariateRealSolverFactoryImplTest.java       |   75 +
 .../solvers/UnivariateRealSolverUtilsTest.java     |  141 +
 .../commons/math/complex/ComplexFieldTest.java     |   44 +
 .../math/complex/ComplexFormatAbstractTest.java    |  362 ++
 .../commons/math/complex/ComplexFormatTest.java    |   33 +
 .../apache/commons/math/complex/ComplexTest.java   |  996 ++++
 .../commons/math/complex/ComplexUtilsTest.java     |  104 +
 .../math/complex/FrenchComplexFormatTest.java      |   34 +
 .../org/apache/commons/math/dfp/Decimal10.java     |   99 +
 .../org/apache/commons/math/dfp/DfpDecTest.java    |  565 +++
 .../org/apache/commons/math/dfp/DfpMathTest.java   |  587 +++
 .../java/org/apache/commons/math/dfp/DfpTest.java  | 1507 ++++++
 .../math/distribution/BetaDistributionTest.java    |  303 ++
 .../distribution/BinomialDistributionTest.java     |  130 +
 .../math/distribution/CauchyDistributionTest.java  |  129 +
 .../distribution/ChiSquareDistributionTest.java    |  148 +
 .../ContinuousDistributionAbstractTest.java        |  371 ++
 .../distribution/ExponentialDistributionTest.java  |  137 +
 .../math/distribution/FDistributionTest.java       |  150 +
 .../math/distribution/GammaDistributionTest.java   |  169 +
 .../HypergeometricDistributionTest.java            |  229 +
 .../IntegerDistributionAbstractTest.java           |  415 ++
 .../math/distribution/NormalDistributionTest.java  |  222 +
 .../math/distribution/PascalDistributionTest.java  |  136 +
 .../math/distribution/PoissonDistributionTest.java |  231 +
 .../math/distribution/TDistributionTest.java       |  135 +
 .../math/distribution/WeibullDistributionTest.java |  144 +
 .../math/distribution/ZipfDistributionTest.java    |   89 +
 .../math/estimation/EstimatedParameterTest.java    |   74 +
 .../math/estimation/GaussNewtonEstimatorTest.java  |  724 +++
 .../LevenbergMarquardtEstimatorTest.java           |  832 ++++
 .../commons/math/estimation/MinpackTest.java       | 1538 ++++++
 .../math/estimation/WeightedMeasurementTest.java   |  128 +
 .../exception/DimensionMismatchExceptionTest.java  |   34 +
 .../NonMonotonousSequenceExceptionTest.java        |   47 +
 .../math/exception/NotPositiveExceptionTest.java   |   35 +
 .../NotStrictlyPositiveExceptionTest.java          |   35 +
 .../exception/NumberIsTooLargeExceptionTest.java   |   35 +
 .../exception/NumberIsTooSmallExceptionTest.java   |   35 +
 .../math/exception/OutOfRangeExceptionTest.java    |   35 +
 .../commons/math/exception/util/ArgUtilsTest.java  |   76 +
 .../math/exception/util/MessageFactoryTest.java    |   57 +
 .../math/fraction/BigFractionFieldTest.java        |   44 +
 .../math/fraction/BigFractionFormatTest.java       |  323 ++
 .../commons/math/fraction/BigFractionTest.java     |  577 +++
 .../commons/math/fraction/FractionFieldTest.java   |   44 +
 .../commons/math/fraction/FractionFormatTest.java  |  303 ++
 .../apache/commons/math/fraction/FractionTest.java |  583 +++
 .../math/genetics/BinaryChromosomeTest.java        |   67 +
 .../commons/math/genetics/BinaryMutationTest.java  |   44 +
 .../commons/math/genetics/ChromosomeTest.java      |  111 +
 .../math/genetics/DummyBinaryChromosome.java       |   44 +
 .../commons/math/genetics/DummyRandomKey.java      |   44 +
 .../math/genetics/ElitisticListPopulationTest.java |   53 +
 .../commons/math/genetics/FitnessCachingTest.java  |   94 +
 .../math/genetics/FixedGenerationCountTest.java    |   63 +
 .../math/genetics/GeneticAlgorithmTestBinary.java  |  122 +
 .../genetics/GeneticAlgorithmTestPermutations.java |  132 +
 .../commons/math/genetics/ListPopulationTest.java  |   61 +
 .../math/genetics/OnePointCrossoverTest.java       |   59 +
 .../math/genetics/RandomKeyMutationTest.java       |   44 +
 .../commons/math/genetics/RandomKeyTest.java       |  164 +
 .../math/genetics/TournamentSelectionTest.java     |   56 +
 .../math/geometry/FrenchVector3DFormatTest.java    |   34 +
 .../commons/math/geometry/RotationOrderTest.java   |   62 +
 .../apache/commons/math/geometry/RotationTest.java |  488 ++
 .../math/geometry/Vector3DFormatAbstractTest.java  |  361 ++
 .../commons/math/geometry/Vector3DFormatTest.java  |   34 +
 .../apache/commons/math/geometry/Vector3DTest.java |  226 +
 .../math/linear/AbstractRealVectorTest.java        |  244 +
 .../math/linear/Array2DRowRealMatrixTest.java      | 1008 ++++
 .../commons/math/linear/ArrayFieldVectorTest.java  |  632 +++
 .../commons/math/linear/ArrayRealVectorTest.java   | 1310 +++++
 .../math/linear/BiDiagonalTransformerTest.java     |  204 +
 .../commons/math/linear/BigMatrixImplTest.java     |  836 ++++
 .../commons/math/linear/BlockFieldMatrixTest.java  | 1288 +++++
 .../commons/math/linear/BlockRealMatrixTest.java   | 1204 +++++
 .../math/linear/CholeskyDecompositionImplTest.java |  150 +
 .../commons/math/linear/CholeskySolverTest.java    |  119 +
 .../math/linear/EigenDecompositionImplTest.java    |  533 +++
 .../commons/math/linear/EigenSolverTest.java       |  158 +
 .../math/linear/FieldLUDecompositionImplTest.java  |  297 ++
 .../commons/math/linear/FieldMatrixImplTest.java   | 1005 ++++
 .../math/linear/FrenchRealVectorFormatTest.java    |   34 +
 .../math/linear/InvalidMatrixExceptionTest.java    |   32 +
 .../math/linear/LUDecompositionImplTest.java       |  288 ++
 .../apache/commons/math/linear/LUSolverTest.java   |  179 +
 .../math/linear/MatrixIndexExceptionTest.java      |   38 +
 .../commons/math/linear/MatrixUtilsTest.java       |  404 ++
 .../math/linear/QRDecompositionImplTest.java       |  256 +
 .../apache/commons/math/linear/QRSolverTest.java   |  213 +
 .../commons/math/linear/RealMatrixImplTest.java    | 1008 ++++
 .../math/linear/RealVectorFormatAbstractTest.java  |  387 ++
 .../commons/math/linear/RealVectorFormatTest.java  |   34 +
 .../linear/SingularValueDecompositionImplTest.java |  250 +
 .../math/linear/SingularValueSolverTest.java       |  147 +
 .../commons/math/linear/SparseFieldMatrixTest.java |  684 +++
 .../commons/math/linear/SparseFieldVectorTest.java |  216 +
 .../commons/math/linear/SparseRealMatrixTest.java  |  671 +++
 .../commons/math/linear/SparseRealVectorTest.java  | 1194 +++++
 .../math/linear/TriDiagonalTransformerTest.java    |  175 +
 .../math/ode/ContinuousOutputModelTest.java        |  197 +
 .../commons/math/ode/FirstOrderConverterTest.java  |  113 +
 .../org/apache/commons/math/ode/TestProblem1.java  |   91 +
 .../org/apache/commons/math/ode/TestProblem2.java  |   93 +
 .../org/apache/commons/math/ode/TestProblem3.java  |  132 +
 .../org/apache/commons/math/ode/TestProblem4.java  |  159 +
 .../org/apache/commons/math/ode/TestProblem5.java  |   44 +
 .../org/apache/commons/math/ode/TestProblem6.java  |   93 +
 .../commons/math/ode/TestProblemAbstract.java      |  191 +
 .../commons/math/ode/TestProblemFactory.java       |   50 +
 .../commons/math/ode/TestProblemHandler.java       |  157 +
 .../commons/math/ode/events/EventStateTest.java    |   76 +
 .../FirstOrderIntegratorWithJacobiansTest.java     |  437 ++
 .../ode/nonstiff/AdamsBashforthIntegratorTest.java |  157 +
 .../ode/nonstiff/AdamsMoultonIntegratorTest.java   |  157 +
 .../ClassicalRungeKuttaIntegratorTest.java         |  308 ++
 .../ClassicalRungeKuttaStepInterpolatorTest.java   |   94 +
 .../nonstiff/DormandPrince54IntegratorTest.java    |  363 ++
 .../DormandPrince54StepInterpolatorTest.java       |  152 +
 .../nonstiff/DormandPrince853IntegratorTest.java   |  428 ++
 .../DormandPrince853StepInterpolatorTest.java      |  152 +
 .../math/ode/nonstiff/EulerIntegratorTest.java     |  188 +
 .../ode/nonstiff/EulerStepInterpolatorTest.java    |  182 +
 .../math/ode/nonstiff/GillIntegratorTest.java      |  243 +
 .../ode/nonstiff/GillStepInterpolatorTest.java     |   95 +
 .../nonstiff/GraggBulirschStoerIntegratorTest.java |  365 ++
 .../GraggBulirschStoerStepInterpolatorTest.java    |  154 +
 .../ode/nonstiff/HighamHall54IntegratorTest.java   |  355 ++
 .../nonstiff/HighamHall54StepInterpolatorTest.java |  152 +
 .../math/ode/nonstiff/MidpointIntegratorTest.java  |  188 +
 .../ode/nonstiff/MidpointStepInterpolatorTest.java |   95 +
 .../commons/math/ode/nonstiff/StepProblem.java     |   64 +
 .../ode/nonstiff/ThreeEighthesIntegratorTest.java  |  238 +
 .../ThreeEighthesStepInterpolatorTest.java         |   94 +
 .../ode/sampling/DummyStepInterpolatorTest.java    |  144 +
 .../sampling/NordsieckStepInterpolatorTest.java    |   92 +
 .../ode/sampling/StepInterpolatorTestUtils.java    |   90 +
 .../math/ode/sampling/StepNormalizerTest.java      |  119 +
 ...ifferentiableMultivariateRealOptimizerTest.java |  159 +
 ...entiableMultivariateVectorialOptimizerTest.java |  192 +
 .../MultiStartMultivariateRealOptimizerTest.java   |   81 +
 .../MultiStartUnivariateRealOptimizerTest.java     |  102 +
 .../optimization/direct/MultiDirectionalTest.java  |  245 +
 .../math/optimization/direct/NelderMeadTest.java   |  334 ++
 .../optimization/direct/PowellOptimizerTest.java   |  163 +
 .../math/optimization/fitting/CurveFitterTest.java |  156 +
 .../optimization/fitting/GaussianFitterTest.java   |  321 ++
 .../optimization/fitting/HarmonicFitterTest.java   |  133 +
 .../fitting/ParametricGaussianFunctionTest.java    |  158 +
 .../optimization/fitting/PolynomialFitterTest.java |  135 +
 .../general/GaussNewtonOptimizerTest.java          |  572 +++
 .../general/LevenbergMarquardtOptimizerTest.java   |  663 +++
 .../math/optimization/general/MinpackTest.java     | 1531 ++++++
 .../NonLinearConjugateGradientOptimizerTest.java   |  492 ++
 .../optimization/linear/SimplexSolverTest.java     |  458 ++
 .../optimization/linear/SimplexTableauTest.java    |  111 +
 .../optimization/univariate/BracketFinderTest.java |   70 +
 .../univariate/BrentOptimizerTest.java             |  146 +
 .../math/random/AbstractRandomGeneratorTest.java   |  144 +
 .../CorrelatedRandomVectorGeneratorTest.java       |  148 +
 .../math/random/EmpiricalDistributionTest.java     |  259 +
 .../math/random/GaussianRandomGeneratorTest.java   |   43 +
 .../commons/math/random/MersenneTwisterTest.java   |  424 ++
 .../commons/math/random/RandomAdaptorTest.java     |  104 +
 .../apache/commons/math/random/RandomDataTest.java |  990 ++++
 .../commons/math/random/TestRandomGenerator.java   |   42 +
 .../UncorrelatedRandomVectorGeneratorTest.java     |   84 +
 .../math/random/UniformRandomGeneratorTest.java    |   43 +
 .../commons/math/random/ValueServerTest.java       |  199 +
 .../apache/commons/math/random/Well1024aTest.java  |  200 +
 .../apache/commons/math/random/Well19937aTest.java |  110 +
 .../apache/commons/math/random/Well19937cTest.java |  110 +
 .../apache/commons/math/random/Well44497aTest.java |  110 +
 .../apache/commons/math/random/Well44497bTest.java |  110 +
 .../apache/commons/math/random/Well512aTest.java   |   71 +
 .../org/apache/commons/math/special/BetaTest.java  |  119 +
 .../org/apache/commons/math/special/ErfTest.java   |  190 +
 .../org/apache/commons/math/special/GammaTest.java |  151 +
 .../commons/math/stat/CertifiedDataTest.java       |  154 +
 .../apache/commons/math/stat/FrequencyTest.java    |  287 ++
 .../apache/commons/math/stat/StatUtilsTest.java    |  468 ++
 .../stat/clustering/EuclideanIntegerPointTest.java |   65 +
 .../clustering/KMeansPlusPlusClustererTest.java    |  169 +
 .../math/stat/correlation/CovarianceTest.java      |  236 +
 .../stat/correlation/PearsonsCorrelationTest.java  |  308 ++
 .../correlation/SpearmansRankCorrelationTest.java  |  130 +
 .../math/stat/data/CertifiedDataAbstractTest.java  |  156 +
 .../org/apache/commons/math/stat/data/LewTest.java |   29 +
 .../apache/commons/math/stat/data/LotteryTest.java |   29 +
 .../AbstractUnivariateStatisticTest.java           |  102 +
 .../AggregateSummaryStatisticsTest.java            |  290 ++
 .../descriptive/DescriptiveStatisticsTest.java     |  296 ++
 .../math/stat/descriptive/InteractionTest.java     |   88 +
 .../math/stat/descriptive/ListUnivariateImpl.java  |  213 +
 .../stat/descriptive/ListUnivariateImplTest.java   |  157 +
 .../descriptive/MixedListUnivariateImplTest.java   |  202 +
 .../MultivariateSummaryStatisticsTest.java         |  309 ++
 .../descriptive/StatisticalSummaryValuesTest.java  |   85 +
 .../StorelessUnivariateStatisticAbstractTest.java  |  211 +
 .../stat/descriptive/SummaryStatisticsTest.java    |  299 ++
 .../SynchronizedDescriptiveStatisticsTest.java     |   32 +
 ...chronizedMultivariateSummaryStatisticsTest.java |   33 +
 .../SynchronizedSummaryStatisticsTest.java         |   31 +
 .../UnivariateStatisticAbstractTest.java           |  181 +
 .../stat/descriptive/moment/FirstMomentTest.java   |   54 +
 .../stat/descriptive/moment/FourthMomentTest.java  |   54 +
 .../stat/descriptive/moment/GeometricMeanTest.java |   83 +
 .../math/stat/descriptive/moment/KurtosisTest.java |   70 +
 .../math/stat/descriptive/moment/MeanTest.java     |   71 +
 .../stat/descriptive/moment/SecondMomentTest.java  |   54 +
 .../stat/descriptive/moment/SemiVarianceTest.java  |  142 +
 .../math/stat/descriptive/moment/SkewnessTest.java |   69 +
 .../descriptive/moment/StandardDeviationTest.java  |   99 +
 .../stat/descriptive/moment/ThirdMomentTest.java   |   54 +
 .../math/stat/descriptive/moment/VarianceTest.java |  120 +
 .../moment/VectorialCovarianceTest.java            |  101 +
 .../stat/descriptive/moment/VectorialMeanTest.java |   95 +
 .../math/stat/descriptive/rank/MaxTest.java        |   78 +
 .../math/stat/descriptive/rank/MedianTest.java     |   53 +
 .../math/stat/descriptive/rank/MinTest.java        |   78 +
 .../math/stat/descriptive/rank/PercentileTest.java |  159 +
 .../math/stat/descriptive/summary/ProductTest.java |   87 +
 .../math/stat/descriptive/summary/SumLogTest.java  |   83 +
 .../math/stat/descriptive/summary/SumSqTest.java   |   69 +
 .../math/stat/descriptive/summary/SumTest.java     |   77 +
 .../math/stat/inference/ChiSquareFactoryTest.java  |   37 +
 .../math/stat/inference/ChiSquareTestTest.java     |  251 +
 .../math/stat/inference/OneWayAnovaTest.java       |  120 +
 .../math/stat/inference/TTestFactoryTest.java      |   37 +
 .../commons/math/stat/inference/TTestTest.java     |  291 ++
 .../commons/math/stat/inference/TestUtilsTest.java |  458 ++
 .../math/stat/ranking/NaturalRankingTest.java      |  204 +
 .../GLSMultipleLinearRegressionTest.java           |  297 ++
 .../MultipleLinearRegressionAbstractTest.java      |  136 +
 .../OLSMultipleLinearRegressionTest.java           |  512 ++
 .../math/stat/regression/SimpleRegressionTest.java |  363 ++
 .../math/transform/FastCosineTransformerTest.java  |  121 +
 .../math/transform/FastFourierTransformerTest.java |  174 +
 .../transform/FastHadamardTransformerTest.java     |  119 +
 .../math/transform/FastSineTransformerTest.java    |  120 +
 .../apache/commons/math/util/BigRealFieldTest.java |   44 +
 .../org/apache/commons/math/util/BigRealTest.java  |  128 +
 .../commons/math/util/ContinuedFractionTest.java   |   56 +
 .../commons/math/util/DefaultTransformerTest.java  |  102 +
 .../commons/math/util/DoubleArrayAbstractTest.java |  132 +
 .../math/util/FastMathStrictComparisonTest.java    |  248 +
 .../org/apache/commons/math/util/FastMathTest.java | 1118 +++++
 .../commons/math/util/FastMathTestPerformance.java |  377 ++
 .../apache/commons/math/util/MathUtilsTest.java    | 1640 +++++++
 .../math/util/MultidimensionalCounterTest.java     |  169 +
 .../math/util/OpenIntToDoubleHashMapTest.java      |  304 ++
 .../commons/math/util/OpenIntToFieldTest.java      |  315 ++
 .../math/util/ResizableDoubleArrayTest.java        |  540 +++
 .../org/apache/commons/math/util/TestBean.java     |   72 +
 .../commons/math/util/TransformerMapTest.java      |  114 +
 .../org/apache/commons/math/random/emptyFile.txt   |    0
 .../org/apache/commons/math/random/testData.txt    | 1000 ++++
 .../org/apache/commons/math/stat/data/Lew.txt      |  252 +
 .../org/apache/commons/math/stat/data/Lottery.txt  |  261 +
 .../org/apache/commons/math/stat/data/Mavro.txt    |  110 +
 .../org/apache/commons/math/stat/data/Michelso.txt |  160 +
 .../org/apache/commons/math/stat/data/NumAcc1.txt  |   63 +
 .../org/apache/commons/math/stat/data/NumAcc2.txt  | 1061 ++++
 .../org/apache/commons/math/stat/data/NumAcc3.txt  | 1061 ++++
 .../org/apache/commons/math/stat/data/NumAcc4.txt  | 1061 ++++
 .../org/apache/commons/math/stat/data/PiDigits.txt | 5060 ++++++++++++++++++++
 test-jar.xml                                       |  130 +
 875 files changed, 207210 insertions(+)

diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..275e288
--- /dev/null
+++ b/LICENSE.txt
@@ -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.txt b/NOTICE.txt
new file mode 100644
index 0000000..977df71
--- /dev/null
+++ b/NOTICE.txt
@@ -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/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
new file mode 100644
index 0000000..81b237c
--- /dev/null
+++ b/RELEASE-NOTES.txt
@@ -0,0 +1,157 @@
+                        
+
+              Apache Commons Math 2.2 RELEASE NOTES
+
+This is primarily a maintenance release, but it also includes new features and enhancements.
+
+Users of version 2.1 are encouraged to upgrade to 2.2, as this release includes some important bug fixes.
+
+See the detailed list of changes below for full description of all bug fixes and enhancements.
+
+This release contains some minor compatibility breaks with version 2.1 in some internal classes but none
+ of them are in APIs likely to be accessed by user code:
+ the MessagesResources_fr class has been removed (replaced by a properties file);
+ the arguments of the EventState.reinitializeBegin method have changed;
+ some protected fields which already had public accessors in AbstractStepInterpolator have been replaced.
+
+There is a behavior change that users of the multiple regression classes should be aware of. In version
+2.1, there was no way to estimate models without intercept terms, and, while this was not clear from
+the documentation, design (X) matrices needed to include initial unitary columns.  In 2.2, initial 
+unitary columns are not necessary and whether or not models include intercept terms is configurable.
+See the change log and javadoc for the classes in org.apache.commons.math.stat.regression for details.
+
+The major new features are:
+ a new FastMath class, both faster, more accurate and with a few additional functions than StrictMath and Math;
+ a new package for floating point arbitrary precision computing, including high level functions like exponential, sine, square root ...;
+ new linear and tricubic interpolators;
+ a new Gaussian curve fitter;
+ a new erfc function;
+ characteristic support for distributions;
+ a set of new Well Equidistributed Long-period Linear (WELL) random generators.
+
+Changes in this version include:
+
+New features:
+o MATH-364:  Added complementary error function, erfc. Thanks to Christian Winter. 
+o MATH-385:  Added characteristic support to distributions, including methods to return numerical
+        estimates of the mean and variance and upper and lower bounds of support. In version 2.2,
+        methods returning distribution characteristics have been added only to the implementation
+        classes.  In version 3, supporting methods have been added to the abstract base classes
+        and distribution interfaces. 
+o MATH-440:  Created "MathUserException" class to convey cause of failure between
+        layers of user code separated by a layer of Commons-Math code. 
+o MATH-419:  Added new random number generators from the Well Equidistributed Long-period Linear (WELL). 
+o MATH-412:  Added the dfp library providing arbitrary precision floating point computation in the spirit of
+        IEEE 854-1987 (not exactly as it uses base 1000 instead of base 10). In addition to finite numbers,
+        infinities and NaNs are available (but there are no subnormals). All IEEE 854-1987 rounding modes and
+        signaling flags are supported. The available operations are +, -, *, / and the available functions
+        are sqrt, sin, cos, tan, asin, acos, atan, exp, log. Thanks to Bill Rossi. 
+o MATH-375:  Added faster and more accurate version of traditional mathematical functions in a FastMath
+        class intended to be a drop-in replacement for java.util.Math at source-level. Some functions
+        still directly delegates to Math but this will improve with time. Some functions like exp
+        may be twice as fast (even 3 times faster on some processors). Sine, cosine or power functions
+        show typical speedups of 1.5 times faster or more. Thanks to Bill Rossi. 
+o MATH-400:  Added support for Gaussian curve fitting. Thanks to J. Lewis Muir. 
+o MATH-388:  Added a feature allowing error estimation to be computed only on a subset of
+        Ordinary Differential Equations, considered as the main set, the remaining equations
+        being considered only as an extension set that should not influence the ODE integration
+        algorithm 
+o MATH-379:  Created "MultidimensionalCounter" class. 
+o MATH-378:  Implementation of linear interpolation. Thanks to Matthew Rowles. 
+o MATH-370:  Added new "equalsIncludingNaN" methods that have the same semantics as the old "equals" methods.
+        The semantics of the old methods will be modified (in the next major release) such that
+        NaNs are not considered equal (to be more compliant with IEEE754). 
+o MATH-366:  Implementation of tricubic interpolation. 
+
+Fixed Bugs:
+o MATH-505:  TestUtils is thread-hostile. Deprecate the getters and setters. 
+o MATH-471:  MathUtils.equals(double, double) does not work properly for floats
+        - add equivalent (float, float) methods and basic tests 
+o MATH-467:  Fixed an awkward statement that triggered a false positive warning. 
+o MATH-456:  Modified erf (and erfc) to return extreme values for x with abs(x) > 40.
+        For these arguments, the true value is indistinguishable from an extrema as a double. 
+o MATH-414:  Modified NormalDistributionImpl.cumulativeProbability to return 0 or 1,
+        respectively for values more than 40 standard deviations from the mean.
+        For these values, the actual probability is indistinguishable from 0 or 1
+        as a double.  Top coding improves performance for extreme values and prevents
+        convergence exceptions. 
+o MATH-380:  Deprecated the whole ode.jacobians package. It is clumsy and difficult to use. It will
+        be replaced by a completely rewritten implementation in 3.0, which will be more tightly
+        bound to the top level ode package 
+o MATH-426:  Added a normalization feature to transform samples so they have zero mean and unit standard deviation Thanks to Erik van Ingen. 
+o MATH-429:  Fixed k-means++ to add several strategies to deal with empty clusters that may appear
+        during iterations 
+o MATH-391:  Fixed an error preventing zero length vectors to be built by some constructors 
+o MATH-421:  Fixed an error preventing ODE solvers to be restarted after they have been stopped by a discrete event 
+o MATH-415:  Fixed lost cause in MathRuntimeException.createInternalError. Note that the message is still the default
+        message for internal errors asking to report a bug to commons-math JIRA tracker. In order to retrieve
+        the message from the root cause, one has to get the cause itself by getCause(). 
+o MATH-411:  Modified multiple regression newSample methods to ensure that by default in all cases,
+        regression models are estimated with intercept terms.  Prior to the fix for this issue, 
+        newXSampleData(double[][]), newSampleData(double[], double[][]) and
+        newSampleData(double[], double[][], double[][]) all required columns of "1's" to be inserted
+        into the x[][] arrays to create a model with an intercept term; while newSampleData(double[], int, int)
+        created a model including an intercept term without requiring the unitary column.  All methods have
+        been changed to eliminate the need for users to add unitary columns to specify regression models.
+        Users of OLSMultipleLinearRegression or GLSMultipleLinearRegression versions 2.0 or 2.1 should either
+        verify that their code either does not use the first set of data loading methods above or set the noIntercept
+        property to true on estimated models to get the previous behavior. 
+o MATH-386:  Added R-squared and adjusted R-squared statistics to OLSMultipleLinearRegression. 
+o MATH-392:  Corrected the formula used for Y variance returned by calculateYVariance and associated
+        methods in multiple regression classes (AbstractMultipleLinearRegression,
+        OLSMultipleLinearRegression, GLSMultipleLinearRegression).  These methods previously returned
+        estimates of the variance in the model error term.  New "calulateErrorVariance" methods have
+        been added to compute what was previously returned by calculateYVariance. Thanks to Mark Devaney. 
+o MATH-406:  Bug fixed in Levenberg-Marquardt (handling of weights). 
+o MATH-405:  Bug fixed in Levenberg-Marquardt (consistency of current). 
+o MATH-377:  Bug fixed in chi-square computation in AbstractLeastSquaresOptimizer. 
+o MATH-395:  Fixed several bugs in "BrentOptimizer". 
+o MATH-393:  Fixed inconsistency in return values in "MultiStartUnivariateRealOptimizer". 
+o MATH-382:  Fixed bug in precondition check (method "setMicrosphereElements"). 
+o MATH-361:  Improved localization of error messages. 
+o MATH-376:  Allow multiple optimizations with a default simplex. 
+o MATH-352:  Added a setQRRankingThreshold method to Levenberg-Marquardt optimizer to improve robustness
+        of rank determination. 
+o MATH-362:  Fixed Levenberg-Marquardt optimizer that did not use the vectorial convergence checker.
+        Now this optimizer can use either the general vectorial convergence checker or its own
+        specialized convergence settings. 
+o MATH-371:  Fixed loss of significance error in PersonsCorrelation p-value computation causing p-values
+        smaller than the machine epsilon (~1E-16) to be reported as 0. Thanks to Kevin Childs. 
+o MATH-369:  Fix NullPointerException in BisectionSolver.solve(f, min, max, initial) Thanks to Sasun Pundev. 
+o MATH-368:  Fix spelling of getSparcity [sic] method of OpenMapRealVector 
+o MATH-367:  Fix problem with the default sparseIterator when a RealVector has exactly one non-zero entry Thanks to Albert Huang. 
+
+Changes:
+o MATH-384:  Added a constructor and addValues(double[]) methods allowing DescriptiveStatistics to
+        be initialized with values from a double[] array.  Similarly enhanced 
+        ResizeableDoubleArray. 
+o MATH-448:  Added a getUniqueCount() method to Frequency to return the number of unique
+        values included in the frequency table. Thanks to Patrick Meyer. 
+o MATH-420:  Added toString() override to StatisticalSummaryValues. 
+o MATH-417:  Improved Percentile performance by using a selection algorithm instead of a
+        complete sort, and by allowing caching data array and pivots when several
+        different percentiles are desired 
+o MATH-409:  Made intercept / no intercept configurable in multiple regression classes. By default, regression
+        models are estimated with an intercept term.  When the "noIntercept" property is set to
+        true, regression models are estimated without intercepts. 
+o MATH-361:  Created package "exception" to contain the new exceptions hierarchy.
+        Created package "exception.util": utilities for the exception classes
+        (e.g. managing the localization of error messages).
+        Default policy for dealing with invalid null references: raise a
+        "NullArgumentException" (subclass of "IllegalArgumentException"). 
+o MATH-310:  Added random data generation methods to RandomDataImpl for the remaining distributions in the
+        distributions package. Added a generic nextInversionDeviate method that takes a discrete
+        or continuous distribution as argument and generates a random deviate from the distribution.
+        Also added sampling methods based on the implementations in RandomDataImpl to distributions. 
+o MATH-365:  Deprecated SmoothingBicubicSplineInterpolator and SmoothingBicubicSplineInterpolatorTest.
+        Added BicubicSplineInterpolator and BicubicSplineInterpolatorTest.
+        Added SmoothingPolynomialBicubicSplineInterpolator and SmoothingPolynomialBicubicSplineInterpolatorTest.
+        Added method to clear the list of observations in PolynomialFitter. 
+
+ 
+For complete information on Commons Math, including instructions on how to submit bug reports,
+patches, or suggestions for improvement, see the Apache Commons Math website:
+
+http://commons.apache.org/math/
+
+
diff --git a/build.xml b/build.xml
new file mode 100644
index 0000000..13c8389
--- /dev/null
+++ b/build.xml
@@ -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.
+-->
+<project name="Commons Math" default="jar" basedir=".">
+
+
+<!--
+        "Math" component of the Apache Commons Project
+        $Id: build.xml 1072977 2011-02-21 14:22:47Z sebb $
+        $Revision: 1072977 $ $Date: 2011-02-21 15:22:47 +0100 (lun. 21 févr. 2011) $
+-->
+
+
+<!-- ========== Initialize Properties ===================================== -->
+
+  <property file="build.properties"/>                <!-- Component local   -->
+  <property file="${user.home}/build.properties"/>   <!-- User local        -->
+
+
+<!-- ========== External Dependencies ===================================== -->
+
+
+  <!-- Junit -->
+  <property name="junit.version"           value="4.8.2"/>
+  <property name="junit.home"              value="/usr/share/junit"/>
+  <property name="junit.jar"               value="${junit.home}/junit-${junit.version}.jar"/>
+
+
+<!-- ========== Component Declarations ==================================== -->
+
+
+  <!-- The name of this component -->
+  <property name="component.name"          value="commons-math"/>
+
+  <!-- The primary package name of this component -->
+  <property name="component.package"       value="org.apache.commons.math"/>
+
+  <!-- The title of this component -->
+  <property name="component.title"         value="Commons MATH"/>
+
+  <!-- The current version number of this component -->
+  <property name="component.version"       value="2.2"/>
+
+  <!-- The base directory for component sources -->
+  <property name="source.home"             value="src/main/java"/>
+
+  <!-- The base directory for unit test sources -->
+  <property name="test.home"               value="src/test/java"/>
+
+  <!-- The base directory for unit test resources -->
+  <property name="test.resources"          value="src/test/resources"/>
+
+  <!-- Download lib dir -->
+  <property name="download.lib.dir"        value="lib"/>
+
+  <!-- The base directory for compilation targets -->
+  <property name="build.home"              value="target"/>
+
+  <!-- The base directory for distribution targets -->
+  <property name="dist.home"               value="${build.home}/dist"/>
+
+  <!-- The base directory for test reports -->
+  <property name="test.reports"            value="${build.home}/test-reports"/>
+
+  <!-- Base file name for dist files -->
+  <property name="final.name"              value="${component.name}-${component.version}"/>
+
+  <!-- Directory where binary release files are staged -->
+  <property name="stage.bin.dir"           value="${dist.home}/stage-bin"/>
+
+  <!-- Directory where source release files are staged -->
+  <property name="stage.src.dir"           value="${dist.home}/stage-src"/>
+
+<!-- ========== Compiler Defaults ========================================= -->
+
+  <!-- Should Java compilations set the 'debug' compiler option? -->
+  <property name="compile.debug"           value="true"/>
+
+  <!-- Should Java compilations set the 'deprecation' compiler option? -->
+  <property name="compile.deprecation"     value="false"/>
+
+  <!-- Should Java compilations set the 'optimize' compiler option? -->
+  <property name="compile.optimize"        value="true"/>
+    
+  <!-- JDK level -->
+  <property name="compile.source"          value="1.5"/>
+  <property name="compile.target"          value="1.5"/>
+
+  <!-- Base compile classpath -->
+  <path id="compile.classpath">
+    <pathelement location="${build.home}/classes"/>
+  </path>
+
+  <!-- External dependency classpath -->
+  <path id="downloaded.lib.classpath">
+    <pathelement location="${download.lib.dir}/junit-${junit.version}.jar"/>
+  </path>
+
+<!-- ========== Test Execution Defaults =================================== -->
+
+
+  <!-- Construct unit test classpath -->
+  <path id="test.classpath">
+    <pathelement location="${build.home}/classes"/>
+    <pathelement location="${build.home}/test-classes"/>
+    <pathelement location="${junit.jar}"/>
+    <path refid="downloaded.lib.classpath"/>
+  </path>
+
+  <!-- Should the build fail if there are test failures? -->
+  <property name="test.failonerror"        value="true"/>
+
+<!-- ========== Executable Targets ======================================== -->
+
+  <target name="clean" description="Clean build and distribution directories">
+    <delete    dir="${build.home}"/>
+  </target>
+
+
+  <target name="init"
+   description="Initialize and evaluate conditionals">
+    <echo message="-------- ${component.title} ${component.version} --------"/>
+    <filter  token="name"                  value="${component.name}"/>
+    <filter  token="package"               value="${component.package}"/>
+    <filter  token="version"               value="${component.version}"/>
+    <filter  token="compilesource"         value="${compile.source}"/>
+    <filter  token="compiletarget"         value="${compile.target}"/>
+    <tstamp/>
+    <mkdir dir="${build.home}"/>
+    <mkdir dir="${build.home}/classes"/>
+    <mkdir dir="${build.home}/test-classes"/>
+    <mkdir dir="${build.home}/classes/META-INF"/>
+    <copy todir="${build.home}/classes/META-INF">
+       <fileset dir="src/main/resources/META-INF" />
+    </copy>
+  </target>
+
+<!-- ========== Compile Targets =========================================== -->
+
+  <target name="compile" depends="init" description="Compile">
+
+    <javac  srcdir="${source.home}"
+           destdir="${build.home}/classes"
+             source="${compile.source}"
+             target="${compile.target}"
+             debug="${compile.debug}"
+       deprecation="${compile.deprecation}"
+ includeantruntime="false"
+          optimize="${compile.optimize}">
+      <classpath refid="compile.classpath"/>
+    </javac>
+  </target>
+
+
+<!-- ========== Unit Test Targets ========================================= -->
+
+    <target name="compile.tests" depends="compile, download-dependencies" description="Compile unit tests.">
+
+      <javac srcdir="${test.home}"
+             destdir="${build.home}/test-classes"
+             debug="${compile.debug}"
+             deprecation="${compile.deprecation}"
+             includeantruntime="false"
+             optimize="${compile.optimize}">
+          <classpath refid="test.classpath"/>
+      </javac>
+    
+      <copy todir="${build.home}/test-classes">
+          <fileset dir="${test.resources}">
+              <include name="**/*.xml"/>
+              <include name="**/*.txt"/>
+          </fileset>
+      </copy>
+
+    </target>
+
+  <target name="test"  depends="compile.tests"
+                       description="Run unit tests">
+      <mkdir dir="${test.reports}"/>
+      <junit printsummary="true"
+               errorProperty="test.failed"
+               failureProperty="test.failed"
+               fork="true"
+               showOutput="true">
+               <formatter type="brief"/>
+               <classpath refid="test.classpath"/>
+               <!-- If test.entry is defined, run a single test, otherwise run all valid tests -->
+               <!-- N.B. test.entry must be the full path to the test class, for example:
+               ant test -Dtest.entry=org.apache.commons.math.util.FastMathTestPerformance
+               -->
+               <test name="${test.entry}" todir="${test.reports}" if="test.entry"/>
+               <batchtest todir="${test.reports}" unless="test.entry">
+                   <fileset dir="${test.home}">
+                      <include name="**/*Test.java"/> 
+                      <include name="**/*TestBinary.java"/> 
+                      <include name="**/*TestPermutations.java"/> 
+                      <exclude name="**/*AbstractTest.java"/>
+                   </fileset>
+               </batchtest>
+       </junit>
+       <fail message="There were test failures.">
+           <condition>
+               <and>
+                   <istrue value="${test.failonerror}"/>
+                   <isset property="test.failed"/>
+               </and>
+           </condition>
+     </fail>
+  </target>
+
+
+<!-- ========== Produce JavaDocs ========================================== -->
+
+  <target name="javadoc" depends="compile" description="Create component Javadoc documentation">
+    <mkdir dir="${build.home}/apidocs"/>
+    <tstamp>
+        <format property="current.year" pattern="yyyy"/>
+    </tstamp>
+    <javadoc sourcepath="${source.home}"
+                destdir="${build.home}/apidocs"
+           packagenames="org.apache.commons.*"
+                 author="true"
+                private="true"
+                version="true"
+               doctitle="<h1>${component.title} ${component.version}</h1>"
+            windowtitle="${component.title} ${component.version}"
+                 bottom="Copyright (c) 2003-${current.year}  Apache Software Foundation"
+           classpathref="compile.classpath">
+        <link href="http://java.sun.com/j2se/1.5.0/docs/api/"/>  
+        <link href="http://commons.apache.org/collections/api"/> 
+    </javadoc>
+  </target>
+
+
+<!-- ========== Create Jar ================================================ -->
+
+  <target name="jar" depends="test" description="Create jar file">
+
+    <copy file="LICENSE.txt" tofile="${build.home}/classes/META-INF/LICENSE.txt"/>
+    <copy file="NOTICE.txt"  tofile="${build.home}/classes/META-INF/NOTICE.txt"/>
+
+    <manifest file="${build.home}/MANIFEST.MF">
+        <attribute name="Specification-Title"      value="${component.title}"/>
+        <attribute name="Specification-Version"    value="${component.version}"/>
+        <attribute name="Specification-Vendor"     value="Apache Software Foundation"/>
+        <attribute name="Implementation-Title"     value="${component.title}"/>
+        <attribute name="Implementation-Version"   value="${component.version}"/> 
+        <attribute name="Implementation-Vendor"    value="Apache Software Foundation"/>
+        <attribute name="Implementation-Vendor-Id" value="org.apache"/>
+        <attribute name="X-Compile-Source-JDK"     value="${compile.source}"/>
+        <attribute name="X-Compile-Target-JDK"     value="${compile.target}"/>
+    </manifest>
+
+    <jar jarfile="${build.home}/${final.name}.jar"
+         basedir="${build.home}/classes"
+        manifest="${build.home}/MANIFEST.MF"/>
+  </target>
+
+
+<!-- ========== Distribution Target =========================================== -->
+
+  <target name="dist" depends="clean,jar,javadoc" description="Create distribution artifacts">
+
+    <mkdir dir="${dist.home}"/>
+
+    <!-- jar(s) -->
+    <copy todir="${dist.home}">
+      <fileset dir=".">
+        <include name="RELEASE-NOTES.txt"/>
+      </fileset>
+      <fileset dir="${build.home}">
+        <include name="*.jar"/>
+      </fileset>
+    </copy>
+
+    <!-- Binary Distro -->
+    <mkdir dir="${stage.bin.dir}/${final.name}"/>
+    <copy todir="${stage.bin.dir}/${final.name}">
+      <fileset dir=".">
+        <include name="LICENSE.txt"/>
+        <include name="NOTICE.txt"/>
+        <include name="RELEASE-NOTES.txt"/>
+      </fileset>
+      <fileset dir="${build.home}">
+        <include name="*.jar"/>
+      </fileset>
+    </copy>
+    <copy todir="${stage.bin.dir}/${final.name}/apidocs">
+      <fileset dir="${build.home}/apidocs" />
+    </copy>
+
+    <!-- Source Distro -->
+    <mkdir dir="${stage.src.dir}/${final.name}-src"/>
+    <copy todir="${stage.src.dir}/${final.name}-src">
+      <fileset dir=".">
+        <include name="*.xml"/>
+        <include name="*.txt"/>
+        <include name="*.html"/>
+      </fileset>
+    </copy>
+    <copy todir="${stage.src.dir}/${final.name}-src/src">
+      <fileset dir="src" excludes="mantissa/**,experimental/**" />
+    </copy>
+    <zip  zipfile="${dist.home}/${final.name}.zip"     basedir="${stage.bin.dir}"/>
+    <zip  zipfile="${dist.home}/${final.name}-src.zip" basedir="${stage.src.dir}"/>
+    <tar  tarfile="${dist.home}/${final.name}.tar"     basedir="${stage.bin.dir}" longfile="gnu"/>
+    <tar  tarfile="${dist.home}/${final.name}-src.tar" basedir="${stage.src.dir}" longfile="gnu"/>
+    <gzip     src="${dist.home}/${final.name}.tar"     zipfile="${dist.home}/${final.name}.tar.gz"/>
+    <gzip     src="${dist.home}/${final.name}-src.tar" zipfile="${dist.home}/${final.name}-src.tar.gz"/>
+
+    <!-- clean up staging directories -->
+    <delete    dir="${stage.bin.dir}"/>
+    <delete    dir="${stage.src.dir}"/>
+
+  </target>
+
+
+<!-- ========== Gump Target ===================================================== -->
+
+  <target name="gump" depends="clean,test,javadoc,jar" description="Gump Target - clean,test,javadoc,jar"/>
+
+
+<!-- ========== Download Dependencies =========================================== -->
+
+    <target name="download-dependencies" 
+           depends="check-availability" unless="skip.download">
+        <echo message="doing download-dependencies..." />
+        <antcall target="download-junit" />
+    </target>
+
+    <target name="check-availability">
+        <echo message="doing check-availability..." />
+        <available file="${junit.jar}" property="junit.found"/>
+    </target>
+
+    <target name="download-junit" unless="junit.found">
+        <echo message="Downloading junit..."/>
+        <mkdir dir="${download.lib.dir}" />
+        <get dest="${download.lib.dir}/junit-${junit.version}.jar"
+            usetimestamp="true" ignoreerrors="true"
+            src="http://repo1.maven.org/maven2/junit/junit/${junit.version}/junit-${junit.version}.jar"/>
+    </target>
+      
+</project>
+
diff --git a/checkstyle.xml b/checkstyle.xml
new file mode 100644
index 0000000..9c12f6a
--- /dev/null
+++ b/checkstyle.xml
@@ -0,0 +1,178 @@
+<?xml version="1.0"?>
+
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+
+<!DOCTYPE module PUBLIC "-//Puppy Crawl//DTD Check Configuration 1.1//EN" "http://www.puppycrawl.com/dtds/configuration_1_1.dtd">
+
+<!-- commons math customization of default Checkstyle behavior -->
+<module name="Checker">
+  <property name="localeLanguage" value="en"/>
+
+  <module name="TreeWalker">
+   
+    <!-- Verify that EVERY source file has the appropriate license -->
+    <module name="Header">
+      <property name="headerFile" value="${checkstyle.header.file}"/>
+    </module>
+
+    <!-- Operator must be at end of wrapped line -->
+    <module name="OperatorWrap">
+      <property name="option" value="eol"/>
+    </module>
+    
+    <!-- Interfaces must be types (not just constants) -->
+    <module name="InterfaceIsType"/>
+
+    <!-- Must have class / interface header comments including scm version -->
+    <module name="JavadocType">
+        <property name="versionFormat" value="\$Revision.*\$ \$Date.*\$"/>
+    </module>
+        
+     <!-- Require method javadocs, allow undeclared RTE -->
+    <module name="JavadocMethod">
+      <property name="allowUndeclaredRTE" value="true"/>
+    </module>
+        
+    <!-- Require field javadoc -->
+    <module name="JavadocVariable"/>
+        
+    <!-- No tabs allowed! -->
+    <module name="TabCharacter"/>
+    
+    <!-- No public fields -->
+    <module name="VisibilityModifier">
+       <property name="protectedAllowed" value="true"/>
+    </module>
+    
+    <!-- Require hash code override when equals is -->
+    <module name="EqualsHashCode"/>
+    
+    <!-- Disallow unnecessary instantiation of Boolean, String -->
+    <module name="IllegalInstantiation">
+      <property name="classes" value="java.lang.Boolean, java.lang.String"/>
+    </module>
+
+    <!-- Required for SuppressionCommentFilter below -->
+    <module name="FileContentsHolder"/>
+ 
+    <!--  Import should be explicit, really needed and only from pure java packages -->
+    <module name="AvoidStarImport" />
+    <module name="UnusedImports" />
+    <module name="IllegalImport" />
+
+    <!-- Utility class should not be instantiated, they must have a private constructor -->
+    <module name="HideUtilityClassConstructor" />
+
+    <!-- Switch statements should be complete and with independent cases -->
+    <module name="FallThrough" />
+    <module name="MissingSwitchDefault" />
+
+    <!-- Constant names should obey the traditional all uppercase naming convention -->
+    <module name="ConstantName" />
+
+    <!-- Method parameters and local variables should not hide fields, except in constructors and setters -->
+    <module name="HiddenField">
+        <property name="ignoreConstructorParameter" value="true" />
+        <property name="ignoreSetter" value="true" />
+    </module>
+    
+    <!-- No trailing whitespace -->
+    <module name="Regexp">
+      <property name="format" value="[ \t]+$"/>
+      <property name="illegalPattern" value="true"/>
+      <property name="message" value="Trailing whitespace"/>
+    </module>
+    
+    <!-- No System.out.println() statements -->
+    <module name="Regexp">
+      <!-- no sysouts -->
+      <property name="format" value="System\.out\.println"/>
+      <property name="illegalPattern" value="true"/>
+    </module>
+
+    <!-- Authors should be in pom.xml file -->
+    <module name="Regexp">
+      <property name="format" value="@author"/>
+      <property name="illegalPattern" value="true"/>
+      <property name="message" value="developers names should be in pom file"/>
+    </module>
+
+    <!-- Use a consistent way to put modifiers -->
+    <module name="RedundantModifier" />
+    <module name="ModifierOrder" />
+
+    <!-- Use a consistent way to put declarations -->
+    <module name="DeclarationOrder" />
+
+    <!-- Don't add up parentheses when they are not required -->
+    <module name="UnnecessaryParentheses" />
+
+    <!--  Don't use too widespread catch (Exception, Throwable, RuntimeException)  -->
+    <module name="IllegalCatch" />
+
+    <!-- Don't use = or != for string comparisons -->
+    <module name="StringLiteralEquality" />
+ 
+   <!-- Don't declare multiple variables in the same statement -->
+    <module name="MultipleVariableDeclarations" />
+
+    <!-- String literals more than one character long should not be repeated several times -->
+    <!-- the "unchecked" string is also accepted to allow @SuppressWarnings("unchecked") -->
+    <module name="MultipleStringLiterals" >
+      <property name="ignoreStringsRegexp" value='^(("")|(".")|("unchecked"))$'/>
+    </module>
+
+    <!-- <module name="TodoComment" /> -->
+
+  </module>
+  
+  <!-- Require files to end with newline characters -->
+  <module name="NewlineAtEndOfFile"/>
+  
+  <!-- Require package javadoc -->
+  <module name="PackageHtml"/>
+
+  <!-- Setup special comments to suppress specific checks from source files -->
+  <module name="SuppressionCommentFilter">
+    <property name="offCommentFormat" value="CHECKSTYLE\: stop JavadocVariable"/>
+    <property name="onCommentFormat"  value="CHECKSTYLE\: resume JavadocVariable"/>
+    <property name="checkFormat"      value="JavadocVariable"/>
+  </module>
+  <module name="SuppressionCommentFilter">
+    <property name="offCommentFormat" value="CHECKSTYLE\: stop JavadocMethodCheck"/>
+    <property name="onCommentFormat"  value="CHECKSTYLE\: resume JavadocMethodCheck"/>
+    <property name="checkFormat"      value="JavadocMethodCheck"/>
+  </module>
+  <module name="SuppressionCommentFilter">
+    <property name="offCommentFormat" value="CHECKSTYLE\: stop ConstantName"/>
+    <property name="onCommentFormat"  value="CHECKSTYLE\: resume ConstantName"/>
+    <property name="checkFormat"      value="ConstantName"/>
+  </module>
+  <module name="SuppressionCommentFilter">
+    <property name="offCommentFormat" value="CHECKSTYLE\: stop HideUtilityClassConstructor"/>
+    <property name="onCommentFormat"  value="CHECKSTYLE\: resume HideUtilityClassConstructor"/>
+    <property name="checkFormat"      value="HideUtilityClassConstructor"/>
+  </module>
+  <module name="SuppressionCommentFilter">
+    <property name="offCommentFormat" value="CHECKSTYLE\: stop MultipleVariableDeclarations"/>
+    <property name="onCommentFormat"  value="CHECKSTYLE\: resume MultipleVariableDeclarations"/>
+    <property name="checkFormat"      value="MultipleVariableDeclarations"/>
+  </module>
+
+</module>
+
diff --git a/findbugs-exclude-filter.xml b/findbugs-exclude-filter.xml
new file mode 100644
index 0000000..6b93ca0
--- /dev/null
+++ b/findbugs-exclude-filter.xml
@@ -0,0 +1,254 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<!--
+  This file contains some false positive bugs detected by findbugs. Their
+  false positive nature has been analyzed individually and they have been
+  put here to instruct findbugs it must ignore them.
+-->
+<FindBugsFilter>
+
+  <!--  the following equality tests are part of the reference algorithms -->
+  <!--  which already know about limited precision of the double numbers -->
+  <Match>
+    <Class name="org.apache.commons.math.distribution.SaddlePointExpansion" />
+    <Or>
+      <Method name="getDeviancePart" params="double,double" returns="double" />
+      <Method name="getStirlingError" params="double" returns="double" />
+    </Or>
+    <Bug pattern="FE_FLOATING_POINT_EQUALITY" />
+  </Match>
+  <Match>
+    <Class name="org.apache.commons.math.optimization.univariate.BrentOptimizer" />
+    <Method name="localMin" params="boolean,double,double,double,double,double" returns="double" />
+    <Bug pattern="FE_FLOATING_POINT_EQUALITY" />
+  </Match>
+  <Match>
+    <Class name="org.apache.commons.math.analysis.solvers.BrentSolver" />
+    <Method name="solve" params="org.apache.commons.math.analysis.UnivariateRealFunction,double,double,double,double,double,double" returns="double" />
+    <Bug pattern="FE_FLOATING_POINT_EQUALITY" />
+  </Match>
+  <Match>
+    <Class name="org.apache.commons.math.analysis.solvers.MullerSolver" />
+    <Or>
+      <Method name="solve"  params="org.apache.commons.math.analysis.UnivariateRealFunction,double,double" returns="double" />
+      <Method name="solve2" params="org.apache.commons.math.analysis.UnivariateRealFunction,double,double" returns="double" />
+    </Or>
+    <Bug pattern="FE_FLOATING_POINT_EQUALITY" />
+  </Match>
+  <Match>
+    <Class name="org.apache.commons.math.linear.EigenDecompositionImpl" />
+    <Method name="findEigenVectors" params="double[][]" returns="void" />
+    <Bug pattern="FE_FLOATING_POINT_EQUALITY" />
+  </Match>
+
+  <!-- The following equality test is intentional and needed for semantic purposes -->
+  <Match>
+    <Class name="org.apache.commons.math.geometry.Vector3D" />
+    <Method name="equals" params="java.lang.Object" returns="boolean" />
+    <Bug pattern="FE_FLOATING_POINT_EQUALITY" />
+  </Match>
+  <Match>
+    <Class name="org.apache.commons.math.optimization.linear.LinearConstraint" />
+    <Method name="equals" params="java.lang.Object" returns="boolean" />
+    <Bug pattern="FE_FLOATING_POINT_EQUALITY" />
+  </Match>
+
+  <!-- The following equality test is intentional and needed for rounding purposes -->
+  <Match>
+    <Class name="org.apache.commons.math.util.MathUtils" />
+    <Method name="roundUnscaled" params="double,double,int" returns="double" />
+    <Bug pattern="FE_FLOATING_POINT_EQUALITY" />
+  </Match>
+
+  <!-- The following equality test is intentional for division protection -->
+  <Match>
+    <Class name="org.apache.commons.math.analysis.interpolation.LoessInterpolator" />
+    <Method name="smooth" params="double[],double[]" returns="double[]" />
+    <Bug pattern="FE_FLOATING_POINT_EQUALITY" />
+  </Match>
+
+  <!-- The following equality test is intentional for infinity detection -->
+  <Match>
+    <Class name="org.apache.commons.math.util.FastMath" />
+    <Method name="atan2" params="double,double" returns="double" />
+    <Bug pattern="FE_FLOATING_POINT_EQUALITY" />
+  </Match>
+  
+  <!-- Spurious: Findbugs confused by final local variables -->
+  <Match>
+    <Class name="org.apache.commons.math.util.FastMath" />
+    <Method name="atan" params="double,double,boolean" returns="double" />
+    <Bug pattern="DLS_DEAD_LOCAL_STORE" />
+  </Match>
+
+  <!-- the following expositions of internal representation are intentional and documented -->
+  <Match>
+    <Class name="org.apache.commons.math.stat.descriptive.AbstractUnivariateStatistic"/>
+    <Method name="getDataRef" params="" returns="double[]" />
+    <Bug pattern="EI_EXPOSE_REP" />
+  </Match>
+  <Match>
+    <Class name="org.apache.commons.math.optimization.RealPointValuePair"/>
+    <Method name="getPointRef" params="" returns="double[]" />
+    <Bug pattern="EI_EXPOSE_REP" />
+  </Match>
+  <Match>
+    <Class name="org.apache.commons.math.optimization.RealPointValuePair"/>
+    <Method name="<init>" params="double[],double,boolean" returns="void" />
+    <Bug pattern="EI_EXPOSE_REP2" />
+  </Match>
+  <Match>
+    <Class name="org.apache.commons.math.optimization.VectorialPointValuePair"/>
+    <Or>
+      <Method name="getPointRef" params="" returns="double[]" />
+      <Method name="getValueRef" params="" returns="double[]" />
+    </Or>
+    <Bug pattern="EI_EXPOSE_REP" />
+  </Match>
+  <Match>
+    <Class name="org.apache.commons.math.optimization.VectorialPointValuePair"/>
+    <Method name="<init>" params="double[],double[][],boolean" returns="void" />
+    <Bug pattern="EI_EXPOSE_REP2" />
+  </Match>
+  <Match>
+    <Class name="org.apache.commons.math.ode.sampling.DummyStepInterpolator"/>
+    <Method name="<init>" params="double[],double[],boolean" returns="void" />
+    <Bug pattern="EI_EXPOSE_REP2" />
+  </Match>
+  <Match>
+    <Class name="org.apache.commons.math.ode.sampling.AbstractStepInterpolator"/>
+    <Or>
+      <Method name="getInterpolatedState" params="" returns="double[]" />
+      <Method name="getInterpolatedDerivatives" params="" returns="double[]" />
+    </Or>
+    <Bug pattern="EI_EXPOSE_REP" />
+  </Match>
+  <Match>
+    <Class name="org.apache.commons.math.ode.sampling.NordsieckStepInterpolator"/>
+    <Method name="reinitialize" params="double,double,double[],org.apache.commons.math.linear.Array2DRowRealMatrix" returns="void" />
+    <Bug pattern="EI_EXPOSE_REP2" />
+  </Match>
+  <Match>
+    <Class name="org.apache.commons.math.ode.sampling.NordsieckStepInterpolator"/>
+    <Method name="getInterpolatedStateVariation" params="" returns="double[]" />
+    <Bug pattern="EI_EXPOSE_REP" />
+  </Match>
+  <Match>
+    <Class name="org.apache.commons.math.linear.BigMatrixImpl"/>
+    <Method name="<init>" params="java.math.BigDecimal[][],boolean" returns="void" />
+    <Bug pattern="EI_EXPOSE_REP2" />
+  </Match>
+  <Match>
+    <Class name="org.apache.commons.math.linear.BigMatrixImpl"/>
+    <Method name="getDataRef" params="" returns="java.math.BigDecimal[][]" />
+    <Bug pattern="EI_EXPOSE_REP" />
+  </Match>
+  <Match>
+    <Class name="org.apache.commons.math.linear.Array2DRowRealMatrix"/>
+    <Method name="<init>" params="double[][],boolean" returns="void" />
+    <Bug pattern="EI_EXPOSE_REP2" />
+  </Match>
+  <Match>
+    <Class name="org.apache.commons.math.linear.Array2DRowRealMatrix"/>
+    <Method name="getDataRef" params="" returns="double[][]" />
+    <Bug pattern="EI_EXPOSE_REP" />
+  </Match>
+  <Match>
+    <Class name="org.apache.commons.math.linear.RealMatrixImpl"/>
+    <Method name="<init>" params="double[][],boolean" returns="void" />
+    <Bug pattern="EI_EXPOSE_REP2" />
+  </Match>
+  <Match>
+    <Class name="org.apache.commons.math.linear.RealMatrixImpl"/>
+    <Method name="getDataRef" params="" returns="double[][]" />
+    <Bug pattern="EI_EXPOSE_REP" />
+  </Match>
+  <Match>
+    <Class name="org.apache.commons.math.linear.BlockFieldMatrix"/>
+    <Method name="<init>" params="int,int,org.apache.commons.math.FieldElement[][],boolean" returns="void" />
+    <Bug pattern="EI_EXPOSE_REP2" />
+  </Match>
+  <Match>
+    <Class name="org.apache.commons.math.linear.Array2DRowFieldMatrix"/>
+    <Method name="<init>" params="org.apache.commons.math.FieldElement[][],boolean" returns="void" />
+    <Bug pattern="EI_EXPOSE_REP2" />
+  </Match>
+  <Match>
+    <Class name="org.apache.commons.math.linear.Array2DRowFieldMatrix"/>
+    <Method name="getDataRef" params="" returns="org.apache.commons.math.FieldElement[][]" />
+    <Bug pattern="EI_EXPOSE_REP" />
+  </Match>
+  <Match>
+    <Class name="org.apache.commons.math.linear.BlockRealMatrix"/>
+    <Method name="<init>" params="int,int,double[][],boolean" returns="void" />
+    <Bug pattern="EI_EXPOSE_REP2" />
+  </Match>
+  <Match>
+    <Class name="org.apache.commons.math.util.ResizableDoubleArray"/>
+    <Or>
+      <Method name="getValues"         params="" returns="double[]" />
+      <Method name="getInternalValues" params="" returns="double[]" />
+    </Or>
+    <Bug pattern="EI_EXPOSE_REP" />
+  </Match>
+  <Match>
+    <Class name="org.apache.commons.math.linear.ArrayRealVector"/>
+    <Method name="getDataRef" params="" returns="double[]" />
+    <Bug pattern="EI_EXPOSE_REP" />
+  </Match>
+  <Match>
+    <Class name="org.apache.commons.math.linear.ArrayFieldVector"/>
+    <Method name="getDataRef" params="" returns="org.apache.commons.math.FieldElement[]" />
+    <Bug pattern="EI_EXPOSE_REP" />
+  </Match>
+  <Match>
+    <Class name="org.apache.commons.math.stat.clustering.EuclideanIntegerPoint"/>
+    <Method name="<init>" params="int[]" returns="void" />
+    <Bug pattern="EI_EXPOSE_REP2" />
+  </Match>
+  <Match>
+    <Class name="org.apache.commons.math.stat.clustering.EuclideanIntegerPoint"/>
+    <Method name="getPoint" params="" returns="int[]" />
+    <Bug pattern="EI_EXPOSE_REP" />
+  </Match>
+
+  <!-- The following cases are intentional unit tests for null parameters -->
+  <Match>
+    <Class name="org.apache.commons.math.stat.StatUtilsTest" />
+    <Method name="testPercentile" params="" returns="void" />
+    <Bug pattern="NP_NULL_PARAM_DEREF_NONVIRTUAL" />
+  </Match>
+  <Match>
+    <Class name="org.apache.commons.math.analysis.UnivariateRealSolverFactoryImplTest" />
+    <Method name="testNewNewtonSolverNull" params="" returns="void" />
+    <Bug pattern="NP_NULL_PARAM_DEREF_ALL_TARGETS_DANGEROUS" />
+  </Match>
+  <Match>
+    <Class name="org.apache.commons.math.stat.regression.OLSMultipleLinearRegressionTest" />
+    <Method name="cannotAddNullYSampleData" params="" returns="void" />
+    <Bug pattern="NP_NULL_PARAM_DEREF_ALL_TARGETS_DANGEROUS" />
+  </Match>
+  
+  <!-- IntDoublePair intentionally implements Comparable inconsistently with equals -->
+  <Match>
+    <Class name="org.apache.commons.math.stat.ranking.NaturalRanking$IntDoublePair" />
+    <Bug pattern="EQ_COMPARETO_USE_OBJECT_EQUALS" />
+  </Match>
+
+</FindBugsFilter>
diff --git a/license-header.txt b/license-header.txt
new file mode 100644
index 0000000..ae6f28c
--- /dev/null
+++ b/license-header.txt
@@ -0,0 +1,16 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..af58bf1
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,364 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <parent>
+    <groupId>org.apache.commons</groupId>
+    <artifactId>commons-parent</artifactId>
+    <version>18</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+  <groupId>org.apache.commons</groupId>
+  <artifactId>commons-math</artifactId>
+  <version>2.2</version>
+  <name>Commons Math</name>
+
+  <inceptionYear>2003</inceptionYear>
+  <description>The Math project is a library of lightweight, self-contained mathematics and statistics components addressing the most common practical problems not immediately available in the Java programming language or commons-lang.</description>
+
+  <url>http://commons.apache.org/math/</url>
+
+  <issueManagement>
+    <system>jira</system>
+    <url>http://issues.apache.org/jira/browse/MATH</url>
+  </issueManagement>
+
+  <scm>
+    <connection>scm:svn:http://svn.apache.org/repos/asf/commons/proper/math/trunk</connection>
+    <developerConnection>scm:svn:https://svn.apache.org/repos/asf/commons/proper/math/trunk</developerConnection>
+    <url>http://svn.apache.org/viewvc/commons/proper/math/trunk</url>
+  </scm>
+  
+  <distributionManagement>
+    <site>
+      <id>people.apache.org</id>
+      <name>Commons Math</name>
+      <url>scp://people.apache.org/www/commons.apache.org/math</url>
+    </site>
+  </distributionManagement>
+
+  <developers>
+    <developer>
+      <name>Mikkel Meyer Andersen</name>
+      <id>mikl</id>
+      <email>mikl at apache dot org</email>
+    </developer>
+    <developer>
+      <name>Bill Barker</name>
+      <id>billbarker</id>
+      <email>billbarker at apache dot org</email>
+    </developer>
+    <developer>
+      <name>Albert Davidson Chou</name>
+      <id>achou</id>
+      <email>achou at apache dot org</email>
+    </developer>
+    <developer>
+      <name>Mark Diggory</name>
+      <id>mdiggory</id>
+      <email>mdiggory at apache dot org</email>
+    </developer>
+    <developer>
+      <name>Robert Burrell Donkin</name>
+      <id>rdonkin</id>
+      <email>rdonkin at apache dot org</email>
+    </developer>
+    <developer>
+      <name>Tim O'Brien</name>
+      <id>tobrien</id>
+      <email>tobrien at apache dot org</email>
+    </developer>
+    <developer>
+      <name>Luc Maisonobe</name>
+      <id>luc</id>
+      <email>luc at apache dot org</email>
+    </developer>
+    <developer>
+      <name>J. Pietschmann</name>
+      <id>pietsch</id>
+      <email>j3322ptm at yahoo dot de</email>
+    </developer>
+    <developer>
+      <name>Dimitri Pourbaix</name>
+      <id>dimpbx</id>
+      <email>dimpbx at apache dot org</email>
+    </developer>
+    <developer>
+      <name>Phil Steitz</name>
+      <id>psteitz</id>
+      <email>psteitz at apache dot org</email>
+    </developer>
+    <developer>
+      <name>Brent Worden</name>
+      <id>brentworden</id>
+      <email>brentworden at apache dot org</email>
+    </developer>
+    <developer>
+      <name>Gilles Sadowski</name>
+      <id>erans</id>
+      <email>erans at apache dot org</email>
+    </developer>
+  </developers>
+  <contributors>
+    <contributor>
+      <name>C. Scott Ananian</name>
+    </contributor>
+    <contributor>
+      <name>Mark Anderson</name>
+    </contributor>
+    <contributor>
+      <name>Rémi Arntzen</name>
+    </contributor>
+    <contributor>
+      <name>Michael Bjorkegren</name>
+    </contributor>
+    <contributor>
+      <name>John Bollinger</name>
+    </contributor>
+    <contributor>
+      <name>Cyril Briquet</name>
+    </contributor>
+    <contributor>
+      <name>Paul Cowan</name>
+    </contributor>
+    <contributor>
+      <name>Benjamin Croizet</name>
+    </contributor>
+    <contributor>
+      <name>Larry Diamond</name>
+    </contributor>
+    <contributor>
+      <name>Rodrigo di Lorenzo Lopes</name>
+    </contributor>
+    <contributor>
+      <name>Hasan Diwan</name>
+    </contributor>
+    <contributor>
+      <name>Ted Dunning</name>
+    </contributor>
+    <contributor>
+      <name>John Gant</name>
+    </contributor>
+    <contributor>
+      <name>Ken Geis</name>
+    </contributor>
+    <contributor>
+      <name>Bernhard Grünewaldt</name>
+    </contributor>
+    <contributor>
+      <name>Elliotte Rusty Harold</name>
+    </contributor>
+    <contributor>
+      <name>Matthias Hummel</name>
+    </contributor>
+    <contributor>
+      <name>Ismael Juma</name>
+    </contributor>
+    <contributor>
+      <name>Eugene Kirpichov</name>
+    </contributor>
+    <contributor>
+      <name>Piotr Kochanski</name>
+    </contributor>
+    <contributor>
+      <name>Bob MacCallum</name>
+    </contributor>
+    <contributor>
+      <name>Jake Mannix</name>
+    </contributor>
+    <contributor>
+      <name>Benjamin McCann</name>
+    </contributor>
+    <contributor>
+      <name>J. Lewis Muir</name>
+    </contributor>
+    <contributor>
+      <name>Fredrik Norin</name>
+    </contributor>
+    <contributor>
+      <name>Sujit Pal</name>
+    </contributor>
+    <contributor>
+      <name>Todd C. Parnell</name>
+    </contributor>
+    <contributor>
+      <name>Andreas Rieger</name>
+    </contributor>
+    <contributor>
+      <name>Bill Rossi</name>
+    </contributor>
+    <contributor>
+      <name>Matthew Rowles</name>
+    </contributor>
+    <contributor>
+      <name>Joni Salonen</name>
+    </contributor>
+    <contributor>
+      <name>Christopher Schuck</name>
+    </contributor>
+    <contributor>
+      <name>Christian Semrau</name>
+    </contributor>
+    <contributor>
+      <name>David Stefka</name>
+    </contributor>
+    <contributor>
+      <name>Mauro Talevi</name>
+    </contributor>
+    <contributor>
+      <name>Kim van der Linde</name>
+    </contributor>
+    <contributor>
+      <name>Jörg Weimar</name>
+    </contributor>
+    <contributor>
+      <name>Xiaogang Zhang</name>
+    </contributor>
+  </contributors>
+
+  <dependencies>
+    <dependency>
+      <groupId>junit</groupId>
+      <artifactId>junit</artifactId>
+      <version>4.4</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <properties>
+    <commons.componentid>math</commons.componentid>
+    <commons.release.version>2.2</commons.release.version>
+    <commons.rc.version>RC6</commons.rc.version>
+    <commons.binary.suffix></commons.binary.suffix>
+    <commons.jira.id>MATH</commons.jira.id>
+    <commons.jira.pid>12310485</commons.jira.pid>
+    <maven.compile.source>1.5</maven.compile.source>
+    <maven.compile.target>1.5</maven.compile.target>
+    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+    <!-- Allow severity to be overriden by the command-line option -DminSeverity=level -->
+    <minSeverity>info</minSeverity>
+  </properties> 
+
+  <build>
+      <plugins>
+        <plugin>
+          <groupId>org.apache.maven.plugins</groupId>
+          <artifactId>maven-surefire-plugin</artifactId>
+            <configuration>
+              <includes>
+                <include>**/*Test.java</include>
+                <include>**/*TestBinary.java</include>
+                <include>**/*TestPermutations.java</include>
+              </includes>
+              <excludes>
+                <exclude>**/*AbstractTest.java</exclude>
+              </excludes>
+          </configuration>
+        </plugin>
+        <plugin>
+          <artifactId>maven-assembly-plugin</artifactId>
+          <version>2.2-beta-4</version>
+          <configuration>
+            <descriptors>
+              <descriptor>src/main/assembly/src.xml</descriptor>
+              <descriptor>src/main/assembly/bin.xml</descriptor>
+            </descriptors>
+          </configuration>
+        </plugin>
+        <plugin>
+          <groupId>org.codehaus.mojo</groupId>
+          <artifactId>clirr-maven-plugin</artifactId>
+          <version>2.3</version>
+          <executions>
+            <execution>
+              <goals>
+              </goals>
+            </execution>
+          </executions>
+        </plugin>
+      </plugins>
+      <resources>
+        <resource>
+          <directory>${basedir}</directory>
+          <targetPath>META-INF</targetPath>
+          <includes>
+            <include>NOTICE.txt</include>
+            <include>LICENSE.txt</include>
+          </includes>
+        </resource>
+        <resource>
+          <directory>${basedir}/src/main/resources/META-INF/localization</directory>
+          <targetPath>META-INF/localization</targetPath>
+        </resource>
+      </resources>
+
+  </build>
+  <reporting>
+    <plugins>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>findbugs-maven-plugin</artifactId>
+        <version>2.1</version>
+        <configuration>
+          <threshold>Normal</threshold>
+          <effort>Default</effort>
+          <excludeFilterFile>${basedir}/findbugs-exclude-filter.xml</excludeFilterFile>
+       </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-changes-plugin</artifactId>
+        <version>2.3</version>
+        <configuration>
+          <xmlPath>${basedir}/src/site/xdoc/changes.xml</xmlPath>
+          <issueLinkTemplatePerSystem>
+            <default>%URL%/%ISSUE%</default>
+          </issueLinkTemplatePerSystem>
+          <template>math-release-notes.vm</template>
+          <templateDirectory>templates</templateDirectory>
+        </configuration>
+        <reportSets>
+          <reportSet>
+            <reports>
+              <report>changes-report</report>
+            </reports>
+          </reportSet>
+        </reportSets>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-checkstyle-plugin</artifactId>
+        <version>2.3</version>
+        <configuration>
+          <configLocation>${basedir}/checkstyle.xml</configLocation>
+          <enableRulesSummary>false</enableRulesSummary>
+          <headerFile>${basedir}/license-header.txt</headerFile>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>clirr-maven-plugin</artifactId>
+        <version>2.2.3</version>
+        <configuration>
+          <minSeverity>${minSeverity}</minSeverity>
+         </configuration>
+      </plugin>
+    </plugins>
+  </reporting>
+</project>
+
diff --git a/src/main/assembly/bin.xml b/src/main/assembly/bin.xml
new file mode 100644
index 0000000..653b41b
--- /dev/null
+++ b/src/main/assembly/bin.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<assembly>
+  <id></id>
+  <formats>
+    <format>tar.gz</format>
+    <format>zip</format>
+  </formats>
+  <includeSiteDirectory>false</includeSiteDirectory>
+  <fileSets>
+    <fileSet>
+      <includes>
+        <include>LICENSE*</include>
+        <include>NOTICE*</include>
+        <include>RELEASE-NOTES.txt</include>
+      </includes>
+    </fileSet>
+    <fileSet>
+      <directory>target</directory>
+      <outputDirectory></outputDirectory>
+      <includes>
+        <include>*.jar</include>
+      </includes>
+    </fileSet>
+    <fileSet>
+      <directory>target/site</directory>
+      <outputDirectory>docs</outputDirectory>
+      <includes>
+        <include>apidocs/**</include>
+        <include>css/**</include>
+        <include>images/**</include>
+        <include>userguide/**</include>
+      </includes>
+    </fileSet>
+  </fileSets>
+</assembly>
diff --git a/src/main/assembly/src.xml b/src/main/assembly/src.xml
new file mode 100644
index 0000000..8bd4b58
--- /dev/null
+++ b/src/main/assembly/src.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+   contributor license agreements.  See the NOTICE file distributed with
+   this work for additional information regarding copyright ownership.
+   The ASF licenses this file to You under the Apache License, Version 2.0
+   (the "License"); you may not use this file except in compliance with
+   the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+<assembly>
+  <id>src</id>
+  <formats>
+    <format>tar.gz</format>
+    <format>zip</format>
+  </formats>
+  <baseDirectory>
+     ${project.artifactId}-${commons.release.version}-src
+  </baseDirectory>
+  <fileSets>
+    <fileSet>
+      <includes>
+        <include>*.txt</include>
+        <include>*.xml</include>
+      </includes>
+    </fileSet>
+    <fileSet>
+      <directory>src</directory>
+    </fileSet>
+  </fileSets>
+</assembly>
+
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.
+ */
+ at 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 × e<sub>1</sub> =
+     * e<sub>1</sub> × 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 × a.
+     * @param a element to multiply
+     * @return a new element representing this × a
+     */
+    T multiply(T a);
+
+    /** Compute this ÷ a.
+     * @param a element to add
+     * @return a new element representing this ÷ 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
+ */
+ at 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 ∫ Li<sup>2</sup> where Li (x) =
+ * ∏ (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.
+ */
+ at 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).
+ */
+ at 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 ± INFINITY i) = 1 &#x2213; INFINITY i
+     * cos(±INFINITY + i) = NaN + NaN i
+     * cos(±INFINITY ± 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 ± INFINITY i) = NaN + NaN i
+     * cosh(±INFINITY + i) = INFINITY ± INFINITY i
+     * cosh(±INFINITY ± 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 ± INFINITY i) = NaN + NaN i
+     * exp(INFINITY + i) = INFINITY + INFINITY i
+     * exp(-INFINITY + i) = 0 + 0i
+     * exp(±INFINITY ± 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 ± INFINITY i) = INFINITY ± (π/2)i
+     * log(INFINITY + i) = INFINITY + 0i
+     * log(-INFINITY + i) = INFINITY + πi
+     * log(INFINITY ± INFINITY i) = INFINITY ± (π/4)i
+     * log(-INFINITY ± INFINITY i) = INFINITY ± (3π/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·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 ± INFINITY i) = 1 ± INFINITY i
+     * sin(±INFINITY + i) = NaN + NaN i
+     * sin(±INFINITY ± 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 ± INFINITY i) = NaN + NaN i
+     * sinh(±INFINITY + i) = ± INFINITY + INFINITY i
+     * sinh(±INFINITY ± 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 ≥ 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 ± INFINITY i) = INFINITY + NaN i
+     * sqrt(INFINITY + i) = INFINITY + 0i
+     * sqrt(-INFINITY + i) = 0 + INFINITY i
+     * sqrt(INFINITY ± INFINITY i) = INFINITY + NaN i
+     * sqrt(-INFINITY ± INFINITY i) = NaN ± 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 ± INFINITY i) = 0 + NaN i
+     * tan(±INFINITY + i) = NaN + NaN i
+     * tan(±INFINITY ± INFINITY i) = NaN + NaN i
+     * tan(±π/2 + 0 i) = ±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 ± INFINITY i) = NaN + NaN i
+     * tanh(±INFINITY + i) = NaN + 0 i
+     * tanh(±INFINITY ± INFINITY i) = NaN + NaN i
+     * tanh(0 + (π/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πk/n) + i (sin(phi + 2π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·e<sup>i·theta</sup></code>,
+     * computed as <code>r·cos(theta) + r·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, π/4) = INFINITY + INFINITY i
+     * polar2Complex(INFINITY, 0) = INFINITY + NaN i
+     * polar2Complex(INFINITY, -π/4) = INFINITY - INFINITY i
+     * polar2Complex(INFINITY, 5π/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·e<sup>i·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 × mant × (radix)<sup>exp</sup>;</p>
+ *  </pre>
+ *  where sign is ±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 × 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<=x<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 <= divisor < 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 √2. */
+    private static String sqr2String;
+
+    /** High precision string representation of √2 / 2. */
+    private static String sqr2ReciprocalString;
+
+    /** High precision string representation of √3. */
+    private static String sqr3String;
+
+    /** High precision string representation of √3 / 3. */
+    private static String sqr3ReciprocalString;
+
+    /** High precision string representation of π. */
+    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 √2. */
+    private final Dfp sqr2;
+
+    /** A two elements {@link Dfp} array with value √2 split in two pieces. */
+    private final Dfp[] sqr2Split;
+
+    /** A {@link Dfp} with value √2 / 2. */
+    private final Dfp sqr2Reciprocal;
+
+    /** A {@link Dfp} with value √3. */
+    private final Dfp sqr3;
+
+    /** A {@link Dfp} with value √3 / 3. */
+    private final Dfp sqr3Reciprocal;
+
+    /** A {@link Dfp} with value π. */
+    private final Dfp pi;
+
+    /** A two elements {@link Dfp} array with value π 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 √2.
+     * @return a {@link Dfp} with value √2
+     */
+    public Dfp getSqr2() {
+        return sqr2;
+    }
+
+    /** Get the constant √2 split in two pieces.
+     * @return a {@link Dfp} with value √2 split in two pieces
+     */
+    public Dfp[] getSqr2Split() {
+        return sqr2Split.clone();
+    }
+
+    /** Get the constant √2 / 2.
+     * @return a {@link Dfp} with value √2 / 2
+     */
+    public Dfp getSqr2Reciprocal() {
+        return sqr2Reciprocal;
+    }
+
+    /** Get the constant √3.
+     * @return a {@link Dfp} with value √3
+     */
+    public Dfp getSqr3() {
+        return sqr3;
+    }
+
+    /** Get the constant √3 / 3.
+     * @return a {@link Dfp} with value √3 / 3
+     */
+    public Dfp getSqr3Reciprocal() {
+        return sqr3Reciprocal;
+    }
+
+    /** Get the constant π.
+     * @return a {@link Dfp} with value π
+     */
+    public Dfp getPi() {
+        return pi;
+    }
+
+    /** Get the constant π split in two pieces.
+     * @return a {@link Dfp} with value π 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 π 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 π
+     */
+    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 × 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> × 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 × mant × (radix)<sup>exp</sup>;</p>
+</pre>
+where sign is ±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 < x) = <code>p</code>.
+     *
+     * @param p the desired probability
+     * @return x, such that P(X < 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 < <i>lower bound</i>) < <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 < <i>upper bound</i>) > <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 ≤ X ≤ x1).
+     * <p>
+     * The default implementation uses the identity</p>
+     * <p>
+     * P(x0 ≤ X ≤ x1) = P(X ≤ x1) - P(X ≤ 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 ≤ 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 ≤ X ≤ 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 ≤ 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 ≤ X ≤ 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 ≤ x) ≤ <code>p</code>.
+     *
+     * @param p the desired probability
+     * @return the largest x such that P(X ≤ 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 < <i>lower bound</i>) < <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 < <i>upper bound</i>) > <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 < <i>lower bound</i>) <
+     *         <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 < <i>upper bound</i>) >
+     *         <code>p</code>
+     */
+    @Override
+    protected int getDomainUpperBound(double p) {
+        return numberOfTrials;
+    }
+
+    /**
+     * For this distribution, X, this method returns P(X ≤ 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 ≤ x) ≤ <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 ≤ 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 < <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 < 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 < 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 < <i>lower bound</i>) < <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 < <i>upper bound</i>) > <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 < 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 < 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 < 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 < <i>lower bound</i>) < <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 < <i>upper bound</i>) > <code>p</code>
+     */
+    @Override
+    protected double getDomainUpperBound(double p) {
+        // NOTE: chi squared is skewed to the left
+        // NOTE: therefore, P(X < μ) > .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 < μ) > .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 < 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 ≤ 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 ≤ X ≤ 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 < 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 < 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 < 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 < <i>lower bound</i>) < <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 < <i>upper bound</i>) > <code>p</code>
+     */
+    @Override
+    protected double getDomainUpperBound(double p) {
+        // NOTE: exponential is skewed to the left
+        // NOTE: therefore, P(X < μ) > .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 < μ) > .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 < 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 < 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 < 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 < <i>lower bound</i>) < <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 < <i>upper bound</i>) > <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 > 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 > 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 < 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 < 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 < 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 < <i>lower bound</i>) < <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 < <i>upper bound</i>) > <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 < μ) > .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 < μ) > .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) $
+ */
+ at 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 ≤ 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 < <i>lower bound</i>) <
+     *         <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 < <i>upper bound</i>) >
+     *         <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 ≥ 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 ≤ X ≤ 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 ≤ X ≤ 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 ≤ 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 ≤ X ≤ 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 ≤ 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 ≤ 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 ≤ 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 π) */
+    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 < <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 < 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 < 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 < <i>lower bound</i>) < <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 < <i>upper bound</i>) > <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 < <i>lower bound</i>) <
+     *         <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 < <i>upper bound</i>) >
+     *         <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 ≤ 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 ≤ x) ≤ <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 ≤ 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 ≤ 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 ≤ 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 ≤ 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 ≤ 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 ≤ 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 π). */
+    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 < <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 < 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 < 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 < <i>lower bound</i>) < <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 < <i>upper bound</i>) > <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 > 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 > 2</code> then <code>df / (df - 2)</code> </li>
+     *  <li>if <code>1 < df <= 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 < <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 < 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 < 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 < <i>lower bound</i>) < <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 < <i>upper bound</i>) > <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 ≤ 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 ≤ 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 ≤ 0 or s ≤ 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 ≤ 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 ≤ 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 ≤ 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 ≤ 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 < <i>lower bound</i>) < <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 < <i>upper bound</i>) > <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 ≥ 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
+ *
+ */
+ at 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
+ *
+ */
+ at 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
+ *
+ */
+ at 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
+ *
+ */
+ at 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
+ *
+ */
+ at 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
+ *
+ */
+ at 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>) <
+     * 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>) <
+     * 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érique matricielle
+ * appliquée à l'art de l'ingé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
+ *
+ */
+ at 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érique matricielle appliquée à
+   * l'art de l'ingé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
+
+ */
+ at 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
+ */
+
+ at 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 π/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 θ about the unit vector (x, y, z) is the same as the
+   * rotation build from quaternion components { cos(-θ/2),
+   * x * sin(-θ/2), y * sin(-θ/2), z * sin(-θ/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 π)
+   * @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 π + a<sub>1</sub>, π
+   * - a<sub>2</sub> and π + 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 -π/2 and π/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 π (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
+   * -π/2 or +π/2, for Euler angle singularities occur when the
+   * second angle is close to 0 or π, 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 π. 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 (α) around Z
+   *              (0 is +X, π/2 is +Y, π is -X and 3π/2 is -Y)
+   * @param delta elevation (δ) above (XY) plane, from -π/2 to +π/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>∞</sub> norm for the vector.
+   * @return L<sub>∞</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 (α) of the vector, between -π and +π
+   * @see #Vector3D(double, double)
+   */
+  public double getAlpha() {
+    return FastMath.atan2(y, x);
+  }
+
+  /** Get the elevation of the vector.
+   * @return elevation (δ) of the vector, between -π/2 and +π/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>∞</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>∞</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>∞</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 × n matrix A can be written as the product of three matrices:
+ * A = U × B × V<sup>T</sup> with U an m × m orthogonal matrix,
+ * B an m × n bi-diagonal matrix (lower diagonal if m < n, upper diagonal
+ * otherwise), and V an n × 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
+ */
+ at 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) $
+ */
+ at 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 × 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 × X = B.
+         * <p>The A matrix is implicit here. It is </p>
+         * @param b right-hand side of the equation A × X = B
+         * @return a vector X such that A × 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 × X = B.
+ * <p>Decomposition algorithms decompose an A matrix has a product of several specific
+ * matrices from which they can solve A × X = B in least squares sense: they find X
+ * such that ||A × 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 × 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 × 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 × X = B
+     * @return a vector X that minimizes the two norm of A × 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 × 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 × X = B
+     * @return a vector X that minimizes the two norm of A × 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 × 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 × X = B
+     * @return a matrix X that minimizes the two norm of A × 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 × D × V<sup>T</sup>.
+ * A, V and D are all m × 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 × 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 × 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 × X = B for symmetric matrices A.
+         * <p>
+         * This method only find exact linear solutions, i.e. solutions for
+         * which ||A × X - B|| is exactly 0.
+         * </p>
+         * @param b
+         *            right-hand side of the equation A × X = B
+         * @return a vector X that minimizes the two norm of A × 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 × X = B for symmetric matrices A.
+         * <p>
+         * This method only find exact linear solutions, i.e. solutions for
+         * which ||A × X - B|| is exactly 0.
+         * </p>
+         * @param b
+         *            right-hand side of the equation A × X = B
+         * @return a vector X that minimizes the two norm of A × 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 × X = B for symmetric matrices A.
+         * <p>
+         * This method only find exact linear solutions, i.e. solutions for
+         * which ||A × X - B|| is exactly 0.
+         * </p>
+         * @param b
+         *            right-hand side of the equation A × X = B
+         * @return a matrix X that minimizes the two norm of A × 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 × X = B.
+ * <p>Decomposition algorithms decompose an A matrix has a product of several specific
+ * matrices from which they can solve A × X = B in least squares sense: they find X
+ * such that ||A × 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 × 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 × 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 × X = B
+     * @return a vector X that minimizes the two norm of A × 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 × 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 × X = B
+     * @return a vector X that minimizes the two norm of A × 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 × 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 × X = B
+     * @return a matrix X that minimizes the two norm of A × 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×A = L×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 × 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×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 × X = B.
+         * <p>The A matrix is implicit here. It is </p>
+         * @param b right-hand side of the equation A × X = B
+         * @return a vector X such that A × 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×A = L×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 × 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×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 × X = B.
+         * <p>The A matrix is implicit here. It is </p>
+         * @param b right-hand side of the equation A × X = B
+         * @return a vector X such that A × 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×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×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×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×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 × 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×n, Q is m×m and R m×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 × X = B.
+         * <p>The A matrix is implicit here. It is </p>
+         * @param b right-hand side of the equation A × X = B
+         * @return a vector X that minimizes the two norm of A × 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}
+ */
+ at 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>∞</sub> norm of the vector.
+     * <p>The L<sub>∞</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>∞</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>∞</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,
+ * Σ and V such that A = U × Σ × V<sup>T</sup>. Let A be
+ * a m × n matrix, then U is a m × p orthogonal matrix, Σ is a
+ * p × p diagonal matrix with positive or null elements, V is a p ×
+ * 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 Σ of the decomposition.
+     * <p>Σ is a diagonal matrix. The singular values are provided in
+     * non-increasing order, for compatibility with Jama.</p>
+     * @return the Σ matrix
+     */
+    RealMatrix getS();
+
+    /**
+     * Returns the diagonal elements of the matrix Σ of the decomposition.
+     * <p>The singular values are provided in non-increasing order, for
+     * compatibility with Jama.</p>
+     * @return the diagonal elements of the Σ 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 × n covariance matrix.
+     * <p>The covariance matrix is V × J × 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 × 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) × 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 × 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,
+ * Σ and V such that A = U × Σ × V<sup>T</sup>. Let A be
+ * a m × n matrix, then U is a m × p orthogonal matrix, Σ is a
+ * p × p diagonal matrix with positive or null elements, V is a p ×
+ * 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 × X = B in least square sense.
+         * <p>
+         * The m×n matrix A may not be square, the solution X is such that
+         * ||A × X - B|| is minimal.
+         * </p>
+         * @param b
+         *            right-hand side of the equation A × X = B
+         * @return a vector X that minimizes the two norm of A × 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 × X = B in least square sense.
+         * <p>
+         * The m×n matrix A may not be square, the solution X is such that
+         * ||A × X - B|| is minimal.
+         * </p>
+         * @param b
+         *            right-hand side of the equation A × X = B
+         * @return a vector X that minimizes the two norm of A × 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 × X = B in least square sense.
+         * <p>
+         * The m×n matrix A may not be square, the solution X is such that
+         * ||A × X - B|| is minimal.
+         * </p>
+         * @param b
+         *            right-hand side of the equation A × X = B
+         * @return a matrix X that minimizes the two norm of A × 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 × m matrix A can be written as the product of three matrices:
+ * A = Q × T × Q<sup>T</sup> with Q an orthogonal matrix and T a symmetrical
+ * tridiagonal matrix. Both Q and T are m × 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
+ */
+ at 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
+ */
+ at 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 × (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
+ */
+ at 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
+ */
+ at 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
+ */
+ at 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
+ */
+ at 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
+ */
+ at 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) + ∑<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)×(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) + ∑<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)×(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 ... ±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 ... ±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) + ∑<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)×(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 ... ±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
+     * ∑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 = ∑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° 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×(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 ∑(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,
+ * ω and φ such that f (t) = a cos (ω t + φ).
+ * </p>
+ *
+ * <p>From the analytical expression, we can compute two primitives :
+ * <pre>
+ *     If2  (t) = ∫ f<sup>2</sup>  = a<sup>2</sup> × [t + S (t)] / 2
+ *     If'2 (t) = ∫ f'<sup>2</sup> = a<sup>2</sup> ω<sup>2</sup> × [t - S (t)] / 2
+ *     where S (t) = sin (2 (ω t + φ)) / (2 ω)
+ * </pre>
+ * </p>
+ *
+ * <p>We can remove S between these expressions :
+ * <pre>
+ *     If'2 (t) = a<sup>2</sup> ω<sup>2</sup> t - ω<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 × t + B × 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 × (t<sub>i</sub> - t<sub>1</sub>) + B × (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 × x<sub>i</sub> + B × y<sub>i</sub>, the
+ * coefficients A and B that minimize a least square criterion
+ * ∑ (z<sub>i</sub> - z (x<sub>i</sub>, y<sub>i</sub>))<sup>2</sup> are given by these expressions:</p>
+ * <pre>
+ *
+ *         ∑y<sub>i</sub>y<sub>i</sub> ∑x<sub>i</sub>z<sub>i</sub> - ∑x<sub>i</sub>y<sub>i</sub> ∑y<sub>i</sub>z<sub>i</sub>
+ *     A = ------------------------
+ *         ∑x<sub>i</sub>x<sub>i</sub> ∑y<sub>i</sub>y<sub>i</sub> - ∑x<sub>i</sub>y<sub>i</sub> ∑x<sub>i</sub>y<sub>i</sub>
+ *
+ *         ∑x<sub>i</sub>x<sub>i</sub> ∑y<sub>i</sub>z<sub>i</sub> - ∑x<sub>i</sub>y<sub>i</sub> ∑x<sub>i</sub>z<sub>i</sub>
+ *     B = ------------------------
+ *         ∑x<sub>i</sub>x<sub>i</sub> ∑y<sub>i</sub>y<sub>i</sub> - ∑x<sub>i</sub>y<sub>i</sub> ∑x<sub>i</sub>y<sub>i</sub>
+ * </pre>
+ * </p>
+ *
+ *
+ * <p>In fact, we can assume both a and ω are positive and
+ * compute them directly, knowing that A = a<sup>2</sup> ω<sup>2</sup> and that
+ * B = - ω<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> = ∫ f<sup>2</sup> from t<sub>1</sub> to t<sub>i</sub>
+ *   z<sub>i</sub> = ∫ f'<sup>2</sup> from t<sub>1</sub> to t<sub>i</sub>
+ *   update the sums ∑x<sub>i</sub>x<sub>i</sub>, ∑y<sub>i</sub>y<sub>i</sub>, ∑x<sub>i</sub>y<sub>i</sub>, ∑x<sub>i</sub>z<sub>i</sub> and ∑y<sub>i</sub>z<sub>i</sub>
+ * end for
+ *
+ *            |--------------------------
+ *         \  | ∑y<sub>i</sub>y<sub>i</sub> ∑x<sub>i</sub>z<sub>i</sub> - ∑x<sub>i</sub>y<sub>i</sub> ∑y<sub>i</sub>z<sub>i</sub>
+ * a     =  \ | ------------------------
+ *           \| ∑x<sub>i</sub>y<sub>i</sub> ∑x<sub>i</sub>z<sub>i</sub> - ∑x<sub>i</sub>x<sub>i</sub> ∑y<sub>i</sub>z<sub>i</sub>
+ *
+ *
+ *            |--------------------------
+ *         \  | ∑x<sub>i</sub>y<sub>i</sub> ∑x<sub>i</sub>z<sub>i</sub> - ∑x<sub>i</sub>x<sub>i</sub> ∑y<sub>i</sub>z<sub>i</sub>
+ * ω     =  \ | ------------------------
+ *           \| ∑x<sub>i</sub>x<sub>i</sub> ∑y<sub>i</sub>y<sub>i</sub> - ∑x<sub>i</sub>y<sub>i</sub> ∑x<sub>i</sub>y<sub>i</sub>
+ *
+ * </pre>
+ * </p>
+
+ * <p>Once we know ω, we can compute:
+ * <pre>
+ *    fc = ω f (t) cos (ω t) - f' (t) sin (ω t)
+ *    fs = ω f (t) sin (ω t) + f' (t) cos (ω t)
+ * </pre>
+ * </p>
+
+ * <p>It appears that <code>fc = a ω cos (φ)</code> and
+ * <code>fs = -a ω sin (φ)</code>, so we can use these
+ * expressions to compute φ. 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 ω. */
+    private double omega;
+
+    /** Guessed phase φ. */
+    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 ω 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 φ 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 ω.
+     * @return guessed pulsation ω
+     */
+    public double getGuessedPulsation() {
+        return omega;
+    }
+
+    /** Get the guessed phase φ.
+     * @return guessed phase φ
+     */
+    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 ω and
+ * the phase φ: <code>f (t) = a cos (ω t + φ)</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 ω and phase φ. */
+    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 ω (index 1) and phase φ (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 (ω t + φ)</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 ω. */
+    private final double omega;
+
+    /** Phase φ. */
+    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 ω.
+     * @return pulsation ω
+     */
+    public double getPulsation() {
+        return omega;
+    }
+
+    /** Get the phase φ.
+     * @return phase φ
+     */
+    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 β parameter
+ * in {@link NonLinearConjugateGradientOptimizer}.
+ * <p>
+ * The β parameter is used to compute the successive conjugate
+ * search directions. For non-linear conjugate gradients, there are
+ * two formulas to compute β:
+ * <ul>
+ *   <li>Fletcher-Reeves formula</li>
+ *   <li>Polak-Ribière formula</li>
+ * </ul>
+ * On the one hand, the Fletcher-Reeves formula is guaranteed to converge
+ * if the start point is close enough of the optimum whether the
+ * Polak-Ribière formula may not converge in rare cases. On the
+ * other hand, the Polak-Ribière formula is often faster when it
+ * does converge. Polak-Ribière is often used.
+ * <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è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érique matricielle
+ * appliquée à l'art de l'ingé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érique matricielle appliquée à
+     * l'art de l'ingé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è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 β 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> <= 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> <=
+ *       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 (=, <=, >=). */
+    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> <= 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> <= 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> <=
+     *       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> <=
+     *       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> <= 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> <=
+ *       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   <= phase 1 objective
+ *   0    1   -15   -10    0     0     0     0     0   <= phase 2 objective
+ *   0    0    1     0     0     1     0     0     2   <= constraint 1
+ *   0    0    0     1     0     0     1     0     3   <= constraint 2
+ *   0    0    1     1     0     0     0     1     4   <= constraint 3
+ * </pre>
+ * W: Phase 1 objective function</br>
+ * Z: Phase 2 objective function</br>
+ * x1 & x2: Decision variables</br>
+ * x-: Extra decision variable to allow for negative values</br>
+ * s1 & 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çois Panneton, Pierre L'Ecuyer and Makoto Matsumoto.
+
+ * <p>This generator is described in a paper by Franç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 = μ. */
+    public static final int UNIFORM_MODE = 2;
+
+    /** Exponential random deviates with mean = μ. */
+    public static final int EXPONENTIAL_MODE = 3;
+
+    /** Gaussian random deviates with mean = μ, std dev = σ. */
+    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çois Panneton, Pierre L'Ecuyer and Makoto Matsumoto.
+
+ * <p>This generator is described in a paper by Franç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çois Panneton, Pierre L'Ecuyer and Makoto Matsumoto.
+
+ * <p>This generator is described in a paper by Franç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çois Panneton, Pierre L'Ecuyer and Makoto Matsumoto.
+
+ * <p>This generator is described in a paper by Franç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çois Panneton, Pierre L'Ecuyer and Makoto Matsumoto.
+
+ * <p>This generator is described in a paper by Franç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çois Panneton, Pierre L'Ecuyer and Makoto Matsumoto.
+
+ * <p>This generator is described in a paper by Franç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çois Panneton, Pierre L'Ecuyer and Makoto Matsumoto.
+
+ * <p>This generator is described in a paper by Franç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ç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/√π <sub>0</sub>∫<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/√π <sub>x</sub>∫<sup>∞</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 Γ(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(Γ(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'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) = Σ[(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) = Σ[(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,·)</code> is the correlation coefficient and
+     * <code>s(·)</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 <=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 <=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 < p ≤ 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 <= 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>Σ (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>
+     *   Σ(weights[i]*(values[i] - weightedMean)<sup>2</sup>)/(Σ(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>
+     *   Σ(weights[i]*(values[i] - weightedMean)<sup>2</sup>)/(Σ(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>
+     *   Σ(weights[i]*(values[i] - mean)<sup>2</sup>)/(Σ(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>
+     *   Σ(weights[i]*(values[i] - mean)<sup>2</sup>)/(Σ(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 < values.length; i++) {<br/>     <span
+            style="font-weight: bold;">stat.increment(values[i]);</span><br/>    
+            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 = ∏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 = ∏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 = Σ(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 = Σ(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>
+     *   <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>
+     *    <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>
+     *    <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>
+     *   <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>
+     *   <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>
+     *    <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>
+     *    <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>
+     *   <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>
+     * ∑[(K * observed1[i] - observed2[i]/K)<sup>2</sup> / (observed1[i] + observed2[i])]
+     * </code> where
+     * <br/><code>K = &sqrt;[&sum(observed2 / ∑(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 · 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>] +
+     *                        ∑<sub>k=1</sub><sup>N-1</sup> f<sub>k</sub> cos(π 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>] +
+     *                        ∑<sub>k=1</sub><sup>N-1</sup> f<sub>k</sub> cos(π 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> = √(1/2N) [f<sub>0</sub> + (-1)<sup>n</sup> f<sub>N</sub>] +
+     *                        √(2/N) ∑<sub>k=1</sub><sup>N-1</sup> f<sub>k</sub> cos(π 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> = √(1/2N) [f<sub>0</sub> + (-1)<sup>n</sup> f<sub>N</sub>] +
+     *                        √(2/N) ∑<sub>k=1</sub><sup>N-1</sup> f<sub>k</sub> cos(π 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) ∑<sub>n=1</sub><sup>N-1</sup> F<sub>n</sub> cos(π 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) ∑<sub>n=1</sub><sup>N-1</sup> F<sub>n</sub> cos(π 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> = √(1/2N) [F<sub>0</sub> + (-1)<sup>k</sup> F<sub>N</sub>] +
+     *                        √(2/N) ∑<sub>n=1</sub><sup>N-1</sup> F<sub>n</sub> cos(π 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> = √(1/2N) [F<sub>0</sub> + (-1)<sup>k</sup> F<sub>N</sub>] +
+     *                        √(2/N) ∑<sub>n=1</sub><sup>N-1</sup> F<sub>n</sub> cos(π 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×log(N)) complexity with
+     * N=n<sub>1</sub>×n<sub>2</sub>×n<sub>3</sub>×...×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 π 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> = ∑<sub>k=0</sub><sup>N-1</sup> f<sub>k</sub> sin(π 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> = ∑<sub>k=0</sub><sup>N-1</sup> f<sub>k</sub> sin(π 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> = √(2/N) ∑<sub>k=0</sub><sup>N-1</sup> f<sub>k</sub> sin(π 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> = √(2/N) ∑<sub>k=0</sub><sup>N-1</sup> f<sub>k</sub> sin(π 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) ∑<sub>n=0</sub><sup>N-1</sup> F<sub>n</sub> sin(π 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) ∑<sub>n=0</sub><sup>N-1</sup> F<sub>n</sub> sin(π 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> = √(2/N) ∑<sub>n=0</sub><sup>N-1</sup> F<sub>n</sub> sin(π 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> = √(2/N) ∑<sub>n=0</sub><sup>N-1</sup> F<sub>n</sub> sin(π 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 × 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 × 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> +<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> +<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 π.
+     * @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>< 0 if !{@link #equals(double, double, double) equals(x, y, eps)} && x < y</li>
+     *       <li>> 0 if !{@link #equals(double, double, double) equals(x, y, eps)} && 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 × 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π:<br/>
+     *       <code>a = MathUtils.normalizeAngle(a, FastMath.PI);</code></li>
+     *   <li>normalize an angle between -π and +π<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 π 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π interval for the result
+     * @return a-2kπ with integer k and center-π <= a-2kπ <= center+π
+     * @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>∞</sub> (max of abs) distance between two points.
+     *
+     * @param p1 the first point
+     * @param p2 the second point
+     * @return the L<sub>∞</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>∞</sub> (max of abs) distance between two points.
+     *
+     * @param p1 the first point
+     * @param p2 the second point
+     * @return the L<sub>∞</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>
diff --git a/src/main/resources/META-INF/localization/LocalizedFormats_fr.properties b/src/main/resources/META-INF/localization/LocalizedFormats_fr.properties
new file mode 100644
index 0000000..daac310
--- /dev/null
+++ b/src/main/resources/META-INF/localization/LocalizedFormats_fr.properties
@@ -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.
+
+ARGUMENT_OUTSIDE_DOMAIN = argument {0} hors domaine [{1} ; {2}]
+ARRAY_SIZES_SHOULD_HAVE_DIFFERENCE_1 = les tableaux devraient avoir une diff\u00e9rence de taille de 1 ({0} != {1} + 1)
+ARRAY_SUMS_TO_ZERO = somme du tableau nulle
+ASSYMETRIC_EIGEN_NOT_SUPPORTED = la d\u00e9composition en valeurs/vecteurs propres de matrices 
+AT_LEAST_ONE_COLUMN = une matrice doit comporter au moins une colonne
+AT_LEAST_ONE_ROW = une matrice doit comporter au moins une ligne
+BANDWIDTH_OUT_OF_INTERVAL = la bande passante devrait \u00eatre dans l''intervalle [0,1], elle est de {0}
+BINOMIAL_INVALID_PARAMETERS_ORDER = n doit \u00eatre sup\u00e9rieur ou \u00e9gal \u00e0 k pour le coefficient du bin\u00f4me (n,k), or n = {0}, k = {1}
+BINOMIAL_NEGATIVE_PARAMETER = n doit \u00eatre positif pour le coefficient du bin\u00f4me (n,k), or n = {0}
+CANNOT_CLEAR_STATISTIC_CONSTRUCTED_FROM_EXTERNAL_MOMENTS = les statistiques bas\u00e9es sur des moments externes ne peuvent pas \u00eatre remises \u00e0 z\u00e9ro
+CANNOT_COMPUTE_0TH_ROOT_OF_UNITY = impossible de calculer la racine z\u00e9roi\u00e8me de l''unit\u00e9, 
+CANNOT_COMPUTE_BETA_DENSITY_AT_0_FOR_SOME_ALPHA = impossible de calculer la densit\u00e9 beta en 0 lorsque alpha = {0,number}
+CANNOT_COMPUTE_BETA_DENSITY_AT_1_FOR_SOME_BETA = impossible de calculer la densit\u00e9 beta en 1 lorsque beta = %.3g
+CANNOT_COMPUTE_NTH_ROOT_FOR_NEGATIVE_N = impossible de calculer la racine ni\u00e8me pour n n\u00e9gatif ou nul : {0}
+CANNOT_CONVERT_OBJECT_TO_FRACTION = impossible de convertir l''objet sous forme d''un nombre rationnel : {0}
+CANNOT_DISCARD_NEGATIVE_NUMBER_OF_ELEMENTS = impossible d''enlever un nombre d''\u00e9l\u00e9ments{0} n\u00e9gatif
+CANNOT_FORMAT_INSTANCE_AS_3D_VECTOR = impossible de formater une instance de {0} comme un vecteur de dimension 3
+CANNOT_FORMAT_INSTANCE_AS_COMPLEX = impossible de formater une instance de {0} comme un nombre complexe
+CANNOT_FORMAT_INSTANCE_AS_REAL_VECTOR = impossible de formater une instance de {0} comme un vecteur r\u00e9el
+CANNOT_FORMAT_OBJECT_TO_FRACTION = impossible de formater l''objet sous forme d''un nombre rationnel
+CANNOT_INCREMENT_STATISTIC_CONSTRUCTED_FROM_EXTERNAL_MOMENTS = les statistiques bas\u00e9es sur des moments externes ne peuvent pas \u00eatre incr\u00e9ment\u00e9es
+CANNOT_NORMALIZE_A_ZERO_NORM_VECTOR = impossible de normer un vecteur de norme nulle
+CANNOT_RETRIEVE_AT_NEGATIVE_INDEX = impossible d''extraire un \u00e9l\u00e9ment \u00e0 un index n\u00e9gatif ({0})
+CANNOT_SET_AT_NEGATIVE_INDEX = impossible de mettre un \u00e9l\u00e9ment \u00e0 un index n\u00e9gatif ({0})
+CANNOT_SUBSTITUTE_ELEMENT_FROM_EMPTY_ARRAY = impossible de substituer un \u00e9l\u00e9ment dans un tableau vide
+CANNOT_TRANSFORM_TO_DOUBLE = Exception de conversion dans une transformation : {0}
+CARDAN_ANGLES_SINGULARITY = singularit\u00e9 d''angles de Cardan
+CLASS_DOESNT_IMPLEMENT_COMPARABLE = la classe ({0}) n''implante pas l''interface Comparable
+CLOSEST_ORTHOGONAL_MATRIX_HAS_NEGATIVE_DETERMINANT = la matrice orthogonale la plus proche a un d\u00e9terminant n\u00e9gatif {0}
+COLUMN_INDEX_OUT_OF_RANGE = l''index de colonne {0} est hors du domaine autoris\u00e9 [{1}, {2}]
+CONTINUED_FRACTION_INFINITY_DIVERGENCE = Divergence de fraction continue \u00e0 l''infini pour la valeur {0}
+CONTINUED_FRACTION_NAN_DIVERGENCE = Divergence de fraction continue \u00e0 NaN pour la valeur {0}
+CONTRACTION_CRITERIA_SMALLER_THAN_EXPANSION_FACTOR = crit\u00e8re de contraction ({0}) inf\u00e9rieur au facteur d''extension ({1}). Ceci induit une boucle infinie d''extensions/contractions car tout tableau de stockage fra\u00eechement \u00e9tendu respecte imm\u00e9diatement le crit\u00e8re de contraction.
+CONTRACTION_CRITERIA_SMALLER_THAN_ONE = crit\u00e8re de contraction inf\u00e9rieur \u00e0 un ({0}). Ceci induit une boucle infinie d''extensions/contractions car tout tableau de stockage de longueur \u00e9gale au nombre d''\u00e9l\u00e9ments respecte le crit\u00e8re de contraction.
+CONVERGENCE_FAILED = \u00c9chec de convergence
+CUMULATIVE_PROBABILITY_RETURNED_NAN = Fonction de probabilit\u00e9 cumulative retourn\u00e9 NaN \u00e0 l''argument de {0} p = {1}
+DIFFERENT_ROWS_LENGTHS = certaines lignes ont une longueur de {0} alors que d''autres ont une longueur de {1}
+DIGEST_NOT_INITIALIZED = mod\u00e8le empirique non initialis\u00e9
+DIMENSIONS_MISMATCH_2x2 = dimensions incoh\u00e9rentes : {0}x{1} \u00e0 la place de {2}x{3}
+DIMENSIONS_MISMATCH_SIMPLE = dimensions incoh\u00e9rentes {0} != {1}
+DISCRETE_CUMULATIVE_PROBABILITY_RETURNED_NAN = Discr\u00e8tes fonction de probabilit\u00e9 cumulative retourn\u00e9 NaN \u00e0 l''argument de {0}
+DISTRIBUTION_NOT_LOADED = aucune distribution n''a \u00e9t\u00e9 charg\u00e9e
+DUPLICATED_ABSCISSA = Abscisse {0} dupliqu\u00e9e aux indices {1} et {2}
+EMPTY_CLUSTER_IN_K_MEANS = groupe vide dans l''algorithme des k-moyennes
+EMPTY_POLYNOMIALS_COEFFICIENTS_ARRAY = tableau de coefficients polyn\u00f4miaux vide
+EMPTY_SELECTED_COLUMN_INDEX_ARRAY = tableau des indices de colonnes s\u00e9lectionn\u00e9es vide
+EMPTY_SELECTED_ROW_INDEX_ARRAY = tableau des indices de lignes s\u00e9lectionn\u00e9es vide
+EMPTY_STRING_FOR_IMAGINARY_CHARACTER = cha\u00eene vide pour le caract\u00e8 imaginaire
+ENDPOINTS_NOT_AN_INTERVAL = les bornes ne d\u00e9finissent pas un intervalle : [{0}, {1}]
+EQUAL_VERTICES_IN_SIMPLEX = sommets {0} et {1} \u00e9gaux dans la configuration du simplex
+EULER_ANGLES_SINGULARITY = singularit\u00e9 d''angles d''Euler
+EVALUATION_FAILED = erreur d''\u00e9valuation pour l''argument {0}
+EXPANSION_FACTOR_SMALLER_THAN_ONE = facteur d''extension inf\u00e9rieur \u00e0 un ({0})
+FACTORIAL_NEGATIVE_PARAMETER = n doit \u00eatre positif pour le calcul de n!, or n = {0}
+FAILED_BRACKETING = nombre d''it\u00e9rations = {0}, it\u00e9rations maximum = {1}, valeur initiale = {2}, borne inf\u00e9rieure = {3}, borne sup\u00e9rieure = {4}, valeur a finale = {5}, valeur b finale = {6}, f(a) = {7}, f(b) = {8}
+FAILED_FRACTION_CONVERSION = Impossible de convertir {0} en fraction apr\u00e8s {1} it\u00e9rations
+FIRST_COLUMNS_NOT_INITIALIZED_YET = les {0} premi\u00e8res colonnes ne sont pas encore initialis\u00e9es
+FIRST_ELEMENT_NOT_ZERO = le premier \u00e9l\u00e9ment n''est pas nul : {0}
+FIRST_ROWS_NOT_INITIALIZED_YET = les {0} premi\u00e8res lignes ne sont pas encore initialis\u00e9es
+FRACTION_CONVERSION_OVERFLOW = D\u00e9passement de capacit\u00e9 lors de la conversion de {0} en fraction ({1}/{2})
+FUNCTION_NOT_DIFFERENTIABLE = la fonction n''est pas diff\u00e9rentiable
+FUNCTION_NOT_POLYNOMIAL = la fonction n''est pas p\u00f4lynomiale
+GCD_OVERFLOW_32_BITS = d\u00e9passement de capacit\u00e9 : le PGCD de {0} et {1} vaut 2^31
+GCD_OVERFLOW_64_BITS = d\u00e9passement de capacit\u00e9 : le PGCD de {0} et {1} vaut 2^63
+HOLE_BETWEEN_MODELS_TIME_RANGES = trou de longueur {0} entre les domaines temporels des mod\u00e8les
+IDENTICAL_ABSCISSAS_DIVISION_BY_ZERO = les abscisses identiques x[{0}] == x[{1}] == {2} engendrent une division par z\u00e9ro
+INDEX_LARGER_THAN_MAX = l''index sp\u00e9cifi\u00e9 ({0}) d\u00e9passe l''index maximal courant ({1})
+INDEX_NOT_POSITIVE = l''indice ({0}) n''est pas positif
+INDEX_OUT_OF_RANGE = l''indice ({0}) est hors du domaine autoris\u00e9 [{1}, {2}]
+INFINITE_ARRAY_ELEMENT = le tableau contient l''\u00e9l\u00e9ment infini {0} \u00e0 l''index {1}
+INFINITE_VALUE_CONVERSION = les valeurs infinies ne peuvent \u00eatre converties
+INITIAL_CAPACITY_NOT_POSITIVE = la capacit\u00e9 initiale ({0}) n''est pas positive
+INITIAL_COLUMN_AFTER_FINAL_COLUMN = colonne initiale {0} apr\u00e8s la colonne finale {1}
+INITIAL_ROW_AFTER_FINAL_ROW = ligne initiale {0} apr\u00e8s la ligne finale {1}
+INPUT_DATA_FROM_UNSUPPORTED_DATASOURCE = les donn\u00e9es d''entr\u00e9e proviennent 
+INSTANCES_NOT_COMPARABLE_TO_EXISTING_VALUES = l''instance de la classe {0} n''est pas comparable aux valeurs existantes
+INSUFFICIENT_DATA_FOR_T_STATISTIC = deux valeurs ou plus sont n\u00e9cessaires pour la statistique t, il y en a {0}
+INSUFFICIENT_DIMENSION = dimension {0} insuffisante, elle devrait \u00eatre au moins {1}
+INSUFFICIENT_OBSERVED_POINTS_IN_SAMPLE = l''\u00e9chantillon ne contient que {0} points alors qu''au moins {1} sont n\u00e9cessaires
+INSUFFICIENT_ROWS_AND_COLUMNS = donn\u00e9es insuffisantes : seulement {0} lignes et {1} colonnes.
+INTEGRATION_METHOD_NEEDS_AT_LEAST_ONE_PREVIOUS_POINT = la m\u00e9thode {0} n\u00e9cessite au moins un point pr\u00e9c\u00e9dent
+INTERNAL_ERROR = erreur interne, veuillez signaler l''erreur \u00e0 {0}
+INVALID_BRACKETING_PARAMETERS = param\u00e8tres d''encadrement invalides : borne inf\u00e9rieure = {0}, valeur initiale = {1}, borne sup\u00e9rieure = {2}
+INVALID_INTERVAL_INITIAL_VALUE_PARAMETERS = param\u00e8tres de l''intervalle initial invalides : borne inf = {0}, valeur initiale = {1}, borne sup = {2}
+INVALID_ITERATIONS_LIMITS = limites d''it\u00e9rations invalides : min = {0}, max = {1}
+INVALID_MAX_ITERATIONS = valeur invalide pour le nombre maximal d''it\u00e9rations : {0}
+INVALID_REGRESSION_ARRAY= longueur du tableau de donn\u00e9es = {0} ne correspond pas au nombre d'observations = {1} et le nombre de variables explicatives = {2}
+INVALID_ROUNDING_METHOD = m\u00e9thode d''arondi {0} invalide, m\u00e9thodes valides : {1} ({2}), {3} ({4}), {5} ({6}), {7} ({8}), {9} ({10}), {11} ({12}), {13} ({14}), {15} ({16})
+ITERATOR_EXHAUSTED = it\u00e9ration achev\u00e9e
+LCM_OVERFLOW_32_BITS = d\u00e9passement de capacit\u00e9 : le MCM de {0} et {1} vaut 2^31
+LCM_OVERFLOW_64_BITS = d\u00e9passement de capacit\u00e9 : le MCM de {0} et {1} vaut 2^63
+LIST_OF_CHROMOSOMES_BIGGER_THAN_POPULATION_SIZE = la liste des chromosomes d\u00e9passe maxPopulationSize
+LOESS_EXPECTS_AT_LEAST_ONE_POINT = la r\u00e9gression Loess n\u00e9cessite au moins un point
+LOWER_BOUND_NOT_BELOW_UPPER_BOUND = la borne inf\u00e9rieure ({0}) doit \u00eatre strictement plus petite que la borne sup\u00e9rieure ({1})
+LOWER_ENDPOINT_ABOVE_UPPER_ENDPOINT = la borne inf\u00e9rieure ({0}) devrait \u00eatre inf\u00e9rieure 
+MAP_MODIFIED_WHILE_ITERATING = la table d''adressage a \u00e9t\u00e9 modifi\u00e9e pendant l''it\u00e9ration
+MAX_EVALUATIONS_EXCEEDED = nombre maximal d''\u00e9valuations ({0}) d\u00e9pass\u00e9
+MAX_ITERATIONS_EXCEEDED = nombre maximal d''it\u00e9rations ({0}) d\u00e9pass\u00e9
+MINIMAL_STEPSIZE_REACHED_DURING_INTEGRATION = pas minimal ({0,number,0.00E00}) atteint, l''int\u00e9gration n\u00e9cessite {1,number,0.00E00}
+MISMATCHED_LOESS_ABSCISSA_ORDINATE_ARRAYS = Loess a besoin de tableaux d'abscisses et d'oordonn\u00e9es de m\u00eame taille, mais il y a {0} points en abscisse et {1} en ordonn\u00e9e
+NAN_ELEMENT_AT_INDEX = l''\u00e9l\u00e9ment {0} est un NaN
+NAN_VALUE_CONVERSION = les valeurs NaN ne peuvent \u00eatre converties
+NEGATIVE_BRIGHTNESS_EXPONENT = l''exposant de brillance devrait \u00eatre positif ou null, or e = {0}
+NEGATIVE_COMPLEX_MODULE = module n\u00e9gatif ({0}) pour un nombre complexe
+NEGATIVE_ELEMENT_AT_2D_INDEX = l''\u00e9l\u00e9ment ({0}, {1}) est n\u00e9gatif : {2}
+NEGATIVE_ELEMENT_AT_INDEX = l''\u00e9l\u00e9ment {0} est n\u00e9gatif : {1}
+NEGATIVE_NUMBER_OF_SUCCESSES = le nombre de succ\u00e8s ne doit pas \u00eatre n\u00e9gatif ({0})
+NEGATIVE_NUMBER_OF_TRIALS = le nombre d''essais ne doit pas \u00eatre n\u00e9gatif ({0})
+NEGATIVE_ROBUSTNESS_ITERATIONS = le nombre d''it\u00e9rations robuste ne peut \u00eatre n\u00e9gatif, alors qu''il est de {0}
+START_POSITION = position de d\u00e9part
+NON_CONVERGENT_CONTINUED_FRACTION = \u00c9chec de convergence de fraction continue pour la valeur {0}
+NON_POSITIVE_MICROSPHERE_ELEMENTS = le nombre d''\u00e9l\u00e9ments de la microsph\u00e8re devrait \u00eatre positif, or n = {0}
+NON_POSITIVE_POLYNOMIAL_DEGREE = le polyn\u00f4me doit \u00eatre de degr\u00e9 positif : degr\u00e9 = {0}
+NON_REAL_FINITE_ABSCISSA = toutes les abscisses doivent \u00eatre des nombres r\u00e9els finis, mais l''abscisse {0} vaut {1}
+NON_REAL_FINITE_ORDINATE = toutes les ordonn\u00e9es doivent \u00eatre des nombres r\u00e9els finis, mais l''ordonn\u00e9e {0} vaut {1}
+NON_REAL_FINITE_WEIGHT = tous les poids doivent \u00eatre des nombres r\u00e9els finis, mais le poids {0} vaut {1}
+NON_SQUARE_MATRIX = une matrice {0}x{1} a \u00e9t\u00e9 fournie \u00e0 la place d''une matrice carr\u00e9e
+NORMALIZE_INFINITE = impossible de normaliser vers une valeur infinie
+NORMALIZE_NAN = impossible de normaliser vers NaN
+NOT_ADDITION_COMPATIBLE_MATRICES = les dimensions {0}x{1} et {2}x{3} sont incompatibles pour l''addition matricielle
+NOT_DECREASING_NUMBER_OF_POINTS = les points {0} et {1} ne sont pas d\u00e9croissants ({2} < {3})
+NOT_DECREASING_SEQUENCE = les points {3} et {2} ne sont pas d\u00e9croissants ({1} < {0})
+NOT_ENOUGH_DATA_FOR_NUMBER_OF_PREDICTORS = pas assez de donn\u00e9es ({0} lignes) pour {1} pr\u00e9dicteurs
+NOT_ENOUGH_POINTS_IN_SPLINE_PARTITION = une partiction spline n\u00e9cessite au moins {0} points, seuls {1} ont \u00e9t\u00e9 fournis
+NOT_INCREASING_NUMBER_OF_POINTS = les points {0} et {1} ne sont pas croissants ({2} > {3})
+NOT_INCREASING_SEQUENCE = les points {3} et {2} ne sont pas croissants ({1} > {0})
+NOT_MULTIPLICATION_COMPATIBLE_MATRICES = les dimensions {0}x{1} et {2}x{3} sont incompatibles pour la multiplication matricielle
+NOT_OVERRIDEN = m\u00e9thode n'est pas remplac\u00e9e
+NOT_POSITIVE_ALPHA = alpha doit \u00eatre positif ({0})
+NOT_POSITIVE_BETA = beta doit \u00eatre positif ({0})
+NOT_POSITIVE_COLUMNDIMENSION = nombre de colonnes invalide : {0} (doit \u00eatre positif)
+NOT_POSITIVE_DEFINITE_MATRIX = matrice non d\u00e9finie positive
+NOT_POSITIVE_DEGREES_OF_FREEDOM = les degr\u00e9s de libert\u00e9 doivent \u00eatre positifs ({0})
+NOT_POSITIVE_ELEMENT_AT_INDEX = l''\u00e9l\u00e9ment {0} n''est pas positif : {1}
+NOT_POSITIVE_EXPONENT = exposant {0} invalide (doit \u00eatre positif)
+NOT_POSITIVE_LENGTH = la longueur doit \u00eatre positive ({0})
+LENGTH = longueur ({0})
+NOT_POSITIVE_MEAN = la moyenne doit \u00eatre positive ({0})
+MEAN = moyenne ({0})
+NOT_POSITIVE_NUMBER_OF_SAMPLES = le nombre d''\u00e9chantillons n''est pas positif : {0}
+NUMBER_OF_SAMPLES = nombre d''\u00e9chantillons ({0})
+NOT_POSITIVE_PERMUTATION = la permutation k ({0}) doit \u00eatre positive
+PERMUTATION_SIZE = taille de la permutation
+NOT_POSITIVE_POISSON_MEAN = la moyenne de Poisson doit \u00eatre positive ({0})
+NOT_POSITIVE_POPULATION_SIZE = la taille de la population doit \u00eatre positive ({0})
+NOT_POSITIVE_ROW_DIMENSION = nombre de lignes invalide : {0} (doit \u00eatre positif)
+NOT_POSITIVE_SAMPLE_SIZE = la taille de l''\u00e9chantillon doit \u00eatre positive ({0})
+NOT_POSITIVE_SCALE = l''\u00e9chelle doit \u00eatre positive ({0})
+NOT_POSITIVE_SHAPE = le facteur de forme doit \u00eatre positif ({0})
+NOT_POSITIVE_STANDARD_DEVIATION = l''\u00e9cart type doit \u00eatre positif ({0})
+STANDARD_DEVIATION = \u00e9cart type
+NOT_POSITIVE_UPPER_BOUND = la borne sup\u00e9rieure doit \u00eatre positive ({0})
+NOT_POSITIVE_WINDOW_SIZE = la taille de la fen\u00eatre doit \u00eatre positive ({0})
+NOT_POWER_OF_TWO = {0} n''est pas une puissance de 2
+NOT_POWER_OF_TWO_CONSIDER_PADDING = {0} n''est pas une puissance de 2, ajoutez des \u00e9l\u00e9ments pour corriger
+NOT_POWER_OF_TWO_PLUS_ONE = {0} n''est pas une puissance de 2 plus un
+NOT_STRICTLY_DECREASING_NUMBER_OF_POINTS = les points {0} et {1} ne sont pas strictement d\u00e9croissants ({2} <= {3})
+NOT_STRICTLY_DECREASING_SEQUENCE = les points {3} et {2} ne sont pas strictement d\u00e9croissants ({1} <= {0})
+NOT_STRICTLY_INCREASING_KNOT_VALUES = les n\u0153uds d''interpolation doivent \u00eatre strictement croissants
+NOT_STRICTLY_INCREASING_NUMBER_OF_POINTS = les points {0} et {1} ne sont pas strictement croissants ({2} >= {3})
+NOT_STRICTLY_INCREASING_SEQUENCE = les points {3} et {2} ne sont pas strictement croissants ({1} >= {0})
+NOT_SUBTRACTION_COMPATIBLE_MATRICES = les dimensions {0}x{1} et {2}x{3} sont incompatibles pour la soustraction matricielle
+NOT_SYMMETRIC_MATRIX = matrice non symm\u00e9trique
+NO_BIN_SELECTED = aucun compartiment s\u00e9lectionn\u00e9
+NO_CONVERGENCE_WITH_ANY_START_POINT = aucun des {0} points de d\u00e9part n''aboutit \u00e0 une convergence
+NO_DATA = aucune donn\u00e9e
+NO_DEGREES_OF_FREEDOM = aucun degr\u00e9 de libert\u00e9 ({0} mesures, {1} param\u00e8tres)
+NO_DENSITY_FOR_THIS_DISTRIBUTION = La fonction de densit\u00e9 pour cette distribution n''a pas \u00e9t\u00e9 mis en \u0153uvre
+NO_FEASIBLE_SOLUTION = aucune solution r\u00e9alisable
+NO_OPTIMUM_COMPUTED_YET = aucun optimum n''a encore \u00e9t\u00e9 calcul\u00e9
+NO_RESULT_AVAILABLE = aucun r\u00e9sultat n''est disponible
+NO_SUCH_MATRIX_ENTRY = pas d''\u00e9l\u00e9ment ({0}, {1}) dans une matrice {2}x{3}
+NULL_NOT_ALLOWED = "null" n''est pas permis
+COVARIANCE_MATRIX = matrice de covariance
+DENOMINATOR = d\u00e9nominateur
+DENOMINATOR_FORMAT = format du d\u00e9nominateur
+FRACTION = fraction
+FUNCTION = fonction
+IMAGINARY_FORMAT = format de la partie imaginaire
+INPUT_ARRAY = tableau d''entr\u00e9e
+NUMERATOR = num\u00e9rateur
+NUMERATOR_FORMAT = format du num\u00e9rateur
+OBJECT_TRANSFORMATION = exception de conversion dans une transformation
+REAL_FORMAT = format de la partie r\u00e9elle
+WHOLE_FORMAT = format complet
+NUMBER_TOO_LARGE = {0} est plus grand que le maximum ({1})
+NUMBER_TOO_SMALL = {0} est plus petit que le minimum ({1})
+NUMBER_TOO_LARGE_BOUND_EXCLUDED = {0} n''est pas strictement plus grand que le maximum ({1})
+NUMBER_TOO_SMALL_BOUND_EXCLUDED = {0} n''est pas strictement plus petit que le minimum ({1})
+NUMBER_OF_SUCCESS_LARGER_THAN_POPULATION_SIZE = le nombre de succ\u00e8s ({0}) doit \u00eatre inf\u00e9rieur ou \u00e9gal \u00e0 la taille de la population ({1}) 
+NUMERATOR_OVERFLOW_AFTER_MULTIPLY = d\u00e9passement de capacit\u00e9 pour le num\u00e9rateur apr\u00e8s multiplication : {0}
+N_POINTS_GAUSS_LEGENDRE_INTEGRATOR_NOT_SUPPORTED = l''int\u00e9grateur de Legendre-Gauss en {0} points n''est pas disponible, le nombre de points doit \u00eatre entre {1} et {2}
+OBSERVED_COUNTS_ALL_ZERO = aucune occurrence dans le tableau des observations {0}
+OBSERVED_COUNTS_BOTTH_ZERO_FOR_ENTRY = les occurrences observ\u00e9es sont toutes deux nulles pour l''entr\u00e9e {0}
+OUT_OF_BOUNDS_QUANTILE_VALUE = valeur de quantile {0} hors bornes, doit \u00eatre dans l''intervalle ]0, 100]
+OUT_OF_BOUND_SIGNIFICANCE_LEVEL = niveau de signification {0} hors domaine, doit \u00eatre entre {1} et {2}
+OUT_OF_ORDER_ABSCISSA_ARRAY = les abscisses doivent \u00eatre en ordre strictement croissant, mais l''\u00e9l\u00e9ment {0} vaut {1} alors que l''\u00e9l\u00e9ment {2} vaut {3}
+OUT_OF_RANGE_ROOT_OF_UNITY_INDEX = l''indice de racine de l''unit\u00e9 {0} est hors du domaine autoris\u00e9 [{1};{2}]
+OUT_OF_RANGE_SIMPLE = {0} hors du domaine [{1}, {2}]
+OVERFLOW_IN_FRACTION = d\u00e9passement de capacit\u00e9 pour la fraction {0}/{1}, son signe ne peut \u00eatre chang\u00e9
+OVERFLOW_IN_ADDITION = d\u00e9passement de capacit\u00e9 pour l''addition : {0} + {1}
+OVERFLOW_IN_SUBTRACTION = d\u00e9passement de capacit\u00e9 pour la soustraction : {0} - {1}
+PERCENTILE_IMPLEMENTATION_CANNOT_ACCESS_METHOD = acc\u00e8s impossible \u00e0 la m\u00e9thode {0}
+PERCENTILE_IMPLEMENTATION_UNSUPPORTED_METHOD = l''implantation de pourcentage {0} ne dispose pas de la m\u00e9thode {1}
+PERMUTATION_EXCEEDS_N = la taille de la permutation ({0}) d\u00e9passe le domaine de la permutation ({1})
+POLYNOMIAL_INTERPOLANTS_MISMATCH_SEGMENTS = le nombre d''interpolants polyn\u00f4miaux doit correspondre au nombre de segments ({0} != {1} - 1)
+POPULATION_LIMIT_NOT_POSITIVE = la limite de population doit \u00eatre positive
+POSITION_SIZE_MISMATCH_INPUT_ARRAY = la position {0} et la taille {1} sont incompatibles avec la taille du tableau d''entr\u00e9e {2}
+POWER_NEGATIVE_PARAMETERS = impossible d''\u00e9lever une valeur enti\u00e8re 
+PROPAGATION_DIRECTION_MISMATCH = directions de propagation incoh\u00e9rentes
+RANDOMKEY_MUTATION_WRONG_CLASS = RandomKeyMutation ne fonctionne qu''avec la classe RandomKeys, pas avec {0}
+ROOTS_OF_UNITY_NOT_COMPUTED_YET = les racines de l''unit\u00e9 n''ont pas encore \u00e9t\u00e9 calcul\u00e9es
+ROTATION_MATRIX_DIMENSIONS = une matrice {0}x{1} ne peut pas \u00eatre une matrice de rotation
+ROW_INDEX_OUT_OF_RANGE = l''index de ligne {0} est hors du domaine autoris\u00e9 [{1}, {2}]
+SAME_SIGN_AT_ENDPOINTS = les valeurs aux bornes de la fonction devraient avoir des signes difff\u00e9rents ; bornes : [{0}, {1}], valeurs : [{2}, {3}]
+SAMPLE_SIZE_EXCEEDS_COLLECTION_SIZE = la taille de l''\u00e9chantillon ({0}) d\u00e9passe la taille de la collection ({1})
+SAMPLE_SIZE_LARGER_THAN_POPULATION_SIZE = la taille de l''\u00e9chantillon doit \u00eatre inf\u00e9rieure 
+SIMPLEX_NEED_ONE_POINT = le simplex doit contenir au moins un point
+SIMPLE_MESSAGE = {0}
+SINGULAR_MATRIX = matrice singuli\u00e8re
+SUBARRAY_ENDS_AFTER_ARRAY_END = le sous-tableau se termine apr\u00e8s la fin du tableau
+TOO_LARGE_CUTOFF_SINGULAR_VALUE = la valeur singuli\u00e8re de coupure vaut {0}, elle ne devrait pas d\u00e9passer {1}
+TOO_MANY_ELEMENTS_TO_DISCARD_FROM_ARRAY = impossible d''enlever {0} \u00e9l\u00e9ments d''un tableau en contenant {1}
+TOO_SMALL_BANDWIDTH = la largeur de bande doit \u00eatre assez grande pour supporter au moins 2 points. Il y a {0} donn\u00e9es et la largeur de bande doit \u00eatre au moins de {1}, or elle est seulement de {2}
+TOO_SMALL_COST_RELATIVE_TOLERANCE = trop petite tol\u00e9rance relative sur le co\u00fbt ({0}), aucune r\u00e9duction de la somme des carr\u00e9s n''est possible
+TOO_SMALL_INTEGRATION_INTERVAL = intervalle d''int\u00e9gration trop petit : {0}
+TOO_SMALL_ORTHOGONALITY_TOLERANCE = trop petite tol\u00e9rance sur l''orthogonalit\u00e9 ({0}), la solution est orthogonale \u00e0 la jacobienne
+TOO_SMALL_PARAMETERS_RELATIVE_TOLERANCE = trop petite tol\u00e9rance relative sur les param\u00e8tres ({0}), aucune am\u00e9lioration de la solution approximative n''est possible
+TWO_OR_MORE_CATEGORIES_REQUIRED = deux cat\u00e9gories ou plus sont n\u00e9cessaires, il y en a {0}
+TWO_OR_MORE_VALUES_IN_CATEGORY_REQUIRED = deux valeurs ou plus sont n\u00e9cessaires pour chaque cat\u00e9gorie, une cat\u00e9gorie en a {0}
+UNABLE_TO_BRACKET_OPTIMUM_IN_LINE_SEARCH = impossible d''encadrer l''optimum lors de la recherche lin\u00e9aire
+UNABLE_TO_COMPUTE_COVARIANCE_SINGULAR_PROBLEM = impossible de calculer les covariances : probl\u00e8me singulier
+UNABLE_TO_FIRST_GUESS_HARMONIC_COEFFICIENTS = impossible de faire une premi\u00e8re estimation des coefficients harmoniques
+UNABLE_TO_ORTHOGONOLIZE_MATRIX = impossible de rendre la matrice orthogonale en {0} it\u00e9rations
+UNABLE_TO_PERFORM_QR_DECOMPOSITION_ON_JACOBIAN = impossible de calculer la factorisation Q.R de la matrice jacobienne {0}x{1}
+UNABLE_TO_SOLVE_SINGULAR_PROBLEM = r\u00e9solution impossible : probl\u00e8me singulier
+UNBOUNDED_SOLUTION = solution non born\u00e9e
+UNKNOWN_MODE = mode {0} inconnu, modes connus : {1} ({2}), {3} ({4}), {5} ({6}), {7} ({8}), {9} ({10}) et {11} ({12})
+UNPARSEABLE_3D_VECTOR = vecteur 3D non analysable : "{0}"
+UNPARSEABLE_COMPLEX_NUMBER = nombre complexe non analysable : "{0}"
+UNPARSEABLE_FRACTION_NUMBER = nombre fractionnaire non analysable : "{0}"
+UNPARSEABLE_REAL_VECTOR = vecteur r\u00e9el non analysable : "{0}"
+UNSUPPORTED_EXPANSION_MODE = mode d''extension {0} non support\u00e9, les modes support\u00e9s sont {1} ({2}) et {3} ({4})
+UNSUPPORTED_OPERATION = op\u00e9ration non disponible
+URL_CONTAINS_NO_DATA = l''adresse {0} ne contient aucune donn\u00e9e
+VALUES_ADDED_BEFORE_CONFIGURING_STATISTIC = {0} valeurs ont \u00e9t\u00e9 ajout\u00e9es 
+VECTOR_LENGTH_MISMATCH = taille de vecteur invalide : {0} au lieu de {1} attendue
+VECTOR_MUST_HAVE_AT_LEAST_ONE_ELEMENT = un vecteur doit comporter au moins un \u00e9l\u00e9ment
+WEIGHT_AT_LEAST_ONE_NON_ZERO = le tableau des poids doit contenir au moins une valeur non nulle
+WRONG_BLOCK_LENGTH = forme de tableau erron\u00e9e (bloc de longueur {0} au lieu des {1} attendus
+WRONG_NUMBER_OF_POINTS = {0} sont n\u00e9cessaires, seuls {1} ont \u00e9t\u00e9 fournis
+NUMBER_OF_POINTS = nombre de points ({0})
+ZERO_DENOMINATOR = le d\u00e9nominateur doit \u00eatre diff\u00e9rent de 0
+ZERO_DENOMINATOR_IN_FRACTION = d\u00e9nominateur null dans le nombre rationnel {0}/{1}
+ZERO_FRACTION_TO_DIVIDE_BY = division par un nombre rationnel nul : {0}/{1}
+ZERO_NORM = norme nulle
+ZERO_NORM_FOR_ROTATION_AXIS = norme nulle pour un axe de rotation
+ZERO_NORM_FOR_ROTATION_DEFINING_VECTOR = norme nulle pour un axe de d\u00e9finition de rotation
+ZERO_NOT_ALLOWED = la valeur z\u00e9ro n''est pas autoris\u00e9e ici
diff --git a/src/main/resources/templates/math-release-notes.vm b/src/main/resources/templates/math-release-notes.vm
new file mode 100644
index 0000000..5fc35bf
--- /dev/null
+++ b/src/main/resources/templates/math-release-notes.vm
@@ -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.
+                        
+
+              Apache ${project.name} ${version} RELEASE NOTES
+
+## Hack to improve layout: replace all pairs of spaces with a single new-line
+$release.description.replaceAll("  ", "
+")
+
+#if ($release.getActions().size() == 0)
+No changes defined in this version.
+#else
+Changes in this version include:
+
+#if ($release.getActions('add').size() !=0)
+New features:
+#foreach($actionItem in $release.getActions('add'))
+#set($action=$actionItem.getAction())
+#if ($actionItem.getIssue())
+#set($issue=$actionItem.getIssue())
+#else
+#set($issue="")
+#end
+#if ($actionItem.getDueTo())
+#set($dueto=$actionItem.getDueTo())
+#else
+#set($dueto="")
+#end
+o#if($!issue != "") $issue: #end ${action} #if($!dueto != "")Thanks to $dueto. #end
+
+#set($issue="")
+#set($dueto="")
+#end 
+#end
+
+#if ($release.getActions('fix').size() !=0)
+Fixed Bugs:
+#foreach($actionItem in $release.getActions('fix'))
+#set($action=$actionItem.getAction())
+#if ($actionItem.getIssue())
+#set($issue=$actionItem.getIssue())
+#else
+#set($issue="")
+#end
+#if ($actionItem.getDueTo())
+#set($dueto=$actionItem.getDueTo())
+#else
+#set($dueto="")
+#end
+o#if($!issue != "") $issue: #end ${action} #if($!dueto != "")Thanks to $dueto. #end
+
+#set($issue="")
+#set($dueto="")
+#end
+#end
+
+#if ($release.getActions('update').size() !=0)
+Changes:
+#foreach($actionItem in $release.getActions('update'))
+#set($action=$actionItem.getAction())
+#if ($actionItem.getIssue())
+#set($issue=$actionItem.getIssue())
+#else
+#set($issue="")
+#end
+#if ($actionItem.getDueTo())
+#set($dueto=$actionItem.getDueTo())
+#else
+#set($dueto="")
+#end
+o#if($!issue != "") $issue: #end ${action} #if($!dueto != "")Thanks to $dueto. #end
+
+#set($issue="")
+#set($dueto="")
+#end
+#end
+
+#if ($release.getActions('remove').size() !=0)
+Removed:
+#foreach($actionItem in $release.getActions('remove'))
+#set($action=$actionItem.getAction())
+#if ($actionItem.getIssue())
+#set($issue=$actionItem.getIssue())
+#else
+#set($issue="")
+#end
+#if ($actionItem.getDueTo())
+#set($dueto=$actionItem.getDueTo())
+#else
+#set($dueto="")
+#end
+o#if($!issue != "") $issue. #end ${action} #if($!dueto != "")Thanks to $dueto. #end
+
+#set($issue="")
+#set($dueto="")
+#end
+#end
+## End of main loop
+#end
+ 
+For complete information on ${project.name}, including instructions on how to submit bug reports,
+patches, or suggestions for improvement, see the Apache ${project.name} website:
+
+${project.url}
+
+
diff --git a/src/site/resources/images/math.gif b/src/site/resources/images/math.gif
new file mode 100644
index 0000000..766ddd2
Binary files /dev/null and b/src/site/resources/images/math.gif differ
diff --git a/src/site/resources/style/project.css b/src/site/resources/style/project.css
new file mode 100644
index 0000000..c1d541c
--- /dev/null
+++ b/src/site/resources/style/project.css
@@ -0,0 +1 @@
+ at import url("http://commons.apache.org/style/commons-maven.css");
diff --git a/src/site/resources/userguide/TrajectoryDeterminationProblem.java b/src/site/resources/userguide/TrajectoryDeterminationProblem.java
new file mode 100644
index 0000000..96e585c
--- /dev/null
+++ b/src/site/resources/userguide/TrajectoryDeterminationProblem.java
@@ -0,0 +1,187 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.apache.commons.math.optimization.general.EstimationException;
+import org.apache.commons.math.optimization.general.EstimatedParameter;
+import org.apache.commons.math.optimization.general.EstimationProblem;
+import org.apache.commons.math.optimization.general.LevenbergMarquardtEstimator;
+import org.apache.commons.math.optimization.general.SimpleEstimationProblem;
+import org.apache.commons.math.optimization.general.WeightedMeasurement;
+
+public class TrajectoryDeterminationProblem extends SimpleEstimationProblem {
+
+    public static void main(String[] args) {
+        try {
+            TrajectoryDeterminationProblem problem =
+              new TrajectoryDeterminationProblem(0.0, 100.0, 800.0, 1.0, 0.0);
+
+            double[][] distances = {
+                    {   0.0, 806.5849 }, {  20.0, 796.8148 }, {  40.0, 791.0833 }, {  60.0, 789.6712 },
+                    {  80.0, 793.1334 }, { 100.0, 797.7248 }, { 120.0, 803.2785 }, { 140.0, 813.4939 },
+                    { 160.0, 826.9295 }, { 180.0, 844.0640 }, { 200.0, 863.3829 }, { 220.0, 883.3143 },
+                    { 240.0, 908.6867 }, { 260.0, 934.8561 }, { 280.0, 964.0730 }, { 300.0, 992.1033 },
+                    { 320.0, 1023.998 }, { 340.0, 1057.439 }, { 360.0, 1091.912 }, { 380.0, 1125.968 },
+                    { 400.0, 1162.789 }, { 420.0, 1201.517 }, { 440.0, 1239.176 }, { 460.0, 1279.347 } };
+            for (int i = 0; i < distances.length; ++i) {
+                problem.addDistanceMeasurement(1.0,  distances[i][0], distances[i][1]);
+            };
+
+            double[][] angles = {
+                    { 10.0, 1.415423 }, { 30.0, 1.352643 }, { 50.0, 1.289290 }, { 70.0, 1.225249 },
+                    { 90.0, 1.161203 }, {110.0, 1.098538 }, {130.0, 1.036263 }, {150.0, 0.976052 },
+                    {170.0, 0.917921 }, {190.0, 0.861830 }, {210.0, 0.808237 }, {230.0, 0.757043 },
+                    {250.0, 0.708650 }, {270.0, 0.662949 }, {290.0, 0.619903 }, {310.0, 0.579160 },
+                    {330.0, 0.541033 }, {350.0, 0.505590 }, {370.0, 0.471746 }, {390.0, 0.440155 },
+                    {410.0, 0.410522 }, {430.0, 0.382701 }, {450.0, 0.356957 }, {470.0, 0.332400 } };
+            for (int i = 0; i < angles.length; ++i) {
+                problem.addAngularMeasurement(3.0e7, angles[i][0], angles[i][1]);
+            };
+
+            LevenbergMarquardtEstimator estimator = new LevenbergMarquardtEstimator();
+            estimator.estimate(problem);
+            System.out.println("initial position: " + problem.getX0() + " " + problem.getY0());
+            System.out.println("velocity: " + problem.getVx0() + " " + problem.getVy0());
+
+        } catch (EstimationException ee) {
+          System.err.println(ee.getMessage());
+        }
+    }
+
+    public TrajectoryDeterminationProblem(double t0,
+                                          double  x0Guess, double  y0Guess,
+                                          double vx0Guess, double vy0Guess) {
+        this.t0 = t0;
+         x0 = new EstimatedParameter( "x0",  x0Guess);
+         y0 = new EstimatedParameter( "y0",  y0Guess);
+        vx0 = new EstimatedParameter("vx0", vx0Guess);
+        vy0 = new EstimatedParameter("vy0", vy0Guess);
+
+        // inform the base class about the parameters
+        addParameter(x0);
+        addParameter(y0);
+        addParameter(vx0);
+        addParameter(vy0);
+
+    }
+
+    public double getX0() {
+        return x0.getEstimate();
+    }
+
+    public double getY0() {
+        return y0.getEstimate();
+    }
+
+    public double getVx0() {
+        return vx0.getEstimate();
+    }
+
+    public double getVy0() {
+        return vy0.getEstimate();
+    }
+
+    public void addAngularMeasurement(double wi, double ti, double ai) {
+        // let the base class handle the measurement
+        addMeasurement(new AngularMeasurement(wi, ti, ai));
+    }
+
+    public void addDistanceMeasurement(double wi, double ti, double di) {
+        // let the base class handle the measurement
+        addMeasurement(new DistanceMeasurement(wi, ti, di));
+    }
+
+    public double x(double t) {
+        return x0.getEstimate() + (t - t0) * vx0.getEstimate();
+    }
+
+    public double y(double t) {
+        return y0.getEstimate() + (t - t0) * vy0.getEstimate();
+    }
+
+    private class AngularMeasurement extends WeightedMeasurement {
+
+        public AngularMeasurement(double weight, double t, double angle) {
+            super(weight, angle);
+            this.t = t;
+        }
+
+        public double getTheoreticalValue() {
+            return Math.atan2(y(t), x(t));
+        }
+
+        public double getPartial(EstimatedParameter parameter) {
+            double xt = x(t);
+            double yt = y(t);
+            double r  = Math.sqrt(xt * xt + yt * yt);
+            double u  = yt / (r + xt);
+            double c  = 2 * u / (1 + u * u);
+            if (parameter == x0) {
+                return -c;
+            } else if (parameter == vx0) {
+                return -c * t;
+            } else if (parameter == y0) {
+                return c * xt / yt;
+            } else {
+                return c * t * xt / yt;
+            }
+        }
+
+        private final double t;
+        private static final long serialVersionUID = -5990040582592763282L;
+
+    }
+
+    private class DistanceMeasurement extends WeightedMeasurement {
+
+        public DistanceMeasurement(double weight, double t, double angle) {
+            super(weight, angle);
+            this.t = t;
+        }
+
+        public double getTheoreticalValue() {
+            double xt = x(t);
+            double yt = y(t);
+            return Math.sqrt(xt * xt + yt * yt);
+        }
+
+        public double getPartial(EstimatedParameter parameter) {
+            double xt = x(t);
+            double yt = y(t);
+            double r  = Math.sqrt(xt * xt + yt * yt);
+            if (parameter == x0) {
+                return xt / r;
+            } else if (parameter == vx0) {
+                return xt * t / r;
+            } else if (parameter == y0) {
+                return yt / r;
+            } else {
+                return yt * t / r;
+            }
+        }
+
+        private final double t;
+        private static final long serialVersionUID = 3257286197740459503L;
+
+    }
+
+    private double t0;
+    private EstimatedParameter x0;
+    private EstimatedParameter y0;
+    private EstimatedParameter vx0;
+    private EstimatedParameter vy0;
+
+}
diff --git a/src/site/resources/userguide/estimation-class-diagram.png b/src/site/resources/userguide/estimation-class-diagram.png
new file mode 100644
index 0000000..fe9b678
Binary files /dev/null and b/src/site/resources/userguide/estimation-class-diagram.png differ
diff --git a/src/site/resources/userguide/estimation-sequence-diagram.png b/src/site/resources/userguide/estimation-sequence-diagram.png
new file mode 100644
index 0000000..32b5868
Binary files /dev/null and b/src/site/resources/userguide/estimation-sequence-diagram.png differ
diff --git a/src/site/site.xml b/src/site/site.xml
new file mode 100644
index 0000000..1b96201
--- /dev/null
+++ b/src/site/site.xml
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements.  See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<project name="Math">
+  <bannerRight>
+    <name>Commons Math</name>
+    <src>/images/math.gif</src>
+    <href>/index.html</href>
+  </bannerRight>
+
+  <body>
+    <menu name="Math">
+      <item name="Overview" href="/index.html"/>
+      <item name="Proposal" href="/proposal.html"/>
+      <item name="Developers Guide" href="/developers.html"/>
+      <item name="Javadoc (3.0-SNAPSHOT)" href="apidocs/index.html"/>
+      <item name="Javadoc (2.2 release)"
+         href="http://commons.apache.org/math/api-2.2/index.html"/>
+      <item name="Javadoc (2.1 release)"
+         href="http://commons.apache.org/math/api-2.1/index.html"/>
+      <item name="Javadoc (2.0 release)"
+         href="http://commons.apache.org/math/api-2.0/index.html"/>
+      <item name="Javadoc (1.2 release)"   
+         href="http://commons.apache.org/math/api-1.2/index.html"/>
+      <item name="Javadoc (1.1 release)"   
+         href="http://commons.apache.org/math/api-1.1/index.html"/>
+      <item name="Javadoc (1.0 release)"
+         href="http://commons.apache.org/math/api-1.0/index.html"/>
+      <item name="Issue Tracking" href="/issue-tracking.html"/>
+      <item name="Source Repository (current)" 
+         href="http://svn.apache.org/viewvc/commons/proper/math/trunk"/>
+      <item name="Downloads" href="http://commons.apache.org/math/download_math.cgi"/>
+      <item name="Wiki"  
+            href="http://wiki.apache.org/commons/Math"/>
+    </menu>
+    
+    <menu name="User Guide">
+      <item name="Contents"                href="/userguide/index.html"/>
+      <item name="Overview"                href="/userguide/overview.html"/>
+      <item name="Statistics"              href="/userguide/stat.html"/>
+      <item name="Data Generation"         href="/userguide/random.html"/>
+      <item name="Linear Algebra"          href="/userguide/linear.html"/>
+      <item name="Numerical Analysis"      href="/userguide/analysis.html"/>
+      <item name="Special Functions"       href="/userguide/special.html"/>
+      <item name="Utilities"               href="/userguide/utilities.html"/>
+      <item name="Complex Numbers"         href="/userguide/complex.html"/>
+      <item name="Distributions"           href="/userguide/distribution.html"/>
+      <item name="Fractions"               href="/userguide/fraction.html"/>
+      <item name="Transform Methods"       href="/userguide/transform.html"/>
+      <item name="3D Geometry"             href="/userguide/geometry.html"/>
+      <item name="Optimization"            href="/userguide/optimization.html"/>
+      <item name="Ordinary Differential Equations" href="/userguide/ode.html"/>
+      <item name="Genetic Algorithms"      href="/userguide/genetics.html"/>
+    </menu>
+
+  </body>
+</project>
diff --git a/src/site/xdoc/changes.xml b/src/site/xdoc/changes.xml
new file mode 100644
index 0000000..400db60
--- /dev/null
+++ b/src/site/xdoc/changes.xml
@@ -0,0 +1,1312 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<!--
+This file is used by the maven-changes-plugin to generate the release notes.
+Useful ways of finding items to add to this file are:
+
+1.  Add items when you fix a bug or add a feature (this makes the 
+release process easy :-).
+
+2.  Do a JIRA search for tickets closed since the previous release.
+
+3.  Use the report generated by the maven-changelog-plugin to see all
+SVN commits. TBA how to use this with SVN.
+
+To generate the release notes from this file:
+
+mvn changes:announcement-generate
+mv target/announcement/math-release-notes.vm RELEASE_NOTES.txt
+then tweak the formatting 
+(e.g. copy/paste the description to replace the one-line version)
+and commit
+
+The <action> type attribute can be add,update,fix,remove.
+-->
+
+<document>
+  <properties>
+    <title>Commons Math Release Notes</title>
+  </properties>
+  <body>
+    <!-- NOTE: 
+    The description below is specially formatted so as to improve the layout of the generated release notes:
+    The parsing process removes all line feeds, replacing them with a single space.
+    The Velocity template in resources/templates has been enhanced to replace pairs of adjacent spaces
+    with a new-line in the release notes. (These spaces are ignored when displaying HTML).
+    If the output is not quite correct, check for invisible trailing spaces!
+     -->
+    <release version="2.2" date="2011-03-02" description="
+This is primarily a maintenance release, but it also includes new features and enhancements.
+
+  Users of version 2.1 are encouraged to upgrade to 2.2, as this release includes some important bug fixes.
+
+  See the detailed list of changes below for full description of all bug fixes and enhancements.
+
+  This release contains some minor compatibility breaks with version 2.1 in some internal classes but none
+  of them are in APIs likely to be accessed by user code:
+  the MessagesResources_fr class has been removed (replaced by a properties file);
+  the arguments of the EventState.reinitializeBegin method have changed;
+  some protected fields which already had public accessors in AbstractStepInterpolator have been replaced.  
+  
+  There is a behavior change that users of the multiple regression classes should be aware of. In version
+  2.1, there was no way to estimate models without intercept terms, and, while this was not clear from
+  the documentation, design (X) matrices needed to include initial unitary columns.  In 2.2, initial 
+  unitary columns are not necessary and whether or not models include intercept terms is configurable.
+  See the change log and javadoc for the classes in org.apache.commons.math.stat.regression for details.
+
+  The major new features are:
+  a new FastMath class, both faster, more accurate and with a few additional functions than StrictMath and Math;
+  a new package for floating point arbitrary precision computing, including high level functions like exponential, sine, square root ...;
+  new linear and tricubic interpolators;
+  a new Gaussian curve fitter;
+  a new erfc function;
+  characteristic support for distributions;
+  a set of new Well Equidistributed Long-period Linear (WELL) random generators.">
+      <action dev="sebb" type="fix" issue="MATH-505">
+          TestUtils is thread-hostile. Deprecate the getters and setters.
+      </action>
+      <action dev="luc" type="fix" issue="MATH-498">
+          FastMath is not an exact replacement for StrictMath
+          (partially fixed) added scalb(double, int), scalb(float, int)
+      </action>
+      <action dev="luc" type="fix" issue="MATH-478">
+          FastMath is not an exact replacement for StrictMath
+          (partially fixed) added hypot(double, double), nextAfter(double, double)
+          and nextAfter(float,double) (beware of the strange double second argument)
+          so that they handle special values in the way as StrictMath
+      </action>
+      <action dev="luc" type="fix" issue="MATH-497">
+          FastMath is not an exact replacement for StrictMath
+          (partially fixed) added getExponent(double) and getExponent(float)
+      </action>
+      <action dev="sebb" type="fix" issue="MATH-496">
+          FastMath is not an exact replacement for StrictMath
+          (partially fixed) Add copySign(double), copySign(float)
+      </action>
+      <action dev="sebb" type="fix" issue="MATH-494">
+          FastMath atan2 does not agree with StrictMath for special cases
+      </action>
+      <action dev="sebb" type="fix" issue="MATH-489">
+          FastMath acos fails when input abs value is less than about 5.7851920321187236E-300 - returns NaN
+      </action>
+      <action dev="luc" type="fix" issue="MATH-484">
+          separate discrete event detection from adaptive step size handling in ODE solvers,
+          thus improving robustness, maintainability and speed
+      </action>
+      <action dev="sebb" type="fix" issue="MATH-486">
+          FastMath toRadian and toDegree don't handle large double numbers well
+      </action>
+      <action dev="sebb" type="fix" issue="MATH-483">
+          FastMath does not handle all special cases correctly
+      </action>
+      <action dev="sebb" type="fix" issue="MATH-480">
+          Fix ulp(Infinity) to return Infinity rather than NaN
+      </action>
+      <action dev="sebb" type="fix" issue="MATH-479">
+        FastMath.signum(-0.0) does not agree with Math.signum(-0.0)
+      </action>
+      <action dev="sebb" type="fix" issue="MATH-478">
+          FastMath is not an exact replacement for StrictMath
+          (partially fixed) Add signum(float), ulp(float)
+      </action>
+      <action dev="sebb" type="fix" issue="MATH-471">
+        MathUtils.equals(double, double) does not work properly for floats
+        - add equivalent (float, float) methods and basic tests
+      </action>
+      <action dev="sebb" type="fix" issue="MATH-482">
+        FastMath.max(50.0f, -50.0f) => -50.0f; should be +50.0f
+        Fixed FastMath.max(float, float) so it returns correct value.
+      </action>
+      <action dev="luc" type="fix" issue="MATH-467">
+        Fixed an awkward statement that triggered a false positive warning.
+      </action>
+      <action dev="psteitz" type="add" issue="MATH-364" due-to="Christian Winter">
+        Added complementary error function, erfc.
+      </action>
+      <action dev="psteitz" type="fix" issue="MATH-456">
+        Modified erf (and erfc) to return extreme values for x with abs(x) > 40.
+        For these arguments, the true value is indistinguishable from an extrema as a double.
+      </action>
+      <action dev="mikl" type="add" issue="MATH-385">
+        Added characteristic support to distributions, including methods to return numerical
+        estimates of the mean and variance and upper and lower bounds of support. In version 2.2,
+        methods returning distribution characteristics have been added only to the implementation
+        classes.  In version 3, supporting methods have been added to the abstract base classes
+        and distribution interfaces.
+      </action>
+      <action dev="mikl" type="update" issue="MATH-384">
+        Added a constructor and addValues(double[]) methods allowing DescriptiveStatistics to
+        be initialized with values from a double[] array.  Similarly enhanced 
+        ResizeableDoubleArray.
+      </action>
+      <action dev="psteitz" type="update" issue="MATH-448" due-to="Patrick Meyer">
+        Added a getUniqueCount() method to Frequency to return the number of unique
+        values included in the frequency table.
+      </action>
+      <action dev="psteitz" type="fix" issue="MATH-414">
+        Modified NormalDistributionImpl.cumulativeProbability to return 0 or 1,
+        respectively for values more than 40 standard deviations from the mean.
+        For these values, the actual probability is indistinguishable from 0 or 1
+        as a double.  Top coding improves performance for extreme values and prevents
+        convergence exceptions.
+      </action>
+      <action dev="psteitz" type="update" issue="MATH-420">
+        Added toString() override to StatisticalSummaryValues.
+      </action>
+      <action dev="luc" type="fix" issue="MATH-380">
+        Deprecated the whole ode.jacobians package. It is clumsy and difficult to use. It will
+        be replaced by a completely rewritten implementation in 3.0, which will be more tightly
+        bound to the top level ode package
+      </action>
+      <action dev="luc" type="fix" issue="MATH-426" due-to="Erik van Ingen">
+        Added a normalization feature to transform samples so they have zero mean and unit standard deviation
+      </action>
+      <action dev="erans" type="add" issue="MATH-440">
+        Created "MathUserException" class to convey cause of failure between
+        layers of user code separated by a layer of Commons-Math code.
+      </action>
+      <action dev="luc" type="fix" issue="MATH-429">
+        Fixed k-means++ to add several strategies to deal with empty clusters that may appear
+        during iterations
+      </action>
+      <action dev="luc" type="update" issue="MATH-417">
+        Improved Percentile performance by using a selection algorithm instead of a
+        complete sort, and by allowing caching data array and pivots when several
+        different percentiles are desired
+      </action>
+      <action dev="luc" type="fix" issue="MATH-391">
+        Fixed an error preventing zero length vectors to be built by some constructors
+      </action>
+      <action dev="luc" type="fix" issue="MATH-421">
+        Fixed an error preventing ODE solvers to be restarted after they have been stopped by a discrete event
+      </action>
+      <action dev="luc" type="add" issue="MATH-419">
+        Added new random number generators from the Well Equidistributed Long-period Linear (WELL).
+      </action>
+      <action dev="psteitz" type="update" issue="MATH-409">
+        Made intercept / no intercept configurable in multiple regression classes. By default, regression
+        models are estimated with an intercept term.  When the "noIntercept" property is set to
+        true, regression models are estimated without intercepts.
+      </action>
+      <action dev="luc" type="fix" issue="MATH-415">
+        Fixed lost cause in MathRuntimeException.createInternalError. Note that the message is still the default
+        message for internal errors asking to report a bug to commons-math JIRA tracker. In order to retrieve
+        the message from the root cause, one has to get the cause itself by getCause().
+      </action>
+      <action dev="psteitz" type="fix" issue="MATH-411">
+        Modified multiple regression newSample methods to ensure that by default in all cases,
+        regression models are estimated with intercept terms.  Prior to the fix for this issue, 
+        newXSampleData(double[][]), newSampleData(double[], double[][]) and
+        newSampleData(double[], double[][], double[][]) all required columns of "1's" to be inserted
+        into the x[][] arrays to create a model with an intercept term; while newSampleData(double[], int, int)
+        created a model including an intercept term without requiring the unitary column.  All methods have
+        been changed to eliminate the need for users to add unitary columns to specify regression models.
+        Users of OLSMultipleLinearRegression or GLSMultipleLinearRegression versions 2.0 or 2.1 should either
+        verify that their code either does not use the first set of data loading methods above or set the noIntercept
+        property to true on estimated models to get the previous behavior.
+      </action>
+      <action dev="luc" type="add" issue="MATH-412" due-to="Bill Rossi">
+        Added the dfp library providing arbitrary precision floating point computation in the spirit of
+        IEEE 854-1987 (not exactly as it uses base 1000 instead of base 10). In addition to finite numbers,
+        infinities and NaNs are available (but there are no subnormals). All IEEE 854-1987 rounding modes and
+        signaling flags are supported. The available operations are +, -, *, / and the available functions
+        are sqrt, sin, cos, tan, asin, acos, atan, exp, log.
+      </action>
+      <action dev="luc" type="add" issue="MATH-375" due-to="Bill Rossi">
+        Added faster and more accurate version of traditional mathematical functions in a FastMath
+        class intended to be a drop-in replacement for java.util.Math at source-level. Some functions
+        still directly delegates to Math but this will improve with time. Some functions like exp
+        may be twice as fast (even 3 times faster on some processors). Sine, cosine or power functions
+        show typical speedups of 1.5 times faster or more. 
+      </action>
+      <action dev="psteitz" type="fix" issue="MATH-386">
+        Added R-squared and adjusted R-squared statistics to OLSMultipleLinearRegression.
+      </action>
+      <action dev="psteitz" type="fix" issue="MATH-392" due-to="Mark Devaney">
+        Corrected the formula used for Y variance returned by calculateYVariance and associated
+        methods in multiple regression classes (AbstractMultipleLinearRegression,
+        OLSMultipleLinearRegression, GLSMultipleLinearRegression).  These methods previously returned
+        estimates of the variance in the model error term.  New "calulateErrorVariance" methods have
+        been added to compute what was previously returned by calculateYVariance.
+      </action>
+      <action dev="dimpbx" type="fix" issue="MATH-406">
+        Bug fixed in Levenberg-Marquardt (handling of weights).
+      </action>
+      <action dev="dimpbx" type="fix" issue="MATH-405">
+        Bug fixed in Levenberg-Marquardt (consistency of current).
+      </action>
+      <action dev="dimpbx" type="fix" issue="MATH-377">
+        Bug fixed in chi-square computation in AbstractLeastSquaresOptimizer.
+      </action>
+      <action dev="luc" type="add" issue="MATH-400" due-to="J. Lewis Muir">
+        Added support for Gaussian curve fitting.
+      </action>
+      <action dev="erans" type="fix" issue="MATH-395">
+        Fixed several bugs in "BrentOptimizer".
+      </action>
+      <action dev="erans" type="fix" issue="MATH-393">
+        Fixed inconsistency in return values in "MultiStartUnivariateRealOptimizer".
+      </action>
+      <action dev="luc" type="add" issue="MATH-388">
+        Added a feature allowing error estimation to be computed only on a subset of
+        Ordinary Differential Equations, considered as the main set, the remaining equations
+        being considered only as an extension set that should not influence the ODE integration
+        algorithm 
+      </action>
+      <action dev="erans" type="fix" issue="MATH-382">
+        Fixed bug in precondition check (method "setMicrosphereElements").
+      </action>
+      <action dev="erans" type="add" issue="MATH-379">
+        Created "MultidimensionalCounter" class.
+      </action>
+      <action dev="erans" type="update" issue="MATH-361">
+        Created package "exception" to contain the new exceptions hierarchy.
+        Created package "exception.util": utilities for the exception classes
+        (e.g. managing the localization of error messages).
+        Default policy for dealing with invalid null references: raise a
+        "NullArgumentException" (subclass of "IllegalArgumentException").
+      </action>
+      <action dev="erans" type="add" issue="MATH-378" due-to="Matthew Rowles">
+        Implementation of linear interpolation.
+      </action>
+      <action dev="luc" type="fix" issue="MATH-361">
+        Improved localization of error messages.
+      </action>      
+      <action dev="erans" type="fix" issue="MATH-376">
+        Allow multiple optimizations with a default simplex.
+      </action>      
+      <action dev="erans" type="add" issue="MATH-370">
+        Added new "equalsIncludingNaN" methods that have the same semantics as the old "equals" methods.
+        The semantics of the old methods will be modified (in the next major release) such that
+        NaNs are not considered equal (to be more compliant with IEEE754).
+      </action>
+      <action dev="luc" type="fix" issue="MATH-352" >
+        Added a setQRRankingThreshold method to Levenberg-Marquardt optimizer to improve robustness
+        of rank determination.
+      </action>
+      <action dev="psteitz" type="update" issue="MATH-310">
+        Added random data generation methods to RandomDataImpl for the remaining distributions in the
+        distributions package. Added a generic nextInversionDeviate method that takes a discrete
+        or continuous distribution as argument and generates a random deviate from the distribution.
+        Also added sampling methods based on the implementations in RandomDataImpl to distributions.
+      </action>
+      <action dev="luc" type="fix" issue="MATH-362" >
+        Fixed Levenberg-Marquardt optimizer that did not use the vectorial convergence checker.
+        Now this optimizer can use either the general vectorial convergence checker or its own
+        specialized convergence settings.
+      </action>
+      <action dev="psteitz" type="fix" issue="MATH-371" due-to="Kevin Childs">
+        Fixed loss of significance error in PersonsCorrelation p-value computation causing p-values
+        smaller than the machine epsilon (~1E-16) to be reported as 0.
+      </action>
+      <action dev="luc" type="fix" issue="MATH-369" due-to="Sasun Pundev">
+        Fix NullPointerException in BisectionSolver.solve(f, min, max, initial)
+      </action>
+      <action dev="billbarker" type="fix" issue="MATH-368">
+       Fix spelling of getSparcity [sic] method of OpenMapRealVector
+      </action>
+      <action dev="billbarker" type="fix" issue="MATH-367" due-to="Albert Huang">
+       Fix problem with the default sparseIterator when a RealVector has exactly one non-zero entry
+      </action>
+      <action dev="erans" type="add" issue="MATH-366">
+        Implementation of tricubic interpolation.
+      </action>
+      <action dev="erans" type="update" issue="MATH-365">
+        Deprecated SmoothingBicubicSplineInterpolator and SmoothingBicubicSplineInterpolatorTest.
+        Added BicubicSplineInterpolator and BicubicSplineInterpolatorTest.
+        Added SmoothingPolynomialBicubicSplineInterpolator and SmoothingPolynomialBicubicSplineInterpolatorTest.
+        Added method to clear the list of observations in PolynomialFitter.
+      </action>
+    </release>
+    <release version="2.1" date="2010-04-02" description="
+This is primarily a maintenance release, but it also includes new features and enhancements.
+
+  Users of version 2.0 are encouraged to upgrade to 2.1, as this release includes some important bug fixes.
+
+  See the detailed list of changes below for full description of all bug fixes and enhancements.
+
+  This release contains some minor API compatibility breaks with version 2.0:
+  the return type of RealVector.copy() has been changed to AbstractRealVector;
+  the no-argument constructor of MatrixUtils() has been made private;
+  the mapXxxToSelf methods of OpenMapRealVector have been removed and some method return types have been changed in this class;
+  new methods have been added to the RealVector interface;
+  several fields in AdaptiveStepSizeIntegrator have been made final;
+  DummyStepInterpolator requires an additional argument for one of its constructors;
+  some protected fields have been removed from AbstractLeastSquaresOptimizer, AbstractScalarDifferentiableOptimizer and AbstractLinearOptimizer;
+  and the isOptimal(SimplexTableau) method has been removed from SimplexSolver. ">
+      <action dev="sebb" type="fix" issue="MATH-360">
+        Fix use of wrong variable in SmoothingBicubicSplineInterpolatorTest.testPreconditions()
+      </action>
+      <action dev="erans" type="update" issue="MATH-356">
+        Added method to clear the list of observations in CurveFitter.
+      </action>
+      <action dev="erans" type="add" issue="MATH-357">
+        Implementation of bicubic interpolation.
+      </action>
+      <action dev="psteitz" type="update" issue="MATH-332" due-to="Mikkel Meyer Andersen">
+        Added density functions to remaining continuous distributions (F, T, Weibull, Cauchy).
+        As of Math 2.1, all continuous distributions implement density functions.  The HasDensity
+        interface has been deprecated and in version 3.0, density(double) will be added to the
+        ContinuousDistribution interface.
+      </action>
+      <action dev="sebb" type="update" issue="MATH-337">
+        Changed equals() methods to use instanceof check rather than catching ClassCastException;
+        this also allows some null checks to be omitted.
+      </action>
+      <action dev="sebb" type="update" issue="MATH-336">
+        Removed unnecessary null checks in equals methods.
+      </action>
+      <action dev="sebb" type="fix" issue="MATH-335">
+        Fraction.hashCode() implementation was not fully consistent with Fraction.equals().
+        Changed hashCode() to use fields directly to agree with equals().
+      </action>
+      <action dev="psteitz" type="fix" issue="MATH-282">
+        Resolved multiple problems leading to inaccuracy and/or failure to compute Normal,
+        ChiSquare and  Poisson probabilities, Erf and Gamma functions.  Made Brent solver 
+        absolute accuracy configurable for all continuous distributions.
+      </action>
+      <action dev="luc" type="fix" issue="MATH-347" >
+        Fixed too stringent interval check in Brent solver: initial guess is now
+        allowed to be at either interval end
+      </action>
+      <action dev="luc" type="add" >
+        Added a way to compute both the final state in an Initial Value Problem (IVP)
+        for Ordinary Differential Equations (ODE) and its derivatives with respect to
+        initial state and with respect to some problem parameters. This allows wrapping
+        ODE solvers into optimization or root finding algorithms, which in turn can be
+        used to solve Boundary Value Problems (BVP). There are no implementations (yet)
+        of BVP solvers in the library.
+      </action>
+      <action dev="luc" type="fix" issue="MATH-358" >
+        Fixed an error in events handling in ODE solvers. In some rare cases, events
+        occurring close to a step start were handled without truncating the step, making
+        them appear as is they occurred close to the step end
+      </action>
+      <action dev="luc" type="fix" >
+        Fixed a problem with getInterpolatedDerivatives returning zero derivatives when
+        an ODE step handler is configured to not use interpolation. It now returns a
+        constant but non-zero value consistent with at least one point inside the step
+      </action>
+      <action dev="luc" type="fix" issue="MATH-344" >
+        Fixed wrong return values when enpoints are roots in Brent solver with
+        a user provided initial guess
+      </action>
+      <action dev="luc" type="fix" issue="MATH-343" >
+        Fixed a missing bracketing check of initial interval in Brent solver.
+      </action>
+      <action dev="dimpbx" type="fix" issue="MATH-342">
+        In SVD, the matrices passed to EigenDecomposition are now symmetric
+        by construction (rather than simply by definition).  In EigenDecomposition,
+        once the tridiagonal form is obtained, the non-significant elements are
+        set to 0.
+      </action>
+      <action dev="dimpbx" type="fix" issue="MATH-333">
+        A EigenDecompositionImpl simplified makes it possible to compute
+        the SVD of a singular matrix (with the right number of elements in
+        the diagonal matrix) or a matrix with singular value(s) of multiplicity
+        greater than 1. 
+      </action>
+      <action dev="psteitz" type="add" issue="MATH-323" due-to="Larry Diamond">
+        Added SemiVariance statistic.
+      </action>
+      <action dev="luc" type="add" issue="MATH-341" >
+        Added a warning in the getCoefficients method documentation for
+        PolynomialFunctionLagrangeForm. Computation may be ill-conditioned
+        so this method should be used with care.
+      </action>
+      <action dev="luc" type="add" issue="MATH-340" >
+        Fixed an error in BigFraction multiplication for large numerators that don't
+        fit in a primitive int.
+      </action>
+      <action dev="luc" type="fix" >
+        Fixed a spurious exception in EigenDecompositionImpl when a 3x3 block
+        had two identical eigenvalues.
+      </action>
+      <action dev="luc" type="add" issue="MATH-334" >
+        Added min/max getters for real vectors (not yet in the RealVector interface for
+        compatibility purposes, but in the AbstractRealVector abstract class).
+      </action>
+      <action dev="luc" type="fix" issue="MATH-338" due-to="Vincent Morand">
+        Fixed automatic step initialization in embedded Runge-Kutta integrators.
+        The relative tolerance setting was never used, only the absolute tolerance
+        was used.
+      </action>
+      <action dev="psteitz" type="fix" issue="MATH-329">
+        Fixed regression in Frequency.getPct(Object) introduced in 2.0. Cumulative
+        percent was being returned for Object arguments in place of percent.
+      </action>
+      <action dev="luc" type="add" issue="MATH-321" >
+        Singular Value Decomposition now computes either the compact SVD (using only
+        positive singular values) or truncated SVD (using a user-specified maximal
+        number of singular values).
+      </action>
+      <action dev="luc" type="fix" issue="MATH-320" >
+        Fixed Singular Value Decomposition solving of singular systems.
+      </action>
+      <action dev="psteitz" type="update" issue="MATH-239" due-to="Christian Semrau">
+        Added MathUtils methods to compute gcd and lcm for long arguments.
+      </action>
+      <action dev="psteitz" type="update" issue="MATH-287" due-to="Matthew Rowles">
+        Added support for weighted univariate statistics.
+      </action>
+      <action dev="luc" type="fix" issue="MATH-326" due-to="Jake Mannix">
+        Fixed a wrong implementation of the Linf norm in vectors.
+      </action>
+      <action dev="luc" type="fix" issue="MATH-324" due-to="Vincent Morand">
+        Fixed a convergence discrepancy with respect to theory in Gragg-Bulirsch-Stoer
+        integrator.
+      </action>
+      <action dev="luc" type="fix" due-to="Dimitri Pourbaix">
+        Fixed a wrong dimension check in SVD solver.
+      </action>
+      <action dev="luc" type="fix" issue="MATH-313" due-to="Jake Mannix">
+        Added composition features for real functions.
+      </action>
+      <action dev="luc" type="fix" issue="MATH-312" due-to="Jake Mannix">
+        Added mapping and iteration methods to vectors. Provided a default implementation
+        for the numerous simple methods in the RealVectorInterface.
+      </action>
+      <action dev="luc" type="fix" issue="MATH-322" >
+        Fixed an error in handling very close events in ODE integration.
+      </action>
+      <action dev="psteitz" type="fix" issue="MATH-305" due-to="Erik van Ingen">
+        Fixed an overflow error in MathUtils.distance that was causing KMeansPlusPlusClusterer
+        to fail with a NullPointerException when component distances between points
+        exceeded Integer.MAXVALUE.
+      </action>
+      <action dev="psteitz" type="update" issue="MATH-315" due-to="Mikkel Meyer Andersen">
+        Added generationsEvolved property to GeneticAlgorithm to track the number of generations
+        evolved by the evolve() method before reaching the StoppingCondition.
+      </action>
+      <action dev="luc" type="fix" issue="MATH-318" due-to="Dimitri Pourbaix">
+        Fixed an index computation error in eigen decomposition. Once again, kudos to Dimitri
+        for debugging this.
+      </action>
+      <action dev="luc" type="fix" issue="MATH-308" due-to="Dimitri Pourbaix">
+        Fixed an ArrayIndexOutOfBoundsException in eigen decomposition. Kudos to Dimitri
+        for debugging this, it was really difficult.
+      </action>
+      <action dev="psteitz" type="fix" issue="MATH-309" due-to="Mikkel Meyer Andersen">
+        Fixed parameter test in RandomDataImpl#nextExponential. The method now throws
+        IllegalArgumentException for mean = 0.
+      </action>
+      <action dev="brentworden" type="update" issue="MATH-311" due-to="Nipun Jawalkar">
+        Changed probability calculations for Binomial, Poisson, and Hypergeometric
+        distributions to use Catherine Loader's saddle point approximations.
+      </action>
+      <action dev="psteitz" type="fix" issue="MATH-306" due-to="Joerg Huber">
+        Removed dead code from Complex#divide.
+      </action>
+      <action dev="psteitz" type="fix" issue="MATH-294">
+        Fixed implementation of RandomDataImpl#nextPoisson by implementing an alternative
+        algorithm for large means.
+      </action>
+      <action dev="psteitz" type="add" issue="MATH-300" due-to="Gilles Sadowski">
+        Added support for multidimensional interpolation using the robust microsphere algorithm.
+      </action>
+      <action dev="psteitz" type="fix" issue="MATH-298">
+        Fixed implementation of EmpiricalDistributionImpl#getUpperBounds to match
+        interface contract.  Added getGeneratorUpperBounds method to
+        EmpiricalDistributionImpl providing previous behavior.
+      </action>
+      <action dev="luc" type="fix" issue="MATH-296" due-to="Eugene Kirpichov">
+        Fixed wrong results on Loess interpolation, also added a way to set weights
+        for smoothing and to ignore zero weights for coefficients computation
+      </action>
+      <action dev="luc" type="fix" issue="MATH-293" due-to="Benjamin McCann">
+        Fixed a OutOfBoundException in simplex solver when some constraints are tight.
+      </action>
+      <action dev="luc" type="fix" issue="MATH-291" due-to="Sebb">
+        Fixed misleading number formats in error messages for adaptive
+        stepsize integrators.
+      </action>
+      <action dev="psteitz" type="add" issue="MATH-287" due-to="Matthew Rowles">
+        Added support for weighted descriptive statistics.
+      </action>
+      <action dev="psteitz" type="add">
+        Added normalizeArray method to MathUtils.
+      </action>
+      <action dev="luc" type="fix" issue="MATH-290" due-to="Benjamin McCann">
+        Fixed a NullPointerException in simplex solver when no solution is possible
+        and some constraints are negative.
+      </action>
+      <action dev="luc" type="fix" issue="MATH-289" >
+        Removed an unused argument in a private method in simplex solver.
+      </action>
+      <action dev="luc" type="fix" issue="MATH-288" due-to="Benjamin McCann">
+        Fixed an error induced by entries set to 0 in simplex solver.
+      </action>
+      <action dev="luc" type="fix" issue="MATH-286" due-to="Benjamin McCann">
+        Fixed an error leading the simplex solver to compute the right solution
+        but return another one.
+      </action>
+      <action dev="luc" type="fix" issue="MATH-283" due-to="Michael Nischt">
+        Prevent infinite loops in multi-directional direct optimization method when
+        the start point is exactly at the optimal point.
+      </action>
+      <action dev="billbarker" type="fix" issue="MATH-297" due-to="Axel Kramer">
+         Prevent possible zero divides on eigenvectors of indefinite matrices
+      </action>
+    </release>
+    <release version="2.0" date="2009-08-03" description="
+This is a major release. It combines bug fixes, new features and
+changes to existing features. Most notable among the new features are:
+decomposition algorithms in the linear algebra package (LU, QR, Cholesky,
+SVD, eigen decomposition) which are based on the popular JAMA API (but
+much faster); support for sparse matrices and vectors;
+support for any field-based matrix (Complex, Fraction ...);
+support for genetic algorithms;
+several new optimization algorithms (Dantzig's simplex for linear
+constrained problems, conjugate gradient, Brent);
+support for curve fitting with special cases for harmonic and polynomial
+functions;
+support for state derivative in ODE step handlers;
+new multistep integrators (Adams-Bashforth and Adams-Moulton) with
+variable stepsize;
+regression algorithms;
+rank transformations;
+Mersenne twister pseudo random number generator.
+This release is NOT source and binary compatible with earlier versions
+of Commons Math. Starting with version 2.0 of the library, the minimal
+version of the Java platform required to compile and use commons-math
+is Java 5.  Users are encouraged to upgrade to this version, as in addition
+to new features, this release includes numerous bug fixes.  Users of 
+Commons Math 1.0-1.2 should recompile their code against the 2.0 jar. 
+Most of the compilation errors users will encounter after the switch
+will be due to classes moved due to package reorganization.  These errors
+are simply solved by adjusting the import statements in users code.">
+      <action dev="luc" type="fix" issue="MATH-281" due-to="Albert Huang">
+        Fixed an error in RealMatrix javadoc
+      </action>
+      <action dev="luc" type="add" >
+        Added an implementation of the Mersenne twister pseudo random number generator
+        from Makoto Matsumoto and Takuji Nishimura
+      </action>
+      <action dev="luc" type="update" due-to="Gilles Sadowski">
+        Changed the return type of the various interpolation algorithms to the
+        specific implementation of UnivariateRealFunction each one uses
+      </action>
+      <action dev="luc" type="fix" issue="MATH-280">
+        The behavior of the bracket method in UnivariateRealSolverUtils has been changed to return successfully
+        when a tentative bracketing interval has a root exactly at one of its end points. Previously, such intervals
+        triggered an exception.
+      </action>
+      <action dev="luc" type="add" issue="MATH-279" due-to="Michael Bjorkegren">
+        Added a check for too few rows with respect to the number of predictors in linear regression
+      </action>
+      <action dev="luc" type="add" due-to="Dimitri Pourbaix">
+        Added a getCovariance method to singular value decomposition
+      </action>
+      <action dev="luc" type="add" issue="MATH-278" due-to="Eugene Kirpichov">
+        Added robust locally weighted regression (Loess).
+      </action>
+      <action dev="luc" type="add" issue="MATH-277" due-to="Mark Anderson">
+        Added a scalar multiply to the Complex class
+      </action>
+      <action dev="luc" type="add" >
+        Added curve fitting with a general case and two specific cases (polynomial and harmonic).
+      </action>
+      <action dev="psteitz" type="update" issue="MATH-276" due-to="Mark Anderson">
+        Optimized Complex isNaN(), isInfinite() by moving computation to constructor.
+      </action>
+      <action dev="psteitz" type="fix" issue="MATH-207" due-to="David Stefka">
+        Added Genetic Algorithm implementation.
+      </action>
+      <action dev="luc" type="fix" issue="MATH-274" >
+        Fixed detection of not positive definite matrices in Cholesky decomposition
+      </action>
+      <action dev="luc" type="fix" issue="MATH-273" due-to="Benjamin McCann">
+        Fixed a wrong check for basic variables
+      </action>
+      <action dev="luc" type="fix" issue="MATH-272" due-to="Benjamin McCann">
+        Fixed a problem when setting some variables (several variables were set
+        instead of only one)
+      </action>
+      <action dev="luc" type="add" due-to="Gilles Sadowski">
+        Added a way to limit the number of functions evaluations in optimizers
+        (the number of iterations could already be limited)
+      </action>
+       <action dev="psteitz" type="add" issue="MATH-267" due-to="Ted Dunning">
+        Added digamma function.
+      </action>
+      <action dev="psteitz" type="add" issue="MATH-136" due-to="John Gant">
+        Added Spearman's rank correlation (SpearmansCorrelation).
+      </action>
+      <action dev="psteitz" type="add">
+        Added support for rank transformations.
+      </action>
+      <action dev="luc" type="update">
+        Completed internationalization of all error messages
+      </action>
+      <action dev="luc" type="add" issue="MATH-266" due-to="Benjamin McCann">
+        Added a clustering package with an implementation of the k-means++ algorithm
+      </action>
+      <action dev="luc" type="fix" issue="MATH-265" due-to="Benjamin McCann">
+        Added distance1, distance and distanceInf utility methods for double and
+        int arrays in MathUtils
+      </action>
+      <action dev="luc" type="fix" issue="MATH-264" due-to="Gilles Sadowski">
+        Added an utility equality method between double numbers using tolerance
+        in ulps (Units in Last Position)
+      </action>
+      <action dev="luc" type="fix" issue="MATH-263">
+        Added getNorm1, getNormInf, distance1 and distanceInf to the Vector3D class
+      </action>
+      <action dev="luc" type="add" >
+        Added support for any type of field in linear algebra (FielxMatrix, FieldVector,
+        FieldLUDecomposition)
+      </action>
+      <action dev="luc" type="add" >
+        The RealMatrixImpl implementation classes has been renamed Array2DRowRealMatrix
+        to reflect its specificities and for consistency with the new implementations. The
+        previous name is still available but is deprecated
+      </action>
+      <action dev="luc" type="add" >
+        Added a block-based storage type for dense matrices improving speed for large dimensions
+      </action>
+      <action dev="psteitz" type="add" due-to="John Bollinger">
+        Added AggregateSummaryStatistics class to support aggregation of SummaryStatistics.
+      </action>
+      <action dev="luc" type="add" >
+        Added general Field and FieldElement interfaces to allow generic algorithms
+        to operate on fields. The library already provides several implementations:
+        Complex, Fraction, BigFraction and BigReal
+      </action>
+      <action dev="luc" type="fix" issue="MATH-257" due-to="Sebb">
+        Fixed inconsistent access to multidimensional array in FastFourierTransformer
+      </action>
+      <action dev="luc" type="fix" issue="MATH-248" >
+        Greatly improved multiplication speed for sparse matrices
+      </action>
+      <action dev="luc" type="fix" issue="MATH-253" due-to="Sebb">
+        Fixed threading issues with MathException and MathRuntimeException
+      </action>
+      <action dev="luc" type="fix" issue="MATH-254" due-to="Sebb">
+        Fixed threading issues with UnivariateRealSolverUtils factory
+      </action>
+      <action dev="luc" type="fix" issue="MATH-255" due-to="Sebb">
+        Reduced visibility of MessagesResources_fr.contents field to private
+      </action>
+      <action dev="luc" type="add" issue="MATH-256" >
+        Added  Fraction.toString()
+      </action>
+      <action dev="luc" type="add">
+        Added  add/subtract/multiply/divide functions with integer parameters to Fraction
+      </action>
+      <action dev="luc" type="add">
+        Added some utility functions to compute powers with integral types (int, long, BigInteger).
+      </action>
+      <action dev="luc" type="fix" issue="MATH-252">
+        Fixed a comparison error when two different fractions evaluate to the
+        same double due to limited precision.
+      </action>
+      <action dev="luc" type="add" issue="MATH-251" due-to="Benjamin Croizet">
+        Added a BigFraction class that does not overflow when big numerators or
+        denominators are used.
+      </action>
+      <action dev="luc" type="add" issue="MATH-246" due-to="Benjamin McCann">
+        Added an optimizer for constrained linear problems based on 2-phases standard simplex.
+      </action>
+      <action dev="luc" type="fix" issue="MATH-177" >
+        Redesigned the optimization framework for a simpler yet more powerful API.
+        Added non-linear conjugate gradient optimizer.
+      </action>
+      <action dev="luc" type="fix" issue="MATH-243" due-to="Christian Semrau">
+        Fixed an error in computing gcd and lcm for some extreme values at integer
+        range boundaries.
+      </action>
+      <action dev="luc" type="add" issue="MATH-247" due-to="Benjamin McCann">
+        Added a MathUtils method to check equality given some error bounds.
+      </action>
+      <action dev="psteitz" type="add" issue="MATH-114" due-to="John Gant">
+        Added PearsonsCorrelation class to compute correlation matrices, standard
+        errors and p-values for correlation coefficients.
+      </action>
+      <action dev="psteitz" type="add" issue="MATH-114">
+        Added Covariance class to compute variance-covariance matrices in new
+        correlation package.
+      </action>
+      <action dev="luc" type="fix" issue="MATH-216" due-to="Cyril Briquet">
+        Improved fast Fourier transform efficiency.
+      </action>
+      <action dev="billbarker" type="add">
+         Added a SparseRealVector class that implements a sparse vector for the RealVector interface.
+      </action>
+      <action dev="luc" type="add" >
+        Added factory methods to create Chebyshev, Hermite, Laguerre and Legendre polynomials.
+      </action> 
+      <action dev="luc" type="add" >
+        Added add, subtract, negate, multiply and toString methods to PolynomialFunction.
+      </action> 
+      <action dev="psteitz" type="update" issue="MATH-189">
+        Changed FractionFormat to extend NumberFormat.
+      </action> 
+      <action dev="psteitz" type="update" issue="MATH-242" due-to="Christian Semrau">
+        Forced symmetry in binomialCoefficientLog and added test cases for MathUtils.
+      </action>
+      <action dev="psteitz" type="fix" issue="MATH-241" due-to="Christian Semrau">
+        Fixed error in binomial coefficient computation.
+      </action>
+      <action dev="luc" type="add" >
+        Added a Legendre-Gauss integrator.
+      </action>
+      <action dev="psteitz" type="fix" issue="MATH-240" due-to="Christian Semrau">
+        Fixed error in factorial computation for 17 <= n <= 20.
+      </action>
+      <action dev="luc" type="update" >
+        Integration algorithms now can have both relative and absolute
+        accuracy settings.
+      </action>
+      <action dev="luc" type="add" issue="MATH-177" due-to="Gilles Sadowski">
+        Added a new univariate sub-package below the optimization package.
+      </action>
+      <action dev="luc" type="update" >
+        The analysis package has been reorganized with several sub-packages.
+      </action>
+      <action dev="luc" type="fix" issue="MATH-238" due-to="Christian Semrau">
+        Fixed an error in gcd computation for large values.
+      </action>
+      <action dev="luc" type="add" >
+        Added method to walk matrix entries with or without changing them in the
+        visitor design pattern sense. Three different orders can be used, row by row,
+        column by column or optimized order according to internal storage. Optimized
+        order should be preferred when no specific order is needed, because it will be
+        more cache efficient.
+      </action>
+      <action dev="luc" type="add" issue="MATH-215" due-to="Bernhard Grünewaldt">
+        Added Fast Hadamard Transform.
+      </action>
+      <action dev="luc" type="add" issue="MATH-236" due-to="Bernhard Grünewaldt">
+        Added nth root computation for complex numbers.
+      </action>
+      <action dev="luc" type="add" issue="MATH-230" due-to="Sujit Pal and Ismael Juma">
+        Added support for sparse matrix.
+      </action>
+      <action dev="luc" type="add" due-to="Ismael Juma">
+        Added an int/double hash map (OpenIntToDoubleHashMap) with much smaller
+        memory overhead than standard java.util.Map (open addressing and no boxing).
+      </action>
+      <action dev="luc" type="add" issue="MATH-152" due-to="Remi Arntzen">
+        Added support for multi-dimensional Fourier transform.
+      </action>
+      <action dev="luc" type="update" issue="MATH-218" >
+        The root solvers and the integrators now take the function to solve as a
+        parameter to the solve/integrate methods, thus allowing to reuse the same
+        solver/integrator for different functions.
+      </action>
+      <action dev="luc" type="add" issue="MATH-234" >
+        Added setter methods for rows and columns in matrices.
+      </action>
+      <action dev="luc" type="add" issue="MATH-232" >
+        Added Frobenius matrix norm.
+      </action>
+      <action dev="luc" type="add" issue="MATH-231" >
+        Added an abstract matrix implementation simplifying extension by users.
+      </action>
+      <action dev="luc" type="add" issue="MATH-178" due-to="Paul Cowan">
+        Added support for the Zipf distribution.
+      </action>
+      <action dev="psteitz" type="add" issue="MATH-212" due-to="Jason C. HandUber">
+        Added support for copying statistics. Changes to stats classes 
+        include copy constructor, static copy(-,-) and instance copy()
+        methods. Added copy() to UnivariateStatistic and StorelessUnivariateStatistic
+        interfaces. 
+      </action>
+      <action dev="luc" type="add" issue="MATH-229" due-to="Cyril Briquet">
+        Added a removal feature for observations in descriptive statistics.
+      </action>
+      <action dev="luc" type="add" >
+        Added a scalb method in MathUtils. This method is similar to the method
+        with same name added in java.lang.Math as of Java 6.
+      </action>
+      <action dev="brentworden" type="fix" issue="MATH-227" due-to="Joerg Henning">
+        Fixed F distribution inverse CDF computation for small denominator degrees of freedom.
+      </action>
+      <action dev="luc" type="fix" issue="MATH-226" due-to="Stuart Siegel">
+        Fixed an error in CorrelatedRandomVectorGenerator leading to a component of
+        the generated vector being constant.
+      </action>
+      <action dev="luc" type="fix" issue="MATH-223" due-to="John Mulcahy">
+        Greatly improved QR-decomposition speed using transposed matrices internally.
+      </action>
+      <action dev="luc" type="fix" due-to="Pascal Parraud">
+        Fixed an infinite loop encountered in some backward integration cases for ODE solvers.
+      </action>
+      <action dev="luc" type="add" issue="MATH-222" due-to="Ted Dunning">
+        Added beta distribution.
+      </action>
+      <action dev="luc" type="add" issue="MATH-222" due-to="Ted Dunning">
+        Added probability density functions computation for distributions for which
+        it is feasible.
+      </action>
+      <action dev="luc" type="fix" issue="MATH-221" due-to="Dieter Roth">
+        Changed the Complex.equals() method so that it considers +0 and -0 are equal,
+        as required by IEEE-754 standard.
+      </action>
+      <action dev="luc" type="add" issue="MATH-220">
+        Added JAMA-like interfaces for eigen/singular problems. The implementation
+        are based on the very quick dqd/dqds algorithms and some parts of the MRRR
+        algorithm. This leads to very fast and accurate solutions.
+      </action>
+      <action dev="luc" type="add" issue="MATH-220" >
+        Added JAMA-like interfaces for decomposition algorithms. These interfaces
+        decompose a matrix as a product of several other matrices with predefined
+        properties and shapes depending on the algorithm. These algorithms allow to
+        solve the equation A * X = B, either for an exact linear solution
+        (LU-decomposition, Cholesky decomposition) or an exact or least-squares
+        solution (QR-decomposition).
+      </action>
+      <action dev="luc" type="add" issue="MATH-219" due-to="Andrew Berry">
+        Added removeData methods for the SimpleRegression class. This allows
+        to support regression calculations across a sliding window of (time-based)
+        observations without having to recalculate for the entire window every time.
+      </action>
+      <action dev="luc" type="add" due-to="Andreas Rieger">
+        Support for one dimensional vectors has been added to the linear algebra
+        package with a RealVector interface, a RealVectorImpl default implementation
+        using a single double array to store elements and a RealVectorFormat for
+        input/output.
+      </action>
+      <action dev="psteitz" type="update" issue="MATH-217">
+        Changed OLS regression implementation added in MATH-203 to use
+        QR decomposition to solve the normal equations.
+      </action>
+      <action dev="luc" type="add">
+        New ODE integrators have been added: the explicit Adams-Bashforth and implicit
+        Adams-Moulton multistep methods. As the implementations of these methods are based
+        on Nordsieck vector rather than a traditional array of previous steps, they both
+        have been improved to handle adaptive stepsize. These methods provide the same rich
+        features has the existing ones: continuous output, step handlers, discrete events,
+        G-stop ...
+      </action>
+      <action dev="luc" type="fix" issue="MATH-214" >
+        Replaced size adjustment of all steps of fixed steps Runge-Kutta integrators by
+        a truncation of the last step only.
+      </action>
+      <action dev="luc" type="update">
+        The ODE integrators now support several step handlers at once, instead of just one.
+        This is more consistent with event handlers management.
+        The setStepHandler method has therefore been replaced by addStephandler, the
+        getStepHandler method has been replaced by getStepHandlers which returns a Collection
+        and a clearStepHandlers method has been added.
+      </action>
+      <action dev="luc" type="add">
+        All ODE integrators now support setting a maximal number of evaluations of differential
+        equations function. If this number is exceeded, an exception will be thrown during
+        integration. This can be used to prevent infinite loops if for example error control or
+        discrete events create a really large number of extremely small steps.
+      </action>
+      <action dev="luc" type="add">
+        All step interpolators for ODE integrators now provide interpolation for
+        both the state and its time derivatives. The interpolated derivatives are
+        the exact derivatives of the interpolated state, thus preserving consistency.
+        The general step handlers hence do not need to call the derivation function
+        anymore. The fixed step handlers also get the time derivative of the state
+        as an additional argument along with the state when they are called.
+      </action>
+      <action dev="luc" type="fix" issue="MATH-213" >
+        Changed return type for FirstOrderIntegrator.integrate() to double
+        in order to retrieve exact stop time. This allows to handle properly
+        integration interruption due to an EventHandler instance asking to
+        stop the integration when its associated event is triggered. The state
+        was already set to the current state at interruption time, but it was
+        difficult to get the corresponding time (it involved setting a step
+        handler monitoring the last step specially).
+      </action>
+      <action dev="luc" type="update" >
+        Events handlers in the ODE package now also provide the switching function
+        variation (increasing/decreasing) when an event occurs
+      </action>
+      <action dev="luc" type="update">
+        Clarified the ODE package by breaking in into several sub-packages and renaming
+        classes (SwitchingFunctions/EventHandler, SwitchingFunctionsHandler/CombinedEventsManager)
+      </action>
+      <action dev="luc" type="fix" issue="MATH-210" >
+        Changed return type for FirstOrderIntegrator.getSwitchingFunctions()
+        to a collection of SwitchingFunction instances. This better suits the
+        intended use of the method and fixes a visibility error since the
+        previous return type referenced the package private SwitchState class.
+      </action>
+      <action dev="luc" type="fix" issue="MATH-209" due-to="Thomas Chust">
+        Fixed dimension error on output vector for the operate method
+        in RealMatrixImpl and BigMatrixImpl classes.
+      </action>
+      <action dev="luc" type="update">
+        The FirstOrderDifferentialEquations, FirstOrderIntegrator and FixedStepHandler
+        interfaces now extends Serializable, allowing integrators, problems and
+        handlers to be embedded into users Serializable classes.
+      </action>
+      <action dev="luc" type="add">
+        Added several convenience methods and constants for Vector3D and Rotation.
+      </action>
+      <action dev="luc" type="update">
+        Replaced public no argument constructors with IDENTITY or ZERO
+        static instances for immutable classes Vector3D and Rotation.
+      </action>
+      <action dev="luc" type="fix">
+        Fixed inconsistencies in the naming scheme for static fields in
+        Vector3D and Rotation with respect to the overall library.
+      </action>
+      <action dev="luc" type="update" >
+        Greatly improved RealMatrixImpl and BigMatrixImpl performances,
+        both in terms of speed and in terms of temporary memory footprint.
+      </action>
+      <action dev="luc" type="add" issue="MATH-203" due-to="Mauro Talevi">
+        Added Mauro's patch to support multiple regression.
+      </action>
+      <action dev="luc" type="update" >
+        Starting with version 2.0 of the library, the minimal version of the Java
+        platform required to compile and use commons-math is Java 5. This version
+        is widely deployed now on many systems. It brings new important features
+        for specific mathematical developments, for example new functions (log10,
+        cbrt, ulp, signum, cosh, sinh, tanh, hypot, expm1, log1p), autoboxing,
+        MathContext or RoundingMode. It also brings important features for general
+        development, for example enums, generics or annotations.
+      </action>
+      <action dev="luc" type="add" >
+        Switching functions can now throw dedicated SwitchException from all their
+        method. At upper call level, the various ODE integrators handle these new
+        exceptions and wrap them into IntegratorException instances, hence the
+        integrators methods signature did not change.
+      </action>
+      <action dev="luc" type="add" issue="MATH-202">
+        Added the getSwitchingFunctions and clearSwitchingFunctions to the
+        FirstOrderIntegrator interface and all its implementations
+      </action>
+      <action dev="luc" type="update" >
+        Removed deprecated features. This includes the following changes. Factory-based
+        instantiation replaced by setter injection in 1.2 in several classes have been
+        removed. Protected fields in matrices implementations have been declared final
+        and private. Identity factory methods moved to MatrixUtils class have been
+        removed. Complex utilities methods that have been superseded by Complex instance
+        methods have been removed.
+      </action>
+      <action dev="luc" type="fix" issue="MATH-205" due-to="Roman Werpachowski">
+        Fixed formula in fast cosine transformer javadoc comments.
+      </action>
+      <action dev="brentworden" type="fix" issue="MATH-193" due-to="Michael Heuer and Sebb">
+        Javadoc and style fixes.
+      </action>
+      <action dev="luc" type="fix" issue="MATH-198" due-to="Frederick Salardi">
+        Added an error detection for missing imaginary character while parsing complex string
+      </action>
+      <action dev="luc" type="fix" issue="MATH-199" due-to="Mick">
+        Detect numerical problems in Q.R decomposition for Levenberg-Marquardt estimator
+        and report them appropriately
+      </action>
+      <action dev="luc" type="fix" issue="MATH-200" due-to="Plamen Petrov">
+        Fixed several crashes in getCovariances() and guessParametersErrors() in
+        AbstractEstimator when some parameters are bound. The methods now explicitly
+        give result only about unbound parameters.
+      </action>
+      <action dev="brentworden" type="fix" issue="MATH-201" due-to="Peter Wyngaard">
+        Fixed truncation error in t-test classes for large t values.
+      </action>
+      <action dev="brentworden" type="fix" issue="MATH-204" due-to="Mick">
+        Added root checks for the endpoints.
+      </action>
+    </release>
+    <release version="1.2" date="2008-02-24"
+    description="This release combines bug fixes and new features. Most notable
+    among the new features are the estimation, optimization, geometry and ode
+    packages added from the Mantissa library. Implementations of fast Fourier
+    transform, QR decomposition and several numerical integration algorithms
+    have also been added, along with enhancements and extensions to packages
+    included in Commons Math 1.1. This release is source and binary compatible
+    with earlier versions of Commons Math.">
+      <action dev="luc" type="fix">
+        Fixed numerous warnings in test code.
+      </action>
+      <action dev="luc" type="fix" issue="MATH-156" due-to="Tyler Ward">
+        Use the initial guess provided by the user in BrentSolver.solve(), thus
+        improving speed.
+      </action>
+      <action dev="luc" type="add">
+        Added the estimation, optimization, geometry and ode packages from the
+        Mantissa library.
+      </action>
+      <action dev="brentworden" type="fix" issue="MATH-18" due-to="Phil Steitz">
+        Made ComplexFormat format double values with a provided NumberFormat
+        instance instead of using the real part format for all values.
+      </action>
+      <action dev="psteitz" type="add" issue="MATH-120" due-to="Todd C. Parnell">
+        Added Pascal distribution implementation.
+      </action>
+      <action dev="psteitz" type="add" issue="MATH-148" due-to="Joni Salonen">
+        Added QR Decomposition.
+      </action>
+      <action dev="psteitz" type="fix" issue="MATH-60" due-to="Nhung Nnguyen">
+        Modified ProperFractionFormat to reject embedded minus signs.
+      </action>
+      <action dev="psteitz" type="fix" issue="MATH-151" due-to="Luc Maisonobe">
+        Added a nextAfter method in MathUtils to return the next
+        machine-representable number in a specified direction from a given
+        floating point number.  Used this to ensure that MathUtils.round does
+        not return incorrect results for numbers with bad IEEE754 
+        representations.
+      </action>
+      <action dev="psteitz" type="add" issue="MATH-140" due-to="Xiaogang Zhang">
+        Added Fast Fourier Transform implementation.
+      </action>
+      <action dev="psteitz" type="fix" issue="MATH-85" due-to="Mark Osborn, Luc Maisonobe">
+        Modified getSumSquaredErrors method in SimpleRegression to always
+        return a non-negative result.
+      </action>
+      <action dev="brentworden" type="fix" issue="MATH-153" due-to="Remi Arntzen">
+        Corrected nextInt and nextLong to handle wide value ranges.
+      </action>
+      <action dev="psteitz" type="fix" issue="MATH-166" due-to="Lukas Theussl">
+        Increased default precision of Gamma and Beta functions.
+      </action>
+      <action dev="psteitz" type="update" issue="MATH-158" due-to="Hasan Diwan">
+        Added log function to MathUtils.
+      </action>
+      <action dev="psteitz" type="update" issue="MATH-160" due-to="Matthias Hummel">
+        Added two sample (binned comparison) ChiSquare test.
+      </action>
+      <action dev="psteitz" type="fix" issue="MATH-167">
+        Modified NormalDistributionImpl.cumulativeProbablity to catch
+        MaxIterationsExceededException and return 0 or 1, resp. if the argument
+        is more than 20 standard deviations from the mean.
+      </action>
+      <action dev="brentworden" type="update" issue="MATH-170" due-to="David J. M. Karlsen">
+        Added SynchronizedDescriptiveStatistics class.
+      </action>
+      <action dev="brentworden" type="update" issue="MATH-154" due-to="Remi Arntzen">
+        Added addAndCheck, mulAndCheck, and subAndCheck MathUtils methods for
+        long integer arguments.
+      </action>
+      <action dev="psteitz" type="update" issue="MATH-171" due-to="Niall Pemberton">
+        Merged most functions from ComplexUtils into Complex class, added
+        static factory method to Complex.
+      </action>
+      <action dev="psteitz" type="update">
+        Deprecated abstract factory methods and made DescriptiveStatistics and
+        and SummaryStatistics concrete classes. Pushed implementations up
+        from DescriptiveStatisticsImpl, SummaryStatisticsImpl. Made
+        implementations of statistics configurable via setters.
+      </action>
+      <action dev="psteitz" type="fix" issue="MATH-174">
+        Changed Mean.evaluate() to use a two-pass algorithm, improving accuracy
+        by exploiting the the fact that this method has access to the full
+        array of data values.
+      </action>
+      <action dev="psteitz" type="fix" issue="MATH-175" due-to="Carl Anderson">
+        Added check and rescaling of expected counts to sum to sum of expected
+        counts if necessary in ChiSquare test.
+      </action>     
+      <action dev="luc" type="fix" issue="MATH-164">
+        Handle multiplication of Complex numbers with infinite parts specially.
+      </action>     
+      <action dev="luc" type="update" issue="MATH-176" due-to="Kazuhiro Koshino">
+        Add errors guessing to least-squares estimators.
+      </action>     
+      <action dev="luc" type="update" issue="MATH-179" due-to="Niall Pemberton">
+        Add tests for Fraction constructor using double parameter.
+      </action> 
+      <action dev="psteitz" type="add" issue="MATH-173" due-to="Bob MacCallum">
+        Added one-way ANOVA implementation.
+      </action>    
+      <action dev="luc" type="update" issue="MATH-181" due-to="Niall Pemberton">
+        Add Fraction constructor using max denominator value.
+      </action> 
+      <action dev="luc" type="fix" issue="MATH-182">
+        Add integer overflow checks in Fraction constructor using double parameter.
+      </action>     
+      <action dev="luc" type="fix" issue="MATH-185">
+        Throw EOFException when using empty files with ValueServer in replay and
+        digest modes.
+      </action>     
+      <action dev="luc" type="update" >
+        Added a equals and hash methods in MathUtils to check for double arrays
+      </action> 
+      <action dev="luc" type="add" >
+        Added an angle normalization method in MathUtils to force angles in some
+        user-defined interval
+      </action> 
+      <action dev="luc" type="add" >
+        Added vectorial covariance computation (either sample or population
+        covariance)
+      </action>
+      <action dev="luc" type="add" >
+        Added multivariate summary statistics.
+      </action>
+      <action dev="psteitz" type="update" issue="MATH-191">
+        Added getSumOfLogs method to SummaryStatistics and made SumOfLogs
+        instance used by GeometricMean configurable.
+      </action>
+      <action dev="psteitz" type="fix" issue="MATH-184" due-to="Yegor Bryukhov">
+        Fixed AbstractIntegerDistribution cumulativeProbablility(-,-)
+        to correctly handle double arguments.
+      </action>
+      <action dev="psteitz" type="update" issue="MATH-188" due-to="Sebastian Bazley">
+        Made numerator and denominator final in Fraction and
+        deprecated protected real and imaginary parts fields in Complex,
+        making Fraction immutable and preparing Complex to become fully
+        immutable in 2.0.
+      </action>
+    </release>
+    <release version="1.1" date="2005-12-17"  
+ description="This is a maintenance release containing bug fixes and enhancements.
+       All API changes are binary compatible with version 1.0. The enhancements 
+       include some new probability distributions, a Fraction class, new matrix
+       and numerical utilities, and a PRNG pluggability framework making it
+       possible to replace the JDK-supplied random number generator in
+       commons-math (and elsewhere) with alternative PRNG implementations.">
+      <action dev="psteitz" type="fix" issue="MATH-74">
+        Made NewtonSolver derivative field transient and implemented readObject to
+        initialize.
+      </action>
+      <action dev="psteitz" type="fix" issue="MATH-74">
+       Made sampleStats field private and changed getUpperBounds to return a fresh
+       copy in EmpiricalDistributionImpl.
+      </action>
+      <action dev="psteitz" type="update">
+        Added polar2Complex method to ComplexUtils to create Complex numbers
+        from polar representations.
+      </action>
+      <action dev="psteitz" type="fix" issue="MATH-49" due-to="Elliotte Harold">
+        Made all serialVersionUIDs private.
+      </action> 
+      <action dev="psteitz" type="fix" issue="MATH-5">
+        Improved documentation and test cases related to handling of infinite
+        and NaN values in Complex, ComplexUtils classes. 
+      </action> 
+      <action dev="psteitz" type="fix" issue="MATH-57" due-to="Mauro Talevi">
+        Fixed incorrect NaN handling in o.a.m.s.d.rank.Min, Max
+      </action>
+      <action dev="psteitz" type="fix" issue="MATH-74">
+       Changed RealMatrixImpl.equals to use Double.doubleToLongBits to compare
+       corresponding matrix entries.
+      </action>
+      <action dev="psteitz" type="fix" issue="MATH-74">
+       Eliminated floating point equals comparison in Percentile.evaluate.
+      </action>
+      <action dev="psteitz" type="fix" issue="MATH-74">
+       Eliminated unnecessary assignment statements in Skewness.getResult
+       method.
+      </action>
+      <action dev="psteitz" type="fix" issue="MATH-74">
+       Synchronized getters in ResizeableDoubleArray.
+      </action>
+      <action dev="psteitz" type="fix" issue="MATH-74">
+       Eliminated unnecessary assignment statement in BisectionSolver.solve
+       method.
+      </action>
+      <action dev="psteitz" type="fix" issue="MATH-74">
+       Implemented hashCode in the Complex class and changed the semantics of
+       equals to make all instances with real or imaginary part NaN equal.
+      </action>
+      <action dev="psteitz" type="fix" issue="MATH-12" due-to="Keith McDonald">
+        Fixed bin index overflow problem in EmpiricalDistributionImpl.
+      </action>
+      <action dev="brentworden" type="fix" issue="MATH-108" due-to="Xiaogang Zhang">
+        Added protection against numerical overflow and underflow in the
+        isBracketing method.
+      </action>
+      <action dev="brentworden" type="fix" issue="MATH-47" due-to="Nikhil Gupte">
+        Fixed division by zero error in rounding methods.
+      </action>
+      <action dev="brentworden" type="fix" issue="MATH-100" due-to="Mike Hu">
+        Added upper tail cumulative probability method to HypergeometricDistributionImpl.
+      </action>
+      <action dev="brentworden" type="fix" issue="MATH-22" due-to="Xiaogang Zhang">
+        Added better handling of numerical overflow and division by zero in
+        Complex calculations.
+      </action>
+      <action dev="brentworden" type="fix" issue="MATH-92" due-to="Mikael Weigelt">
+        Changed ContinuedFraction to better handle infinite convergents that
+        resulted in divergent continued fraction evaluations.
+      </action>
+      <action dev="brentworden" type="fix" issue="MATH-32"  due-to="Srinivas Vemury">
+        Changed rounding methods to not rely on BigDecimal conversions which
+        was causing numerical error.
+      </action>
+      <action dev="psteitz" type="fix" issue="MATH-3"  due-to="Jörg Weimar">
+        Changed Fraction(double) to correctly handle near-integral arguments.
+      </action>
+      <action dev="psteitz" type="fix" issue="MATH-143"  due-to="Jörg Weimar">
+        Changed lcm to throw ArithmeticException (instead of returning bogus
+        value) if the result is too large to store as an integer.
+      </action>
+      <action dev="psteitz" type="fix" issue="MATH-70"  due-to="Mary Ellen Foster">
+        Added factories for TTest, ChiSquareTest and TestUtils class with
+        static methods to create instances and execute tests.
+      </action>
+      <action dev="psteitz" type="update" issue="MATH-95"  due-to="Paul Field">
+        Eliminated repeated endpoint function evalutations in BrentSolver, SecantSolver.
+      </action>
+      <action dev="psteitz" type="update" issue="MATH-137"  due-to="Rodrigo di Lorenzo Lopes">
+        Added setSubMatrix methods to RealMatrixImpl, BigMatrixImpl.  To
+        Preserve backward compatibility with version 1.0, these methods were
+        not added to the RealMatrix, BigMatrix interfaces.
+      </action>
+      <action dev="psteitz" type="update">
+        Added createXIdentityMatrix methods to MatrixUtils and deprecated
+        getIdentity methods in RealMatrixImpl, BigMatrixImpl.
+        Modified RealMatrixImpl, BigMatrixImpl constructors to throw
+        IllegalArgumentExceptions instead of ArrayIndexOutOfBounds when
+        dimension arguments are not positive.
+      </action>
+      <action dev="psteitz" type="update">
+        Made PRNG pluggable for classes in the random package.  Added
+        RandomGenerator interface extracted from java.util.random and abstract
+        implementation, AbstractRandomGenerator providing default
+        implementations of methods based on nextDouble().  Added a constructor
+        taking a RandomGenerator as an argument to RandomDataImpl.  Changed
+        ValueServer to use a RandomData in its constructor.  Changes to 
+        1.0 classes should be backward compatible (including serialization).
+      </action>
+      <action dev="psteitz" type="update" due-to="C. Scott Ananian">
+        Added utility methods for overflow-checked integer arithmetic and
+        improved gcd method in MathUtils.
+      </action>
+       <action dev="psteitz" type="fix" issue="MATH-79" due-to="Gilles Gaillard">
+        Fixed error in TTestImpl.homoscedasticTtest.  Implementation was
+        incorrectly using heteroscedastic t statistic.  Also improved
+        sensitivity of test cases.
+      </action>
+      <action dev="psteitz" type="fix" issue="MATH-44" due-to="Gilles Gaillard">
+        Fixed javadoc errors. One-sided t-test significance adjustment was
+        reversed in javadoc for boolean-valued test methods.
+      </action>
+      <action dev="brentworden" type="fix" due-to="Ben Litchfield">
+        Fixed bug in PolynomialSplineFunction to allow evaluation of the
+        function at the last knot point.
+      </action>
+      <action dev="brentworden" type="add">
+        Added Weibull distribution implementation.
+      </action>
+      <action dev="brentworden" type="add">
+        Added Cauchy distribution implementation.
+      </action>
+      <action dev="brentworden" type="add">
+        Added convenience methods for rounding.
+      </action>
+      <action dev="brentworden" type="add" due-to="C. Scott Ananian">
+        Added Fraction class based on commons-lang implementation.  With the
+        fraction class, FractionFormat and ProperFractionFormat classes were
+        added to provide fraction formatting and parsing.
+      </action>
+    </release>  
+    <release version="1.0" date="2004-12-06"  
+       description="Apache Commons Math 1.0 - Initial Release">
+    </release>    
+  </body>
+</document>
diff --git a/src/site/xdoc/developers.xml b/src/site/xdoc/developers.xml
new file mode 100644
index 0000000..17f07b1
--- /dev/null
+++ b/src/site/xdoc/developers.xml
@@ -0,0 +1,311 @@
+<?xml version="1.0"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT 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: 804554 $ $Date: 2009-08-16 04:52:32 +0200 (dim. 16 août 2009) $ -->
+<document>
+ <properties>
+  <title>Developers Guide</title>
+  <author email="rdonkin at apache.org">Robert Burrell Donkin</author>
+ </properties>
+
+ <body>
+  <section name="Aims">
+   <p>
+    Creating and maintaining a mathematical and statistical library that is
+    accurate requires a greater degree of communication than might be the
+    case for other components. It is important that developers follow
+    guidelines laid down by the community to ensure that the code they create
+    can be successfully maintained by others.
+    </p>
+   </section>
+   <section name='Guidelines'>
+   <p>
+   Developers are asked to comply with the following development guidelines.
+   Code that does not comply with the guidelines including the word <i>must</i>
+   will not be committed.  Our aim will be to fix all of the exceptions to the
+   "<i>should</i>" guidelines prior to a release.
+   </p>
+   <subsection name="Contributing">
+    <p><strong>Getting Started</strong></p>
+    <p>
+    <ol>
+        <li>Start by reviewing the overall objectives stated in the
+            <a href="proposal.html">proposal</a> upon which the project is 
+            founded.
+        </li>
+        <li>Download the commons math source code.  Follow the instructions
+        under the heading "Anonymous Subversion" on the
+        <a href="http://www.apache.org/dev/version-control.html">Apache version
+         control page</a>  (Also have a look at the 
+        <a href="http://wiki.apache.org/commons/UsingSVN">Commons wiki
+        svn page </a>) to check out the commons math code base from Subversion.
+        The svn url for the current development sources of commons-math
+        is 
+<source>https://svn.apache.org/repos/asf/commons/proper/math/trunk</source>
+        </li>
+        <li>Like most commons components, commons-math uses Apache Maven as our
+            build tool. We now use Maven 2 as our primary build platform (what
+            we use to cut releases, for continuous integration builds, and for
+            development).  The sources can also be built using Ant (a working 
+            Ant build.xml is included in the top level project directory). 
+            To build commons math using Maven 2, you can follow the instructions for
+            <a href="http://maven.apache.org/run-maven/index.html">Building a
+            project with Maven 2</a>. Launch Maven from the top-level directory
+            in the checkout of commons-math trunk. No special setup is required,
+            except that currently to build the site (i.e. to execute Maven's
+            "site" goal), you may need to increase the default memory allocation
+            (e.g. <code>export MAVEN_OPTS=-Xmx512m</code>) before launching
+            Maven.
+        </li>
+        <li>Have a look at the new features that users and developers have requested
+            on the <a href="http://wiki.apache.org/commons/MathWishList">
+            Math Wish List Wiki Page.</a>
+        </li>
+        <li>Be sure to join the commons-dev and commons-user 
+            <a href="mail-lists.html">
+            email lists</a> and use them appropriately (make sure the string
+            "[math]" starts the Subject line of all your postings).
+            Make any proposals here where the group can comment on them.
+        </li>
+        <li>
+            <a href="https://issues.apache.org/jira/secure/Signup!default.jspa">
+            Setup an account on JIRA</a> and use it to submit patches and
+            identify bugs. Read the 
+            <a href="http://issues.apache.org/bugwritinghelp.html">
+            directions</a> for submitting bugs and search the database to
+            determine if an issue exists or has already been dealt with.        
+            <p>
+              See the <a href="http://commons.apache.org/math/issue-tracking.html">
+              Commons Math Issue Tracking Page</a> for more information on how to
+              search for or submit bugs or enhancement requests.
+            </p>     
+        <li>
+          Generating patches: The requested format for generating patches is
+          the Unified Diff format, which can be easily generated using the svn
+          client or various IDEs.
+<source>svn diff  > patch </source>
+        </li>
+      </li>
+    </ol>
+    </p>
+    <p><strong>Contributing ideas and code</strong></p>
+    <p>
+     Follow the steps below when making suggestions for additions or
+     enhancements to commons-math. This will make it easier for the community
+     to comment on your ideas and for the committers to keep track of them. 
+     Thanks in advance!
+     <ol>
+       <li>Start with a post to the commons-dev mailing list, with [math] at
+       the beginning of the subject line, followed by a good, short title
+       describing the new feature or enhancement.  For example, "[math]
+       Principal Components Analysis." The body of the post should include each
+       of the following items (but be <strong>as brief as possible</strong>):
+       <ul>
+         <li>A concise description of the new feature / enhancement</li>
+         <li>References to definitions and algorithms. Using standard
+         definitions and algorithms makes communication much easier and will
+         greatly increase the chances that we will accept the code / idea</li>
+         <li>Some indication of why the addition / enhancement is practically
+         useful</li>
+       </ul></li>
+       <li>Assuming a generally favorable response to the idea on commons-dev,
+       the next step is to add an entry to the 
+       <a href="http://wiki.apache.org/commons/MathWishList">Math Wish
+       List</a> corresponding to the idea.  Include a reference to the
+       discussion thread and, for substantial enhancements, a new Wiki page
+       named using the enhancement / addition name, e.g. 
+       "PrincipalComponentsAnalysis." We can then us this page to lay out the
+       development plan and track progress and decisions related to the
+       feature.</li>
+       <li>Create a JIRA ticket using the the feature title as the short
+       description. Incorporate feedback from the initial posting in the
+       description. Add a reference to the JIRA ticket to the WishList entry.
+       </li>
+       <li>Submit code as attachments to the JIRA ticket.  Please use one
+       ticket for each feature, adding multiple patches to the ticket
+       as necessary.  Use the svn diff command to generate your patches as
+       diffs.  Please do not submit modified copies of existing java files. Be
+       patient (but not <strong>too</strong> patient) with  committers reviewing
+       patches. Post a *nudge* message to commons-dev with a reference to the
+       ticket if a patch goes more than a few days with no comment or commit.
+       </li>
+      </ol>
+    </p>   
+   </subsection>
+   <subsection name='Coding Style'>
+    <p>
+     Commons-math follows <a href="http://java.sun.com/docs/codeconv/">Code
+     Conventions for the Java Programming Language</a>. As part of the maven
+     build process, style checking is performed using the Checkstyle plugin,
+     using the properties specified in <code>checkstyle.xml</code>.
+     Committed code <i>should</i> generate no Checkstyle errors.  One thing
+     that Checkstyle will complain about is tabs included in the source code.
+     Please make sure to set your IDE or editor to use spaces instead of tabs.
+    </p>
+    <p>
+      Committers should make sure that svn properties are correctly set on
+      files added to the repository.  See the section on Committer Subversion
+      Access on the <a href="http://www.apache.org/dev/version-control.html">
+      Apache Source Code Repositories</a> page.
+    </p>
+   </subsection>
+   <subsection name='Documentation'>
+    <ul>
+     <li>
+      Committed code <i>must</i> include full javadoc.</li>
+     <li>
+      All component contracts <i>must</i> be fully specified in the javadoc class,
+      interface or method comments, including specification of acceptable ranges
+      of values, exceptions or special return values.</li>
+     <li>
+      External references or full statements of definitions for all mathematical
+      terms used in component documentation <i>must</i> be provided.</li>
+     <li>
+      Implementations <i>should</i> use standard algorithms and
+      references or full descriptions of all algorithms <i>should</i> be 
+      provided.</li>
+     <li>
+      Additions and enhancements <i>should</i> include updates to the User
+      Guide.</li>
+    </ul>
+   </subsection>
+   <subsection name='Unit Tests'>
+    <ul>
+     <li>
+      Committed code <i>must</i> include unit tests.</li>
+     <li>
+      Unit tests <i>should</i> provide full path coverage. </li>
+     <li>
+      Unit tests <i>should</i> verify all boundary conditions specified in
+      interface contracts, including verification that exceptions are thrown or
+      special values (e.g. Double.NaN, Double.Infinity) are returned as
+      expected. </li>
+    </ul>
+   </subsection>
+   <subsection name='Licensing and copyright'>
+    <ul>
+     <li>
+      All new source file submissions <i>must</i> include the Apache Software
+      License in a comment that begins the file </li>
+     <li>
+      All contributions must comply with the terms of the Apache 
+      <a href="http://www.apache.org/licenses/cla.pdf">Contributor License
+      Agreement (CLA)</a></li>
+     <li>
+       Patches <i>must</i> be accompanied by a clear reference to a "source"
+       - if code has been "ported" from another language, clearly state the
+       source of the original implementation.  If the "expression" of a given
+       algorithm is derivative, please note the original source (textbook,
+       paper, etc.).</li>
+     <li>
+       References to source materials covered by restrictive proprietary
+       licenses should be avoided.  In particular, contributions should not
+       implement or include references to algorithms in 
+       <a href="http://www.nr.com/">Numerical Recipes (NR)</a>.
+       Any questions about copyright or patent issues should be raised on
+       the commons-dev mailing list before contributing or committing code.
+      </li>
+    </ul>
+   </subsection>
+  </section>
+  <section name='Recommended Readings'>
+   <p>
+    Here is a list of relevant materials.  Much of the discussion surrounding
+    the development of this component will refer to the various sources
+    listed below, and frequently the Javadoc for a particular class or
+    interface will link to a definition contained in these documents.
+   </p>
+   <subsection name='Recommended Readings'>
+    <dl>
+     <dt>Concerning floating point arithmetic.</dt>
+     <dd>
+      <a href="http://www.validlab.com/goldberg/paper.ps">
+       http://www.validlab.com/goldberg/paper.ps
+      </a><br/>
+      <a href="http://www.cs.berkeley.edu/~wkahan/ieee754status/ieee754.ps">
+       http://www.cs.berkeley.edu/~wkahan/ieee754status/ieee754.ps
+      </a><br/>
+      <a href="http://www.cs.berkeley.edu/~wkahan/JAVAhurt.pdf">
+       http://www.cs.berkeley.edu/~wkahan/JAVAhurt.pdf
+      </a><br/>
+     </dd>
+     <dt>Numerical analysis</dt>
+     <dd>
+      <a href="http://www.mathcom.com/corpdir/techinfo.mdir/scifaq/index.html">
+        Scientific Computing FAQ @ Mathcom
+      </a><br/>
+      <a href="http://www.ma.man.ac.uk/~higham/asna/asna2.pdf">
+       Bibliography of accuracy and stability of numerical algorithms
+      </a><br/>
+       <a href="http://tonic.physics.sunysb.edu/docs/num_meth.html">
+       SUNY Stony Brook numerical methods page
+      </a><br/>
+       <a href="http://epubs.siam.org/sam-bin/dbq/toclist/SINUM">
+       SIAM Journal of Numerical Analysis Online
+      </a><br/>
+     </dd>
+     <dt>Probability and statistics</dt>
+     <dd>
+      <a href="http://lib.stat.cmu.edu/">
+       Statlib at CMU
+      </a><br/>
+      <a href="http://www.itl.nist.gov/div898/handbook/">
+       NIST Engineering Statistics Handbook
+      </a><br/>
+      <a href="http://www.psychstat.smsu.edu/sbk00.htm">
+       Online Introductory Statistics (David W. Stockburger)
+      </a><br/>
+       <a href="http://www.jstatsoft.org/">
+       Online Journal of Statistical Software
+      </a><br/>
+     </dd>
+    </dl>
+   </subsection>
+   <subsection name='Javadoc comment resources'>
+    <dl>
+     <dt>References for mathematical definitions.</dt>
+     <dd>
+      <a href="http://rd11.web.cern.ch/RD11/rkb/titleA.html">
+       http://rd11.web.cern.ch/RD11/rkb/titleA.html
+      </a><br/>
+      <a href="http://mathworld.wolfram.com/">
+       http://mathworld.wolfram.com/
+      </a><br/>
+      <a href="http://www.itl.nist.gov/div898/handbook">
+       http://www.itl.nist.gov/div898/handbook
+      </a><br/>
+      <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><br/>
+     </dd>
+    </dl>
+   </subsection>
+   <subsection name='XML'>
+    <dl>
+     <dt>XML related resources.</dt>
+     <dd>
+      <a href="http://www.openmath.org">
+       http://www.openmath.org
+      </a><br/>
+     </dd>
+    </dl>
+   </subsection>
+  </section>
+ </body>
+</document>
diff --git a/src/site/xdoc/download_math.xml b/src/site/xdoc/download_math.xml
new file mode 100644
index 0000000..9edec91
--- /dev/null
+++ b/src/site/xdoc/download_math.xml
@@ -0,0 +1,138 @@
+<?xml version="1.0"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<!--
+ +======================================================================+
+ |****                                                              ****|
+ |****      THIS FILE IS GENERATED BY THE COMMONS BUILD PLUGIN      ****|
+ |****                    DO NOT EDIT DIRECTLY                      ****|
+ |****                                                              ****|
+ +======================================================================+
+ | TEMPLATE FILE: download-page-template.xml                            |
+ | commons-build-plugin/trunk/src/main/resources/commons-xdoc-templates |
+ +======================================================================+
+ |                                                                      |
+ | 1) Re-generate using: mvn commons:download-page                      |
+ |                                                                      |
+ | 2) Set the following properties in the component's pom:              |
+ |    - commons.componentid (required, alphabetic, lower case)          |
+ |    - commons.release.version (required)                              |
+ |    - commons.binary.suffix (optional)                                |
+ |      (defaults to "-bin", set to "" for pre-maven2 releases)         |
+ |                                                                      |
+ | 3) Example Properties                                                |
+ |                                                                      |
+ |  <properties>                                                        |
+ |    <commons.componentid>math</commons.componentid>                   |
+ |    <commons.release.version>1.2</commons.release.version>            |
+ |  </properties>                                                       |
+ |                                                                      |
+ +======================================================================+
+-->
+<document>
+  <properties>
+    <title>Download Commons Math</title>
+    <author email="dev at commons.apache.org">Commons Documentation Team</author>
+  </properties>
+  <body>
+    <section name="Download Commons Math">
+    <subsection name="Using a Mirror">
+      <p>
+        We recommend you use a mirror to download our release
+        builds, but you <strong>must</strong> verify the integrity of
+        the downloaded files using signatures downloaded from our main 
+        distribution directories. Recent releases (48 hours) may not yet
+        be available from the mirrors.
+      </p>
+
+      <p>
+        You are currently using <b>[preferred]</b>.  If you
+        encounter a problem with this mirror, please select another
+        mirror.  If all mirrors are failing, there are <i>backup</i>
+        mirrors (at the end of the mirrors list) that should be
+        available.
+        <br></br>
+        [if-any logo]<a href="[link]"><img align="right" src="[logo]" border="0"></img></a>[end]
+      </p>
+
+      <form action="[location]" method="get" id="SelectMirror">
+        <p>
+          Other mirrors: 
+          <select name="Preferred">
+          [if-any http]
+            [for http]<option value="[http]">[http]</option>[end]
+          [end]
+          [if-any ftp]
+            [for ftp]<option value="[ftp]">[ftp]</option>[end]
+          [end]
+          [if-any backup]
+            [for backup]<option value="[backup]">[backup] (backup)</option>[end]
+          [end]
+          </select>
+          <input type="submit" value="Change"></input>
+        </p>
+      </form>
+
+      <p>
+        The <a href="http://www.apache.org/dist/commons/KEYS">KEYS</a>
+        link links to the code signing keys used to sign the product.
+        The <code>PGP</code> link downloads the OpenPGP compatible signature from our main site. 
+        The <code>MD5</code> link downloads the checksum from the main site.
+      </p>
+    </subsection>
+    </section>
+    <section name="Commons Math 2.2 ">
+      <subsection name="Binaries">
+        <table>
+          <tr>
+              <td><a href="[preferred]/commons/math/binaries/commons-math-2.2.tar.gz">commons-math-2.2.tar.gz</a></td>
+              <td><a href="http://www.apache.org/dist/commons/math/binaries/commons-math-2.2.tar.gz.md5">md5</a></td>
+              <td><a href="http://www.apache.org/dist/commons/math/binaries/commons-math-2.2.tar.gz.asc">pgp</a></td>
+          </tr>
+          <tr>
+              <td><a href="[preferred]/commons/math/binaries/commons-math-2.2.zip">commons-math-2.2.zip</a></td>
+              <td><a href="http://www.apache.org/dist/commons/math/binaries/commons-math-2.2.zip.md5">md5</a></td>
+              <td><a href="http://www.apache.org/dist/commons/math/binaries/commons-math-2.2.zip.asc">pgp</a></td>
+          </tr>
+        </table>
+      </subsection>
+      <subsection name="Source">
+        <table>
+          <tr>
+              <td><a href="[preferred]/commons/math/source/commons-math-2.2-src.tar.gz">commons-math-2.2-src.tar.gz</a></td>
+              <td><a href="http://www.apache.org/dist/commons/math/source/commons-math-2.2-src.tar.gz.md5">md5</a></td>
+              <td><a href="http://www.apache.org/dist/commons/math/source/commons-math-2.2-src.tar.gz.asc">pgp</a></td>
+          </tr>
+          <tr>
+              <td><a href="[preferred]/commons/math/source/commons-math-2.2-src.zip">commons-math-2.2-src.zip</a></td>
+              <td><a href="http://www.apache.org/dist/commons/math/source/commons-math-2.2-src.zip.md5">md5</a></td>
+              <td><a href="http://www.apache.org/dist/commons/math/source/commons-math-2.2-src.zip.asc">pgp</a></td>
+          </tr>
+        </table>
+      </subsection>
+    </section>
+    <section name="Archives">
+        <p>
+          Older releases can be obtained from the archives.
+        </p>
+        <ul>
+          <li class="download"><a href="[preferred]/commons/math/">browse download area</a></li>
+          <li><a href="http://archive.apache.org/dist/commons/math/">archives...</a></li>
+        </ul>
+    </section>
+  </body>
+</document>
diff --git a/src/site/xdoc/index.xml b/src/site/xdoc/index.xml
new file mode 100644
index 0000000..3368cde
--- /dev/null
+++ b/src/site/xdoc/index.xml
@@ -0,0 +1,86 @@
+<?xml version="1.0"?>
+
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT 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: 923339 $ $Date: 2010-03-15 18:04:53 +0100 (lun. 15 mars 2010) $ -->
+<document>
+
+ <properties>
+  <title>Commons-Math: The Apache Commons Mathematics Library</title>
+ </properties>
+
+ <body>
+
+  <section name="Commons-Math: The Apache Commons Mathematics Library" href="summary">
+   <p>
+    Commons Math is a library of lightweight, self-contained
+    mathematics and statistics components addressing the most common
+    problems not available in the Java programming language or Commons
+    Lang.
+   </p>
+   <p>
+    Guiding principles:
+    <ol>
+     <li>
+      Real-world application use cases determine development
+      priority.
+     </li>
+     <li>
+      This package emphasizes small, easily integrated components
+      rather than large libraries with complex dependencies and
+      configurations.
+     </li>
+     <li>
+      All algorithms are fully documented and follow generally
+      accepted best practices.
+     </li>
+     <li>
+      In situations where multiple standard algorithms exist, a
+      Strategy pattern is used to support multiple
+      implementations.
+     </li>
+     <li>
+      Limited dependencies. No external dependencies beyond Commons
+      components and the core Java platform (at least Java 1.3 up to
+      version 1.2 of the library, at least Java 5 starting with version
+      2.0 of the library).
+     </li>
+    </ol>
+   </p>
+  </section>
+  <section name="Download Math">
+   <subsection name="Releases">
+    <p>
+      Download the 
+      <a href="http://commons.apache.org/math/download_math.cgi">
+      Latest Release</a> of Commons Math.
+    </p>
+   </subsection>
+   <!--
+   <subsection name="Nightly Builds">
+    <p>
+     <a href="http://people.apache.org/builds/commons/nightly/commons-math/">
+     Nightly builds</a> are built once a day from the current SVN HEAD.
+     This is (nearly) the latest code and so should be treated with
+     caution!
+    </p>
+   </subsection>
+   -->
+  </section>
+ </body>
+</document>
diff --git a/src/site/xdoc/issue-tracking.xml b/src/site/xdoc/issue-tracking.xml
new file mode 100644
index 0000000..237c4c3
--- /dev/null
+++ b/src/site/xdoc/issue-tracking.xml
@@ -0,0 +1,102 @@
+<?xml version="1.0"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<!--
+ +======================================================================+
+ |****                                                              ****|
+ |****      THIS FILE IS GENERATED BY THE COMMONS BUILD PLUGIN      ****|
+ |****                    DO NOT EDIT DIRECTLY                      ****|
+ |****                                                              ****|
+ +======================================================================+
+ | TEMPLATE FILE: issue-tracking-template.xml                           |
+ | commons-build-plugin/trunk/src/main/resources/commons-xdoc-templates |
+ +======================================================================+
+ |                                                                      |
+ | 1) Re-generate using: mvn commons:jira-page                          |
+ |                                                                      |
+ | 2) Set the following properties in the component's pom:              |
+ |    - commons.jira.id  (required, alphabetic, upper case)             |
+ |    - commons.jira.pid (required, numeric)                            |
+ |                                                                      |
+ | 3) Example Properties                                                |
+ |                                                                      |
+ |  <properties>                                                        |
+ |    <commons.jira.id>MATH</commons.jira.id>                           |
+ |    <commons.jira.pid>12310485</commons.jira.pid>                     |
+ |  </properties>                                                       |
+ |                                                                      |
+ +======================================================================+
+-->
+<document>
+  <properties>
+    <title>Commons Math Issue tracking</title>
+    <author email="dev at commons.apache.org">Commons Documentation Team</author>
+  </properties>
+  <body>
+
+    <section name="Commons Math Issue tracking">
+      <p>
+      Commons Math uses <a href="http://issues.apache.org/jira/">ASF JIRA</a> for tracking issues.
+      See the <a href="http://issues.apache.org/jira/browse/MATH">Commons Math JIRA project page</a>.
+      </p>
+
+      <p>
+      To use JIRA you may need to <a href="http://issues.apache.org/jira/secure/Signup!default.jspa">create an account</a>
+      (if you have previously created/updated Commons issues using Bugzilla an account will have been automatically
+      created and you can use the <a href="http://issues.apache.org/jira/secure/ForgotPassword!default.jspa">Forgot Password</a>
+      page to get a new password).
+      </p>
+
+      <p>
+      If you would like to report a bug, or raise an enhancement request with
+      Commons Math please do the following:
+      <ol>
+        <li><a href="http://issues.apache.org/jira/secure/IssueNavigator.jspa?reset=true&pid=12310485&sorter/field=issuekey&sorter/order=DESC&status=1&status=3&status=4">Search existing open bugs</a>.
+            If you find your issue listed then please add a comment with your details.</li>
+        <li><a href="mail-lists.html">Search the mailing list archive(s)</a>.
+            You may find your issue or idea has already been discussed.</li>
+        <li>Decide if your issue is a bug or an enhancement.</li>
+        <li>Submit either a <a href="http://issues.apache.org/jira/secure/CreateIssueDetails!init.jspa?pid=12310485&issuetype=1&priority=4&assignee=-1">bug report</a>
+            or <a href="http://issues.apache.org/jira/secure/CreateIssueDetails!init.jspa?pid=12310485&issuetype=4&priority=4&assignee=-1">enhancement request</a>.</li>
+      </ol>
+      </p>
+
+      <p>
+      Please also remember these points:
+      <ul>
+        <li>the more information you provide, the better we can help you</li>
+        <li>test cases are vital, particularly for any proposed enhancements</li>
+        <li>the developers of Commons Math are all unpaid volunteers</li>
+      </ul>
+      </p>
+
+      <p>
+      For more information on subversion and creating patches see the
+      <a href="http://www.apache.org/dev/contributors.html">Apache Contributors Guide</a>.
+      </p>
+
+      <p>
+      You may also find these links useful:
+      <ul>
+        <li><a href="http://issues.apache.org/jira/secure/IssueNavigator.jspa?reset=true&pid=12310485&sorter/field=issuekey&sorter/order=DESC&status=1&status=3&status=4">All Open Commons Math bugs</a></li>
+        <li><a href="http://issues.apache.org/jira/secure/IssueNavigator.jspa?reset=true&pid=12310485&sorter/field=issuekey&sorter/order=DESC&status=5&status=6">All Resolved Commons Math bugs</a></li>
+        <li><a href="http://issues.apache.org/jira/secure/IssueNavigator.jspa?reset=true&pid=12310485&sorter/field=issuekey&sorter/order=DESC">All Commons Math bugs</a></li>
+      </ul>
+      </p>
+    </section>
+  </body>
+</document>
diff --git a/src/site/xdoc/mail-lists.xml b/src/site/xdoc/mail-lists.xml
new file mode 100644
index 0000000..6649d6d
--- /dev/null
+++ b/src/site/xdoc/mail-lists.xml
@@ -0,0 +1,202 @@
+<?xml version="1.0"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+<!--
+ +======================================================================+
+ |****                                                              ****|
+ |****      THIS FILE IS GENERATED BY THE COMMONS BUILD PLUGIN      ****|
+ |****                    DO NOT EDIT DIRECTLY                      ****|
+ |****                                                              ****|
+ +======================================================================+
+ | TEMPLATE FILE: mail-lists-template.xml                               |
+ | commons-build-plugin/trunk/src/main/resources/commons-xdoc-templates |
+ +======================================================================+
+ |                                                                      |
+ | 1) Re-generate using: mvn commons:mail-page                          |
+ |                                                                      |
+ | 2) Set the following properties in the component's pom:              |
+ |    - commons.componentid (required, alphabetic, lower case)          |
+ |                                                                      |
+ | 3) Example Properties                                                |
+ |                                                                      |
+ |  <properties>                                                        |
+ |    <commons.componentid>math</commons.componentid>                   |
+ |  </properties>                                                       |
+ |                                                                      |
+ +======================================================================+
+-->
+<document>
+  <properties>
+    <title>Commons Math Mailing Lists</title>
+    <author email="dev at commons.apache.org">Commons Documentation Team</author>
+  </properties>
+  <body>
+
+    <section name="Overview">
+      <p>
+        <a href="index.html">Commons Math</a> shares mailing lists with all the other 
+        <a href="http://commons.apache.org/components.html">Commons Components</a>.
+        To make it easier for people to only read messages related to components they are interested in,
+        the convention in Commons is to prefix the subject line of messages with the component's name,
+        for example:
+        <ul>
+          <li>[math] Problem with the ...</li>
+        </ul>
+      </p>
+      <p>
+        Questions related to the usage of Commons Math should be posted to the
+        <a href="http://mail-archives.apache.org/mod_mbox/commons-user/">User List</a>.
+        <br />
+        The <a href="http://mail-archives.apache.org/mod_mbox/commons-dev/">Developer List</a>
+        is for questions and discussion related to the development of Commons Math.
+        <br />
+        Please do not cross-post; developers are also subscribed to the user list.
+      </p>
+      <p>
+        <strong>Note:</strong> please don't send patches or attachments to any of the mailing lists.
+        Patches are best handled via the <a href="issue-tracking.html">Issue Tracking</a> system. 
+        Otherwise, please upload the file to a public server and include the URL in the mail. 
+      </p>
+    </section>
+
+    <section name="Commons Math Mailing Lists">
+      <p>
+        <strong>Please prefix the subject line of any messages for <a href="index.html">Commons Math</a>
+        with <i>[math]</i></strong> - <i>thanks!</i>
+        <br />
+        <br />
+      </p>
+
+      <table>
+        <tr>
+          <th>Name</th>
+          <th>Subscribe</th>
+          <th>Unsubscribe</th>
+          <th>Post</th>
+          <th>Archive</th>
+          <th>Other Archives</th>
+        </tr>
+
+
+        <tr>
+          <td>
+            <strong>Commons User List</strong>
+            <br /><br />
+            Questions on using Commons Math.
+            <br /><br />
+          </td>
+          <td><a href="mailto:user-subscribe at commons.apache.org">Subscribe</a></td>
+          <td><a href="mailto:user-unsubscribe at commons.apache.org">Unsubscribe</a></td>
+          <td><a href="mailto:user at commons.apache.org?subject=[math]">Post</a></td>
+          <td><a href="http://mail-archives.apache.org/mod_mbox/commons-user/">mail-archives.apache.org</a></td>
+          <td><a href="http://markmail.org/list/org.apache.commons.users/">markmail.org</a><br />
+              <a href="http://www.mail-archive.com/user@commons.apache.org/">www.mail-archive.com</a><br />
+              <a href="http://news.gmane.org/gmane.comp.jakarta.commons.devel">news.gmane.org</a>
+          </td>
+        </tr>
+
+
+        <tr>
+          <td>
+            <strong>Commons Developer List</strong>
+            <br /><br />
+            Discussion of development of Commons Math.
+            <br /><br />
+          </td>
+          <td><a href="mailto:dev-subscribe at commons.apache.org">Subscribe</a></td>
+          <td><a href="mailto:dev-unsubscribe at commons.apache.org">Unsubscribe</a></td>
+          <td><a href="mailto:dev at commons.apache.org?subject=[math]">Post</a></td>
+          <td><a href="http://mail-archives.apache.org/mod_mbox/commons-dev/">mail-archives.apache.org</a></td>
+          <td><a href="http://markmail.org/list/org.apache.commons.dev/">markmail.org</a><br />
+              <a href="http://www.mail-archive.com/dev@commons.apache.org/">www.mail-archive.com</a><br />
+              <a href="http://news.gmane.org/gmane.comp.jakarta.commons.devel">news.gmane.org</a>
+          </td>
+        </tr>
+
+
+        <tr>
+          <td>
+            <strong>Commons Issues List</strong>
+            <br /><br />
+            Only for e-mails automatically generated by the <a href="issue-tracking.html">issue tracking</a> system.
+            <br /><br />
+          </td>
+          <td><a href="mailto:issues-subscribe at commons.apache.org">Subscribe</a></td>
+          <td><a href="mailto:issues-unsubscribe at commons.apache.org">Unsubscribe</a></td>
+          <td><i>read only</i></td>
+          <td><a href="http://mail-archives.apache.org/mod_mbox/commons-issues/">mail-archives.apache.org</a></td>
+          <td><a href="http://markmail.org/list/org.apache.commons.issues/">markmail.org</a><br />
+              <a href="http://www.mail-archive.com/issues@commons.apache.org/">www.mail-archive.com</a>
+          </td>
+        </tr>
+
+
+        <tr>
+          <td>
+            <strong>Commons Commits List</strong>
+            <br /><br />
+            Only for e-mails automatically generated by the <a href="source-repository.html">source control</a> sytem.
+            <br /><br />
+          </td>
+          <td><a href="mailto:commits-subscribe at commons.apache.org">Subscribe</a></td>
+          <td><a href="mailto:commits-unsubscribe at commons.apache.org">Unsubscribe</a></td>
+          <td><i>read only</i></td>
+          <td><a href="http://mail-archives.apache.org/mod_mbox/commons-commits/">mail-archives.apache.org</a></td>
+          <td><a href="http://markmail.org/list/org.apache.commons.commits/">markmail.org</a><br />
+              <a href="http://www.mail-archive.com/commits@commons.apache.org/">www.mail-archive.com</a>
+          </td>
+        </tr>
+
+      </table>
+
+    </section>
+    <section name="Apache Mailing Lists">
+      <p>
+        Other mailing lists which you may find useful include:
+      </p>
+
+      <table>
+        <tr>
+          <th>Name</th>
+          <th>Subscribe</th>
+          <th>Unsubscribe</th>
+          <th>Post</th>
+          <th>Archive</th>
+          <th>Other Archives</th>
+        </tr>
+        <tr>
+          <td>
+            <strong>Apache Announce List</strong>
+            <br /><br />
+            General announcements of Apache project releases.
+            <br /><br />
+          </td>
+          <td><a class="externalLink" href="mailto:announce-subscribe at apache.org">Subscribe</a></td> 
+          <td><a class="externalLink" href="mailto:announce-unsubscribe at apache.org">Unsubscribe</a></td> 
+          <td><i>read only</i></td>
+          <td><a class="externalLink" href="http://mail-archives.apache.org/mod_mbox/announce/">mail-archives.apache.org</a></td> 
+          <td><a class="externalLink" href="http://markmail.org/list/org.apache.announce/">markmail.org</a><br />
+              <a class="externalLink" href="http://old.nabble.com/Apache-News-and-Announce-f109.html">old.nabble.com</a><br />
+              <a class="externalLink" href="http://www.mail-archive.com/announce@apache.org/">www.mail-archive.com</a><br />
+              <a class="externalLink" href="http://news.gmane.org/gmane.comp.apache.announce">news.gmane.org</a>
+          </td>
+        </tr>
+      </table>
+
+    </section>
+  </body>
+</document>
diff --git a/src/site/xdoc/proposal.xml b/src/site/xdoc/proposal.xml
new file mode 100644
index 0000000..1e2b160
--- /dev/null
+++ b/src/site/xdoc/proposal.xml
@@ -0,0 +1,132 @@
+<?xml version="1.0"?>
+
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT 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: 799967 $ $Date: 2009-08-02 03:06:03 +0200 (dim. 02 août 2009) $ -->
+<document>
+
+  <properties>
+    <title>Proposal for math Package</title>
+    <author email="martin at mvdb.net">Robert Burrell Donkin</author>
+  </properties>
+
+    <body>
+
+    <section name='Proposal for math Package'>
+
+    <subsection name='(0) Rationale'>
+<p>The Java programming language and the math extensions in commons-lang provide implementations 
+for only the most basic mathematical algorithms. Routine development tasks such as computing 
+basic statistics or solving a system of linear equations require components not available in java 
+or commons-lang.</p>
+
+<p>Most basic mathematical or statistical algorithms are available in open source implementations, 
+but to assemble a simple set of capabilities one has to use multiple libraries, many of which have 
+more restrictive licensing terms than the ASF. In addition, many of the best open source 
+implementations (e.g. the R statistical package) are either not available in Java or require large 
+support libraries and/or external dependencies to work.</p>
+
+<p>A commons-math community will provide a productive environment for aggregation, testing and 
+support of efficient Java implementations of commonly used mathematical and statistical algorithms.</p>
+
+    </subsection>
+
+    <subsection name='(1) Scope of the Package'>
+
+<p>The Math project shall create and maintain a library of lightweight, self-contained mathematics 
+and statistics components addressing the most common practical problems not immediately available in 
+the Java programming language or commons-lang. The guiding principles for commons-math will be:
+
+<ol>
+<li>Real-world application use cases determine priority</li>
+<li>Emphasis on small, easily integrated components rather than large libraries with complex 
+dependencies</li>
+<li>All algorithms are fully documented and follow generally accepted best practices</li>
+<li>In situations where multiple standard algorithms exist, use the Strategy pattern to support 
+multiple implementations</li>
+<li>Limited dependencies. No external dependencies beyond Commons components and the JDK</li>
+</ol>
+</p>
+    </subsection>
+    
+    <subsection name='(1.5) Interaction With Other Packages'>
+
+<p><em>math</em> relies only on standard JDK 1.2 (or later) APIs for
+production deployment.  It utilizes the JUnit unit testing framework for
+developing and executing unit tests, but this is of interest only to
+developers of the component.</p>
+
+<p>No external configuration files are utilized.</p>
+
+    </subsection>
+    <subsection name='(2) Initial Source of the Package'>
+
+<p>The initial codebase will consist of implementations of basic statistical algorithms such 
+as the following:
+<ul>
+<li>Simple univariate statistics (mean, standard deviation, n, confidence intervals)</li>
+<li>Frequency distributions</li>
+<li>t-test, chi-square test</li>
+<li>Random numbers from Gaussian, Exponential, Poisson distributions</li>
+<li>Random sampling/resampling</li>
+<li>Bivariate regression, corellation</li>
+</ul>
+
+and mathematical algorithms such as the following:
+<ul>
+<li>Basic Complex Number representation with algebraic operations</li>
+<li>Newton's method for finding roots</li>
+<li>Binomial coefficients</li>
+<li>Exponential growth and decay (set up for financial applications)</li>
+<li>Polynomial Interpolation (curve fitting)</li>
+<li>Basic Matrix representation with algebraic operations</li>
+</ul>
+</p>
+
+<p>The proposed package name for the new component is
+<code>org.apache.commons.math</code>.</p>
+
+    </subsection>
+    <subsection name='(3)  Required Jakarta-Commons Resources'>
+
+<ul>
+<li>CVS Repository - New directory <code>math</code> in the
+    <code>jakarta-commons</code> CVS repository.</li>
+<li>Mailing List - Discussions will take place on the general
+    <em>dev at commons.apache.org</em> mailing list.  To help
+    list subscribers identify messages of interest, it is suggested that
+    the message subject of messages about this component be prefixed with
+    [math].</li>
+<li>Bugzilla - New component "math" under the "Commons" product
+    category, with appropriate version identifiers as needed.</li>
+<li>Jyve FAQ - New category "commons-math" (when available).</li>
+</ul>
+
+    </subsection>
+    <subsection name='(4) Initial Committers'>
+
+<p>The initial committers on the math component shall be:
+<ul>
+  <li><a href="mailto:rdonkin at apache.org">Robert Burrell Donkin</a></li>
+  <li><a href="mailto:tobrien at apache.org">Tim O'Brien</a></li>
+</ul>
+</p>
+    </subsection>
+    </section>
+    </body>
+</document>
diff --git a/src/site/xdoc/userguide/analysis.xml b/src/site/xdoc/userguide/analysis.xml
new file mode 100644
index 0000000..903bb5f
--- /dev/null
+++ b/src/site/xdoc/userguide/analysis.xml
@@ -0,0 +1,413 @@
+<?xml version="1.0"?>
+
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+  
+<?xml-stylesheet type="text/xsl" href="./xdoc.xsl"?>
+<!-- $Revision: 937545 $ $Date: 2010-04-24 00:33:37 +0200 (sam. 24 avril 2010) $ -->
+<document url="analysis.html">
+  <properties>
+    <title>The Commons Math User Guide - Numerical Analysis</title>
+  </properties>
+  <body>
+    <section name="4 Numerical Analysis">
+      <subsection name="4.1 Overview" href="overview">
+        <p>
+         The analysis package is the parent package for algorithms dealing with
+         real-valued functions of one real variable. It contains dedicated sub-packages
+         providing numerical root-finding, integration, and interpolation. It also
+         contains a polynomials sub-package that considers polynomials with real
+         coefficients as differentiable real functions.
+        </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>
+        <p>
+          Possible future additions may include numerical differentiation.
+        </p>
+      </subsection>
+      <subsection name="4.2 Root-finding" href="rootfinding">
+        <p>
+          A <a href="../apidocs/org/apache/commons/math/analysis/solvers/UnivariateRealSolver.html">
+          UnivariateRealSolver</a> provides the means to find roots of
+          <a href="../apidocs/org/apache/commons/math/analysis/UnivariateRealFunction.html">univariate real-valued functions</a>.
+          A root is the value where the function takes the value 0.  Commons-Math
+          includes implementations of the following root-finding algorithms: <ul>
+          <li><a href="../apidocs/org/apache/commons/math/analysis/solvers/BisectionSolver.html">
+          Bisection</a></li>
+          <li><a href="../apidocs/org/apache/commons/math/analysis/solvers/BrentSolver.html">
+          Brent-Dekker</a></li>
+          <li><a href="../apidocs/org/apache/commons/math/analysis/solvers/NewtonSolver.html">
+          Newton's Method</a></li>
+          <li><a href="../apidocs/org/apache/commons/math/analysis/solvers/SecantSolver.html">
+          Secant Method</a></li>
+          <li><a href="../apidocs/org/apache/commons/math/analysis/solvers/MullerSolver.html">
+          Muller's Method</a></li>
+          <li><a href="../apidocs/org/apache/commons/math/analysis/solvers/LaguerreSolver.html">
+          Laguerre's Method</a></li>
+          <li><a href="../apidocs/org/apache/commons/math/analysis/solvers/RidderSolver.html">
+          Ridder's Method</a></li>
+          </ul>      
+        </p>
+        <p>
+          There are numerous non-obvious traps and pitfalls in root finding.
+          First, the usual disclaimers due to the way real world computers
+          calculate values apply.  If the computation of the function provides
+          numerical instabilities, for example due to bit cancellation, the root
+          finding algorithms may behave badly and fail to converge or even
+          return bogus values. There will not necessarily be an indication that
+          the computed root is way off the true value.  Secondly, the root finding
+          problem itself may be inherently ill-conditioned.  There is a
+           "domain of indeterminacy", the interval for which the function has
+          near zero absolute values around the true root,  which may be large.
+          Even worse, small problems like roundoff error may cause the function
+          value to "numerically oscillate" between negative and positive values.
+          This may again result in roots way off the true value, without
+          indication.  There is not much a generic algorithm can do if
+          ill-conditioned problems are met.  A way around this is to transform
+          the problem in order to get a better conditioned function.  Proper 
+          selection of a root-finding algorithm and its configuration parameters
+          requires knowledge of the analytical properties of the function under
+          analysis and numerical analysis techniques.  Users are encouraged
+          to consult a numerical analysis text (or a numerical analyst) when
+          selecting and configuring a solver.
+        </p>
+        <p>
+          In order to use the root-finding features, first a solver object must
+          be created.  It is encouraged that all solver object creation occurs
+          via the <a href="../apidocs/org/apache/commons/math/analysis/solvers/UnivariateRealSolverFactory.html">
+          UnivariateRealSolverFactory</a> class. <code>UnivariateRealSolverFactory</code>
+          is a simple factory used to create all of the solver objects supported by
+          Commons-Math.  
+          The typical usage of <code>UnivariateRealSolverFactory</code> to create a
+          solver object would be:
+        </p>
+        <source>UnivariateRealSolverFactory factory = UnivariateRealSolverFactory.newInstance();
+UnivariateRealSolver solver = factory.newDefaultSolver();</source>
+        <p>
+          The solvers that can be instantiated via the 
+          <code>UnivariateRealSolverFactory</code> are detailed below:
+          <table>
+            <tr><th>Solver</th><th>Factory Method</th><th>Notes on Use</th></tr>
+            <tr><td>Bisection</td><td>newBisectionSolver</td><td><div>Root must be
+                  bracketted.</div><div>Linear, guaranteed convergence</div></td></tr>
+            <tr><td>Brent</td><td>newBrentSolver</td><td><div>Root must be bracketted.</div>
+                <div>Super-linear, guaranteed convergence</div></td></tr>
+            <tr><td>Newton</td><td>newNewtonSolver</td><td><div>Uses single value for
+                  initialization.</div><div>Super-linear, non-guaranteed convergence</div>
+                <div>Function must be differentiable</div></td></tr>
+            <tr><td>Secant</td><td>newSecantSolver</td><td><div>Root must be bracketted.</div>
+                <div>Super-linear, non-guaranteed convergence</div></td></tr>
+            <tr><td>Muller</td><td>newMullerSolver</td><td><div>Root must be bracketted.</div>
+                <div>We restrict ourselves to real valued functions, not complex ones</div>
+            </td></tr>
+            <tr><td>Laguerre</td><td>newLaguerreSolver</td><td><div>Root must be bracketted.</div>
+                <div>Function must be a polynomial</div></td></tr>
+            <tr><td>Ridder</td><td>newRidderSolver</td><td><div>Root must be bracketted.</div>
+                <div></div></td></tr>
+          </table>
+        </p>
+        <p>
+          Using a solver object, roots of functions are easily found using the <code>solve</code>
+          methods.  For a function <code>f</code>, and two domain values, <code>min</code> and
+          <code>max</code>, <code>solve</code> computes a value <code>c</code> such that:
+          <ul>
+            <li><code>f(c) = 0.0</code> (see "function value accuracy")</li>
+            <li><code>min <= c <= max</code></li>
+          </ul>
+        </p>
+        <p>
+          Typical usage:
+        </p>
+        <source>UnivariateRealFunction function = // some user defined function object
+UnivariateRealSolverFactory factory = UnivariateRealSolverFactory.newInstance();
+UnivariateRealSolver solver = factory.newBisectionSolver();
+double c = solver.solve(function, 1.0, 5.0);</source>
+        <p>
+          The <code>BrentSolve</code> uses the Brent-Dekker algorithm which is
+          fast and robust.  This algorithm is recommended for most users and  the 
+          <code>BrentSolver</code> is the default solver provided by the 
+          <code>UnivariateRealSolverFactory</code>.  If there are multiple roots
+          in the interval, or there is a large domain of indeterminacy, the
+          algorithm will converge to a random root in the interval without
+          indication that there are problems.  Interestingly, the examined text
+          book implementations all disagree in details of the convergence
+          criteria.  Also each implementation had problems for one of the test
+          cases, so the expressions had to be fudged further. Don't expect to
+          get exactly the same root values as for other implementations of this
+          algorithm.
+        </p>
+        <p>
+          The <code>SecantSolver</code> uses a variant of the well known secant
+          algorithm.  It may be a bit faster than the Brent solver for a class
+          of well-behaved functions.
+        </p>
+        <p>
+          The <code>BisectionSolver</code> is included for completeness and for
+          establishing a fall back in cases of emergency.  The algorithm is
+          simple, most likely bug free and guaranteed to converge even in very
+          adverse circumstances which might cause other algorithms to
+          malfunction.  The drawback is of course that it is also guaranteed
+          to be slow.
+        </p>
+        <p>
+          The <code>UnivariateRealSolver</code> interface exposes many
+          properties to control the convergence of a solver.  For the most part,
+          these properties should not have to change from their default values
+          to produce good results.  In the circumstances where changing these
+          property values is needed, it is easily done through getter and setter
+          methods on <code>UnivariateRealSolver</code>:
+          <table>
+            <tr><th>Property</th><th>Methods</th><th>Purpose</th></tr>
+            <tr>
+              <td>Absolute accuracy</td>
+              <td>
+                <div>getAbsoluteAccuracy</div>
+                <div>resetAbsoluteAccuracy</div>
+                <div>setAbsoluteAccuracy</div>
+              </td>
+              <td>
+                The Absolute Accuracy is (estimated) maximal difference between
+                the computed root and the true root of the function.  This is
+                what most people think of as "accuracy" intuitively.  The default
+                value is chosen as a sane value for most real world problems,
+                for roots in the range from -100 to +100.  For accurate
+                computation of roots near zero, in the range form -0.0001 to
+                 +0.0001, the value may be decreased.  For computing roots
+                much larger in absolute value than 100, the default absolute
+                accuracy may never be reached because the given relative
+                accuracy is reached first.  
+              </td>
+            </tr>
+              <tr>
+              <td>Relative accuracy</td>
+              <td>
+                <div>getRelativeAccuracy</div>
+                <div>resetRelativeAccuracy</div>
+                <div>setRelativeAccuracy</div>
+              </td>
+              <td>
+                The Relative Accuracy is the maximal difference between the
+                computed root and the true root, divided by the maximum of the
+                absolute values of the numbers. This accuracy measurement is
+                better suited for numerical calculations with computers, due to
+                the way floating point numbers are represented.  The default
+                value is chosen so that algorithms will get a result even for
+                roots with large absolute values, even while it may be
+                impossible to reach the given absolute accuracy.
+              </td>
+            </tr>
+            <tr>
+              <td>Function value accuracy</td>
+              <td>
+                <div>getFunctionValueAccuracy</div>
+                <div>resetFunctionValueAccuracy</div>
+                <div>setFunctionValueAccuracy</div>
+              </td>
+              <td>
+                This value is used by some algorithms in order to prevent
+                numerical instabilities. If the function is evaluated to an
+                absolute value smaller than the Function Value Accuracy, the
+                algorithms assume they hit a root and return the value
+                immediately.  The default value is a "very small value".  If the
+                goal is to get a near zero function value rather than an accurate
+                root, computation may be sped up by setting this value
+                appropriately.
+              </td>
+            </tr>
+            <tr>
+              <td>Maximum iteration count</td>
+              <td>
+                <div>getMaximumIterationCount</div>
+                <div>resetMaximumIterationCount</div>
+                <div>setMaximumIterationCount</div>
+              </td>
+              <td>
+                This is the maximal number of iterations the algorithm will try.
+                If this number is exceeded, non-convergence is assumed and a
+                <code>ConvergenceException</code> exception is thrown.  The
+                default value is 100, which should be plenty, given that a
+                bisection algorithm can't get any more accurate after 52 
+                iterations because of the number of mantissa bits in a double
+                precision floating point number. If a number of ill-conditioned
+                problems is to be solved, this number can be decreased in order
+                to avoid wasting time.
+              </td>
+            </tr>
+          </table>
+        </p>
+      </subsection>
+      <subsection name="4.3 Interpolation" href="interpolation">
+        <p>
+          A <a href="../apidocs/org/apache/commons/math/analysis/interpolation/UnivariateRealInterpolator.html">
+          UnivariateRealInterpolator</a> is used to find a univariate real-valued
+          function <code>f</code> which for a given set of ordered pairs 
+          (<code>x<sub>i</sub></code>,<code>y<sub>i</sub></code>) yields
+          <code>f(x<sub>i</sub>)=y<sub>i</sub></code> to the best accuracy possible. The result
+          is provided as an object implementing the <a
+          href="../apidocs/org/apache/commons/math/analysis/UnivariateRealFunction.html">
+          UnivariateRealFunction</a> interface. It can therefore be evaluated at any point,
+          including point not belonging to the original set.
+          Currently, only an interpolator for generating natural cubic splines and a polynomial
+          interpolator are available.  There is no interpolator factory, mainly because the
+          interpolation algorithm is more determined by the kind of the interpolated function
+          rather than the set of points to interpolate.
+          There aren't currently any accuracy controls either, as interpolation
+          accuracy is in general determined by the algorithm. 
+        </p>
+        <p>Typical usage:</p>
+        <source>double x[] = { 0.0, 1.0, 2.0 };
+double y[] = { 1.0, -1.0, 2.0);
+UnivariateRealInterpolator interpolator = new SplineInterpolator();
+UnivariateRealFunction function = interpolator.interpolate(x, y);
+double interpolationX = 0.5;
+double interpolatedY = function.evaluate(x);
+System.out println("f(" + interpolationX + ") = " + interpolatedY);</source>
+        <p>
+          A natural cubic spline is a function consisting of a polynomial of
+          third degree for each subinterval determined by the x-coordinates of the
+          interpolated points.  A function interpolating <code>N</code>
+          value pairs consists of <code>N-1</code> polynomials. The function
+          is continuous, smooth and can be differentiated twice.  The second
+          derivative is continuous but not smooth.  The x values passed to the
+          interpolator must be ordered in ascending order.  It is not valid to
+          evaluate the function for values outside the range 
+          <code>x<sub>0</sub></code>..<code>x<sub>N</sub></code>.
+        </p>
+        <p>
+          The polynomial function returned by the Neville's algorithm is a single
+          polynomial guaranteed to pass exactly through the interpolation points.
+          The degree of the polynomial is the number of points minus 1 (i.e. the
+          interpolation polynomial for a three points set will be a quadratic
+          polynomial). Despite the fact the interpolating polynomials is a perfect
+          approximation of a function at interpolation points, it may be a loose
+          approximation between the points. Due to <a
+          href="http://en.wikipedia.org/wiki/Runge's_phenomenon">Runge's phenomenom</a>
+          the error can get worse as the degree of the polynomial increases, so
+          adding more points does not always lead to a better interpolation.
+        </p>
+        <p>
+          Loess (or Lowess) interpolation is a robust interpolation useful for
+          smoothing univariate scaterplots. It has been described by William
+          Cleveland in his 1979 seminal paper <a
+          href="http://www.math.tau.ac.il/~yekutiel/MA%20seminar/Cleveland%201979.pdf">Robust
+          Locally Weighted Regression and Smoothing Scatterplots</a>. This kind of
+          interpolation is computationally intensive but robust.
+        </p>
+        <p>
+          Microsphere interpolation is a robust multidimensional interpolation algorithm.
+          It has been described in William Dudziak's <a
+          href="http://www.dudziak.com/microsphere.pdf">MS thesis</a>.
+        </p>
+        <p>
+          A <a href="../apidocs/org/apache/commons/math/analysis/interpolation/BivariateRealGridInterpolator.html">
+          BivariateRealGridInterpolator</a> is used to find a bivariate real-valued
+          function <code>f</code> which for a given set of tuples
+          (<code>x<sub>i</sub></code>,<code>y<sub>j</sub></code>,<code>f<sub>ij</sub></code>)
+          yields <code>f(x<sub>i</sub>,y<sub>j</sub>)=f<sub>ij</sub></code> to the best accuracy
+          possible. The result is provided as an object implementing the
+          <a href="../apidocs/org/apache/commons/math/analysis/BivariateRealFunction.html">
+          BivariateRealFunction</a> interface. It can therefore be evaluated at any point,
+          including a point not belonging to the original set.
+          The arrays <code>x<sub>i</sub></code> and <code>y<sub>j</sub></code> must be
+          sorted in increasing order in order to define a two-dimensional grid.
+        </p>
+        <p>
+          In <a href="http://en.wikipedia.org/wiki/Bicubic_interpolation">bicubic interpolation</a>,
+          the interpolation function is a 3rd-degree polynomial of two variables. The coefficients
+          are computed from the function values sampled on a grid, as well as the values of the
+          partial derivatives of the function at those grid points.
+          From two-dimensional data sampled on a grid, the
+          <a href="../apidocs/org/apache/commons/math/analysis/interpolation/BicubicSplineInterpolator.html">
+          BicubicSplineInterpolator</a> computes a
+          <a href="../apidocs/org/apache/commons/math/analysis/interpolation/BicubicSplineInterpolatingFunction.html">
+          bicubic interpolating function</a>.
+          Prior to computing an interpolating function, the
+          <a href="../apidocs/org/apache/commons/math/analysis/interpolation/SmoothingPolynomialBicubicSplineInterpolator.html">
+          SmoothingPolynomialBicubicSplineInterpolator</a> class performs smoothing of
+          the data by computing the polynomial that best fits each of the one-dimensional
+          curves along each of the coordinate axes.
+        </p>
+        <p>
+          A <a href="../apidocs/org/apache/commons/math/analysis/interpolation/TrivariateRealGridInterpolator.html">
+          TrivariateRealGridInterpolator</a> is used to find a trivariate real-valued
+          function <code>f</code> which for a given set of tuples
+          (<code>x<sub>i</sub></code>,<code>y<sub>j</sub></code>,<code>z<sub>k</sub></code>,
+          <code>f<sub>ijk</sub></code>)
+          yields <code>f(x<sub>i</sub>,y<sub>j</sub>,z<sub>k</sub>)=f<sub>ijk</sub></code>
+          to the best accuracy possible. The result is provided as an object implementing the
+          <a href="../apidocs/org/apache/commons/math/analysis/TrivariateRealFunction.html">
+          TrivariateRealFunction</a> interface. It can therefore be evaluated at any point,
+          including a point not belonging to the original set.
+          The arrays <code>x<sub>i</sub></code>, <code>y<sub>j</sub></code> and
+          <code>z<sub>k</sub></code> must be sorted in increasing order in order to define
+          a three-dimensional grid.
+        </p>
+        <p>
+          In <a href="http://en.wikipedia.org/wiki/Tricubic_interpolation">tricubic interpolation</a>,
+          the interpolation function is a 3rd-degree polynomial of three variables. The coefficients
+          are computed from the function values sampled on a grid, as well as the values of the
+          partial derivatives of the function at those grid points.
+          From three-dimensional data sampled on a grid, the
+          <a href="../apidocs/org/apache/commons/math/analysis/interpolation/TricubicSplineInterpolator.html">
+          TricubicSplineInterpolator</a> computes a
+          <a href="../apidocs/org/apache/commons/math/analysis/interpolation/TricubicSplineInterpolatingFunction.html">
+          tricubic interpolating function</a>.
+        </p>
+      </subsection>
+      <subsection name="4.4 Integration" href="integration">
+        <p>
+          A <a href="../apidocs/org/apache/commons/math/analysis/integration/UnivariateRealIntegrator.html">
+          UnivariateRealIntegrator</a> provides the means to numerically integrate
+          <a href="../apidocs/org/apache/commons/math/analysis/UnivariateRealFunction.html">
+          univariate real-valued functions</a>.
+          Commons-Math includes implementations of the following integration algorithms: <ul>
+          <li><a href="../apidocs/org/apache/commons/math/analysis/integration/RombergIntegrator.html">
+          Romberg's method</a></li>
+          <li><a href="../apidocs/org/apache/commons/math/analysis/integration/SimpsonIntegrator.html">
+          Simpson's method</a></li>
+          <li><a href="../apidocs/org/apache/commons/math/analysis/integration/TrapezoidIntegrator.html">
+          trapezoid method</a></li>
+          <li><a href="../apidocs/org/apache/commons/math/analysis/integration/LegendreGaussIntegrator.html">
+          Legendre-Gauss method</a></li>
+          </ul>      
+        </p>
+      </subsection>
+      <subsection name="4.5 Polynomials" href="polynomials">
+        <p>
+          The <a href="../apidocs/org/apache/commons/math/analysis/polynomials/package-summary.html">
+          org.apache.commons.math.analysis.polynomials</a> package provides real
+          coefficients polynomials.
+        </p>
+        <p>
+          The <a href="../apidocs/org/apache/commons/math/analysis/polynomials/PolynomialFunction.html">
+          PolynomialFunction</a> class is the most general one, using traditional
+          coefficients arrays. The
+          <a href="../apidocs/org/apache/commons/math/analysis/polynomials/PolynomialsUtils.html">
+          PolynomialsUtils</a> utility class provides static factory methods to build
+          Chebyshev, Hermite, Lagrange and Legendre polynomials. Coefficients are
+          computed using exact fractions so these factory methods can build polynomials
+          up to any degree.
+        </p>
+      </subsection>
+    </section>
+  </body>
+</document>
diff --git a/src/site/xdoc/userguide/complex.xml b/src/site/xdoc/userguide/complex.xml
new file mode 100644
index 0000000..72f8547
--- /dev/null
+++ b/src/site/xdoc/userguide/complex.xml
@@ -0,0 +1,150 @@
+<?xml version="1.0"?>
+
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+  
+<?xml-stylesheet type="text/xsl" href="./xdoc.xsl"?>
+<!-- $Revision: 937893 $ $Date: 2010-04-25 23:42:47 +0200 (dim. 25 avril 2010) $ -->
+<document url="stat.html">
+  <properties>
+    <title>The Commons Math User Guide - Complex Numbers</title>
+  </properties>
+  <body>
+    <section name="7 Complex Numbers">
+      <subsection name="7.1 Overview" href="overview">
+        <p>
+          The complex packages provides a complex number type as well as complex
+          versions of common transcendental functions and complex number
+          formatting.
+        </p>
+      </subsection>
+      <subsection name="7.2 Complex Numbers" href="complex">
+        <p>
+          <a href="../apidocs/org/apache/commons/math/complex/Complex.html">
+          Complex</a> provides a complex number type that forms the basis for
+          the complex functionality found in commons-math.
+         </p>  
+         <p>
+           Complex functions and arithmetic operations are implemented in
+           commons-math by applying standard computational formulas and
+           following the rules for <code>java.lang.Double</code> arithmetic in 
+           handling infinite and <code>NaN</code> values.  No attempt is made
+           to comply with ANSII/IEC C99x Annex G or any other standard for
+           Complex arithmetic.  See the class and method javadocs for the 
+           <a href="../apidocs/org/apache/commons/math/complex/Complex.html">
+           Complex</a> and
+           <a href="../apidocs/org/apache/commons/math/complex/ComplexUtils.html">
+           ComplexUtils</a> classes for details on computing formulas.
+        </p>
+        <p>
+          To create a complex number, simply call the constructor passing in two
+          floating-point arguments, the first being the real part of the
+          complex number and the second being the imaginary part:
+          <source>Complex c = new Complex(1.0, 3.0); // 1 + 3i</source>
+        </p>
+        <p>
+          Complex numbers may also be created from polar representations
+          using the <code>polar2Complex</code> method in 
+          <code>ComplexUtils</code>.
+        </p>
+        <p>
+          The <code>Complex</code> class provides basic unary and binary
+          complex number operations.  These operations provide the means to add,
+          subtract, multiply and divide complex numbers along with other
+          complex number functions similar to the real number functions found in
+          <code>java.math.BigDecimal</code>:
+          <source>Complex lhs = new Complex(1.0, 3.0);
+Complex rhs = new Complex(2.0, 5.0);
+
+Complex answer = lhs.add(rhs);       // add two complex numbers
+        answer = lhs.subtract(rhs);  // subtract two complex numbers
+        answer = lhs.abs();          // absolute value
+        answer = lhs.conjugate(rhs); // complex conjugate</source>
+        </p>
+      </subsection>
+      <subsection name="7.3 Complex Transcendental Functions" href="function">
+        <p>
+          <a href="../apidocs/org/apache/commons/math/complex/Complex.html">
+          Complex</a> also provides implementations of serveral transcendental
+          functions involving complex number arguments.
+          Prior to version 1.2, these functions were provided
+          by <a href="../apidocs/org/apache/commons/math/complex/ComplexUtils.html">
+          ComplexUtils</a> in a way similar to the real number functions found in
+          <code>java.lang.Math</code>, but this has been deprecated.
+          These operations provide the means to compute the log, sine, tangent,
+          and other complex values :
+          <source>Complex first  = new Complex(1.0, 3.0);
+Complex second = new Complex(2.0, 5.0);
+
+Complex answer = first.log();        // natural logarithm.
+        answer = first.cos();        // cosine
+        answer = first.pow(second);  // first raised to the power of second</source>
+        </p>
+      </subsection>
+      <subsection name="7.4 Complex Formatting and Parsing" href="formatting">
+        <p>
+          <code>Complex</code> instances can be converted to and from strings
+          using the<a href="../apidocs/org/apache/commons/math/complex/ComplexFormat.html">
+          ComplexFormat</a> class.
+          <code>ComplexFormat</code> is a <code>java.text.Format</code>
+          extension and, as such, is used like other formatting objects (e.g.
+          <code>java.text.SimpleDateFormat</code>):
+          <source>ComplexFormat format = new ComplexFormat(); // default format
+Complex c = new Complex(1.1111, 2.2222);
+String s = format.format(c); // s contains "1.11 + 2.22i"</source>
+        </p>
+        <p>
+          To customize the formatting output, one or two
+          <code>java.text.NumberFormat</code> instances can be used to construct
+          a <code>ComplexFormat</code>.  These number formats control the
+          formatting of the real and imaginary values of the complex number:
+          <source>NumberFormat nf = NumberFormat.getInstance();
+nf.setMinimumFractionDigits(3);
+nf.setMaximumFractionDigits(3);
+
+// create complex format with custom number format
+// when one number format is used, both real and
+// imaginary parts are formatted the same
+ComplexFormat cf = new ComplexFormat(nf);
+Complex c = new Complex(1.11, 2.2222);
+String s = format.format(c); // s contains "1.110 + 2.222i"
+
+NumberFormat nf2 = NumberFormat.getInstance();
+nf.setMinimumFractionDigits(1);
+nf.setMaximumFractionDigits(1);
+
+// create complex format with custom number formats
+cf = new ComplexFormat(nf, nf2);
+s = format.format(c); // s contains "1.110 + 2.2i"</source>
+        </p>
+        <p>
+          Another formatting customization provided by
+          <code>ComplexFormat</code> is the text used for the imaginary
+          designation.  By default, the imaginary notation is "i" but, it can be
+          manipulated using the <code>setImaginaryCharacter</code> method.
+        </p>
+        <p>
+          Formatting inverse operation, parsing, can also be performed by
+          <code>ComplexFormat</code>.  Parse a complex number from a string,
+          simply call the <code>parse</code> method:
+          <source>ComplexFormat cf = new ComplexFormat();
+Complex c = cf.parse("1.110 + 2.222i");</source>
+        </p>
+      </subsection>
+    </section>
+  </body>
+</document>
diff --git a/src/site/xdoc/userguide/distribution.xml b/src/site/xdoc/userguide/distribution.xml
new file mode 100644
index 0000000..11cfd8f
--- /dev/null
+++ b/src/site/xdoc/userguide/distribution.xml
@@ -0,0 +1,95 @@
+<?xml version="1.0"?>
+
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+  
+<?xml-stylesheet type="text/xsl" href="./xdoc.xsl"?>
+<!-- $Revision: 937893 $ $Date: 2010-04-25 23:42:47 +0200 (dim. 25 avril 2010) $ -->
+<document url="distribution.html">
+  <properties>
+    <title>The Commons Math User Guide - Distributions</title>
+  </properties>
+  <body>
+    <section name="8 Probability Distributions">
+      <subsection name="8.1 Overview" href="overview">
+        <p>
+          The distributions package provide a framework for some commonly used
+          probability distributions.
+        </p>
+      </subsection>
+      <subsection name="8.2 Distribution Framework" href="distributions">
+        <p>
+          The distribution framework provides the means to compute probability density
+          function (PDF) probabilities and cumulative distribution function (CDF)
+          probabilities for common probability distributions. Along with the direct
+          computation of PDF and CDF probabilities, the framework also allows for the
+          computation of inverse PDF and inverse CDF values.
+        </p>
+        <p>
+          Using a distribution object, PDF and CDF probabilities are easily computed
+          using the <code>cumulativeProbability</code> methods.  For a distribution
+          <code>X</code>, and a domain value, <code>x</code>,
+          <code>cumulativeProbability</code> computes <code>P(X <= x)</code>
+          (i.e. the lower tail probability of <code>X</code>).
+        </p>
+<source>TDistribution t = new TDistributionImpl(29);
+double lowerTail = t.cumulativeProbability(-2.656);     // P(T <= -2.656)
+double upperTail = 1.0 - t.cumulativeProbability(2.75); // P(T >= 2.75)</source>
+        <p>
+          The inverse PDF and CDF values are just as easily computed using the
+          <code>inverseCumulativeProbability</code> methods.  For a distribution <code>X</code>,
+          and a probability, <code>p</code>, <code>inverseCumulativeProbability</code>
+          computes the domain value <code>x</code>, such that:
+          <ul>
+            <li><code>P(X <= x) = p</code>, for continuous distributions</li>
+            <li><code>P(X <= x) <= p</code>, for discrete distributions</li>
+          </ul>
+          Notice the different cases for continuous and discrete distributions.  This is the result
+          of PDFs not being invertible functions.  As such, for discrete distributions, an exact
+          domain value can not be returned.  Only the "best" domain value.  For Commons-Math, the "best"
+          domain value is determined by the largest domain value whose cumulative probability is
+          less-than or equal to the given probability.
+        </p>
+      </subsection>
+      <subsection name="8.3 User Defined Distributions" href="userdefined">
+        <p>
+        Since there are numerous distributions and Commons-Math only directly
+        supports a handful, it may be necessary to extend the distribution
+        framework to satisfy individual needs.  It is recommended that the
+        <a href="../apidocs/org/apache/commons/math/distribution/Distribution.html">Distribution</a>,
+        <a href="../apidocs/org/apache/commons/math/distribution/ContinuousDistribution.html">
+        ContinuousDistribution</a>,
+        <a href="../apidocs/org/apache/commons/math/distribution/DiscreteDistribution.html">
+        DiscreteDistribution</a>, and <a href="../apidocs/org/apache/commons/math/distribution/IntegerDistribution.html">
+        IntegerDistribution</a> interfaces serve as
+        base types for any extension.  These serve as the basis for all the
+        distributions directly supported by Commons-Math and using those interfaces
+        for implementation purposes will ensure any extension is compatible with the
+        remainder of Commons-Math.  To aid in implementing a distribution extension,
+        the <a href="../apidocs/org/apache/commons/math/distribution/AbstractDistribution.html">
+        AbstractDistribution</a>, <a href="../apidocs/org/apache/commons/math/distribution/AbstractContinuousDistribution.html">
+        AbstractContinuousDistribution</a>, and <a href="../apidocs/org/apache/commons/math/distribution/AbstractIntegerDistribution.html">
+        AbstractIntegerDistribution</a> provide implementation building blocks and
+        offer basic distribution functionality.  By extending these abstract classes
+        directly, much of the repetitive distribution implementation is already
+        developed and should save time and effor in developing user-defined
+        distributions.
+        </p>
+      </subsection>
+    </section>
+  </body>
+</document>
diff --git a/src/site/xdoc/userguide/fraction.xml b/src/site/xdoc/userguide/fraction.xml
new file mode 100644
index 0000000..3166800
--- /dev/null
+++ b/src/site/xdoc/userguide/fraction.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0"?>
+
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+   
+         http://www.apache.org/licenses/LICENSE-2.0
+   
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+  -->
+  
+<?xml-stylesheet type="text/xsl" href="./xdoc.xsl"?>
+<!-- $Revision: 937893 $ $Date: 2010-04-25 23:42:47 +0200 (dim. 25 avril 2010) $ -->
+<document url="fraction.html">
+  <properties>
+    <title>The Commons Math User Guide - Fractions</title>
+  </properties>
+  <body>
+    <section name="9 Fractions">
+      <subsection name="9.1 Overview" href="overview">
+        <p>
+          The fraction packages provides a fraction number type as well as
+          fraction number formatting.
+        </p>
+      </subsection>
+      <subsection name="9.2 Fraction Numbers" href="fraction">
+        <p>
+          <a href="../apidocs/org/apache/commons/math/fraction/Fraction.html">
+          Fraction</a> and <a href="../apidocs/org/apache/commons/math/fraction/BigFraction.html">
+          BigFraction</a> provide fraction number type that forms the basis for
+          the fraction functionality found in Commons-Math. The former one can be
+          used for fractions whose numerators and denominators are small enough
+          to fit in an int (taking care of intermediate values) while the second
+          class should be used when there is a risk the numerator and denominator
+          grow very large.
+        </p>
+        <p>
+          A fraction number, can be built from two integer arguments representing numerator
+          and denominator or from a double which will be approximated:
+          <source>Fraction f = new Fraction(1, 3); // 1 / 3
+Fraction g = new Fraction(0.25); // 1 / 4</source>
+        </p>
+        <p>
+          Of special note with fraction construction, when a fraction is created it is always reduced to lowest terms.
+        </p>
+        <p>
+          The <code>Fraction</code> class provides many unary and binary
+          fraction operations.  These operations provide the means to add,
+          subtract, multiple and, divide fractions along with other functions similar to the real number functions found in
+          <code>java.math.BigDecimal</code>:
+          <source>Fraction lhs = new Fraction(1, 3);
+Fraction rhs = new Fraction(2, 5);
+
+Fraction answer = lhs.add(rhs);     // add two fractions
+        answer = lhs.subtract(rhs); // subtract two fractions
+        answer = lhs.abs();         // absolute value
+        answer = lhs.reciprocal();  // reciprocal of lhs</source>
+        </p>
+        <p>
+          Like fraction construction, for each of the fraction functions, the resulting fraction is reduced to lowest terms.
+        </p>
+      </subsection>
+      <subsection name="9.3 Fraction Formatting and Parsing" href="formatting">
+        <p>
+          <code>Fraction</code> instances can be converted to and from strings
+          using the<a href="../apidocs/org/apache/commons/math/fraction/FractionFormat.html">
+          FractionFormat</a> class. <code>FractionFormat</code> is a
+          <code>java.text.Format</code> extension and, as such, is used like other
+          formatting objects (e.g. <code>java.text.SimpleDateFormat</code>):
+          <source>FractionFormat format = new FractionFormat(); // default format
+Fraction f = new Fraction(2, 4);
+String s = format.format(f); // s contains "1 / 2", note the reduced fraction</source>
+        </p>
+        <p>
+          To customize the formatting output, one or two
+          <code>java.text.NumberFormat</code> instances can be used to construct
+          a <code>FractionFormat</code>.  These number formats control the
+          formatting of the numerator and denominator of the fraction:
+          <source>NumberFormat nf = NumberFormat.getInstance(Locale.FRANCE);
+// create fraction format with custom number format
+// when one number format is used, both numerator and
+// denominator are formatted the same
+FractionFormat format = new FractionFormat(nf);
+Fraction f = new Fraction(2000, 3333);
+String s = format.format(c); // s contains "2.000 / 3.333"
+
+NumberFormat nf2 = NumberFormat.getInstance(Locale.US);
+// create fraction format with custom number formats
+format = new FractionFormat(nf, nf2);
+s = format.format(f); // s contains "2.000 / 3,333"</source>
+        </p>
+        <p>
+          Formatting's inverse operation, parsing, can also be performed by
+          <code>FractionFormat</code>.  To parse a fraction from a string,
+          simply call the <code>parse</code> method:
+          <source>FractionFormat ff = new FractionFormat();
+Fraction f = ff.parse("-10 / 21");</source>
+        </p>
+      </subsection>
+    </section>
+  </body>
+</document>
diff --git a/src/site/xdoc/userguide/genetics.xml b/src/site/xdoc/userguide/genetics.xml
new file mode 100644
index 0000000..14d6326
--- /dev/null
+++ b/src/site/xdoc/userguide/genetics.xml
@@ -0,0 +1,135 @@
+<?xml version="1.0"?>
+
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one or more
+    contributor license agreements.  See the NOTICE file distributed with
+    this work for additional information regarding copyright ownership.
+    The ASF licenses this file to You under the Apache License, Version 2.0
+    (the "License"); you may not use this file except in compliance with
+    the License.  You may obtain a copy of the License at
+   
+         http://www.apache.org/licenses/LICENSE-2.0
+   
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+  -->
+  
+<?xml-stylesheet type="text/xsl" href="./xdoc.xsl"?>
+<!-- $Revision: 937893 $ $Date: 2010-04-25 23:42:47 +0200 (dim. 25 avril 2010) $ -->
+<document url="genetics.html">
+  <properties>
+    <title>The Commons Math User Guide - Genetic Algorithms</title>
+  </properties>
+  <body>
+    <section name="14 Genetic Algorithms">
+      <subsection name="14.1 Overview" href="overview">
+        <p>
+          The genetics package provides a framework and implementations for 
+          genetic algorithms.
+        </p>
+      </subsection>
+      <subsection name="14.2 GA Framework">
+      <p>
+      <a href="../apidocs/org/apache/commons/math/genetics/GeneticAlgorithm.html">
+      GeneticAlgorithm</a> provides an execution framework for Genetic Algorithms (GA).  
+      <a href="../apidocs/org/apache/commons/math/genetics/Population.html">
+      Populations,</a> consisting of <a href="../apidocs/org/apache/commons/math/genetics/Chromosome.html">
+      Chromosomes</a> are evolved by the <code>GeneticAlgorithm</code> until a 
+      <a href="../apidocs/org/apache/commons/math/genetics/StoppingCondition.html">
+      StoppingCondition</a> is reached. Evolution is determined by <a href="../apidocs/org/apache/commons/math/genetics/SelectionPolicy.html">
+      SelectionPolicy</a>, <a href="../apidocs/org/apache/commons/math/genetics/MutationPolicy.html">
+      MutationPolicy</a> and <a href="../apidocs/org/apache/commons/math/genetics/Fitness.html">
+      Fitness</a>.
+      </p>
+      <p>
+      The GA itself is implemented by the <code>evolve</code> method of the
+      <code>GeneticAlgorithm</code> class,
+      which looks like this:
+      <source>public Population evolve(Population initial, StoppingCondition condition) {
+    Population current = initial;
+    while (!condition.isSatisfied(current)) {
+        current = nextGeneration(current);
+    }
+    return current;
+}
+          </source>
+          The <code>nextGeneration</code> method implements the following algorithm:
+          <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 <code>SelectionPolicy</code> to select a pair of parents
+                 from <code>current</code></li>
+             <li>With probability = 
+                 <a href="../apidocs/org/apache/commons/math/genetics/GeneticAlgorithm.html#getCrossoverRate()">
+                 getCrossoverRate()</a>, apply configured <code>CrossoverPolicy</code> to parents</li>
+             <li>With probability = 
+                 <a href="../apidocs/org/apache/commons/math/genetics/GeneticAlgorithm.html#getMutationRate()">
+                 getMutationRate()</a>,
+                 apply configured <code>MutationPolicy</code> to each of the offspring</li>
+             <li>Add offspring individually to nextGeneration,
+                 space permitting</li>
+          </ul>
+          <li>Return nextGeneration</li>
+         </ol>  
+      </p>
+      </subsection>
+      <subsection name="14.3 Implementation">
+      <p>
+      Here is an example GA execution:
+      <source>
+// initialize a new genetic algorithm
+GeneticAlgorithm ga = new GeneticAlgorithm(
+    new OnePointCrossover<Integer>(),
+    1,
+    new RandomKeyMutation(),
+    0.10,
+    new TournamentSelection(TOURNAMENT_ARITY)
+);
+        
+// initial population
+Population initial = getInitialPopulation();
+        
+// stopping condition
+StoppingCondition stopCond = new FixedGenerationCount(NUM_GENERATIONS);
+        
+// run the algorithm
+Population finalPopulation = ga.evolve(initial, stopCond);
+        
+// best chromosome from the final population
+Chromosome bestFinal = finalPopulation.getFittestChromosome();
+        </source>
+        The arguments to the <code>GeneticAlgorithm</code> constructor above are: <br/>
+        <table>
+        <tr><th>Parameter</th><th>value in example</th><th>meaning</th></tr>
+        <tr><td>crossoverPolicy</td>
+        <td><a href="../apidocs/org/apache/commons/math/genetics/OnePointCrossover.html">OnePointCrossover</a></td>
+        <td>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.</td></tr>
+        <tr><td>crossoverRate</td>
+        <td>1</td>
+        <td>Always apply crossover</td></tr>
+        <tr><td>mutationPolicy</td>
+        <td><a href="../apidocs/org/apache/commons/math/genetics/RandomKeyMutation.html">RandomKeyMutation</a></td>
+        <td>Changes a randomly chosen element of the array representation to a random value uniformly distributed in [0,1].</td></tr>
+        <tr><td>mutationRate</td>
+        <td>.1</td>
+        <td>Apply mutation with probability 0.1 - that is, 10% of the time.</td></tr>
+        <tr><td>selectionPolicy</td>
+        <td><a href="../apidocs/org/apache/commons/math/genetics/TournamentSelection.html">TournamentSelection</a></td>
+        <td>Each of the two selected chromosomes is selected based on an n-ary tournament -- this is done by drawing
+        n random chromosomes without replacement from the population, and then selecting the fittest chromosome among them.</td></tr>
+        </table><br/>
+        The algorithm starts with an <code>initial</code> population of <code>Chromosomes.</code> and executes until 
+        the specified <a href="../apidocs/org/apache/commons/math/genetics/StoppingCondition.html">StoppingCondition</a>
+        is reached.  In the example above, a
+        <a href="../apidocs/org/apache/commons/math/genetics/FixedGenerationCount.html">FixedGenerationCount</a>
+        stopping condition is used, which means the algorithm proceeds through a fixed number of generations.
+      </p>
+      </subsection>
+    </section>
+  </body>
+</document>
diff --git a/src/site/xdoc/userguide/geometry.xml b/src/site/xdoc/userguide/geometry.xml
new file mode 100644
index 0000000..28aa8cc
--- /dev/null
+++ b/src/site/xdoc/userguide/geometry.xml
@@ -0,0 +1,141 @@
+<?xml version="1.0"?>
+
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+  
+<?xml-stylesheet type="text/xsl" href="./xdoc.xsl"?>
+<!-- $Revision: 937893 $ $Date: 2010-04-25 23:42:47 +0200 (dim. 25 avril 2010) $ -->
+<document url="geometry.html">
+
+  <properties>
+    <title>The Commons Math User Guide - Geometry</title>
+  </properties>
+
+  <body>
+    <section name="11 Geometry">
+      <subsection name="11.1 Overview" href="overview">
+        <p>
+           The geometry package provides classes useful for many physical simulations
+           in the real 3D space, namely vectors and rotations.
+        </p>
+      </subsection>
+      <subsection name="11.2 Vectors" href="vectors">
+        <p>
+          <a href="../apidocs/org/apache/commons/math/geometry/Vector3D.html">
+          Vector3D</a> provides a simple vector type. One important feature is
+          that instances of this class are guaranteed
+          to be immutable, this greatly simplifies modelling dynamical systems
+          with changing states: once a vector has been computed, a reference to it
+          is known to preserve its state as long as the reference itself is preserved.
+        </p>
+        <p>
+          Numerous constructors are available to create vectors. In addition to the
+          straightforward cartesian coordinates constructor, a constructor using
+          azimuthal coordinates can build normalized vectors and linear constructors
+          from one, two, three or four base vectors are also available. Constants have
+          been defined for the most commons vectors (plus and minus canonical axes,
+          null vector, and special vectors with infinite or NaN coordinates).
+        </p>
+        <p>
+          The generic vectorial space operations are available including dot product,
+          normalization, orthogonal vector finding and angular separation computation
+          which have a specific meaning in 3D. The 3D geometry specific cross product
+          is of course also implemented.
+        </p>
+        <p>
+          <a href="../apidocs/org/apache/commons/math/geometry/Vector3DFormat.html">
+          Vector3DFormat</a> is a specialized format for formatting output or parsing
+          input with text representation of 3D vectors.
+        </p>
+      </subsection>
+      <subsection name="11.3 Rotations" href="rotations">
+        <p>
+          <a href="../apidocs/org/apache/commons/math/geometry/Rotation.html">
+          Rotation</a> represents 3D rotations.
+          Rotation instances are also immutable objects, as Vector3D instances.
+        </p>
+        <p>
+          Rotations can be represented by several different mathematical
+          entities (matrices, axe and angle, Cardan or Euler angles,
+          quaternions). This class presents a higher level abstraction, more
+          user-oriented and hiding 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 implicitely 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 can be done using the
+          following single line of code:
+        </p>
+        <source>double[] angles = new Rotation(matrix, 1.0e-10).getAngles(RotationOrder.XYZ);</source>
+        <p>
+          Focus is oriented on what a rotation <em>does</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 vectors into other three
+          dimensional vectors. Depending on the application, the meaning of
+          these vectors may vary as well as the semantics of the rotation.
+        </p>
+        <p>
+          For example in a spacecraft attitude simulation tool, users will
+          often consider the vectors are fixed (say the Earth direction for
+          example) and the rotation transforms the coordinates coordinates of
+          this 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 (we have fixed vectors and moving frame).
+          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 (we have moving vectors in fixed frame). 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.
+        </p>
+        <p>
+          These examples show that a rotation means what the user wants it to
+          mean, so 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: <code>applyTo(Vector3D)</code> and
+          <code>applyInverseTo(Vector3D)</code>.
+        </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: <code>applyTo(Rotation)</code> and
+          <code>applyInverseTo(Rotation)</code>.
+        </p>
+      </subsection>
+     </section>
+  </body>
+</document>
diff --git a/src/site/xdoc/userguide/index.xml b/src/site/xdoc/userguide/index.xml
new file mode 100644
index 0000000..da6bdf2
--- /dev/null
+++ b/src/site/xdoc/userguide/index.xml
@@ -0,0 +1,147 @@
+<?xml version="1.0"?>
+
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+  
+<?xml-stylesheet type="text/xsl" href="./xdoc.xsl"?>
+<!-- $Revision: 920131 $ $Date: 2010-03-07 23:19:18 +0100 (dim. 07 mars 2010) $ -->
+<document url="index.html">
+  <properties>
+    <title>The Commons Math User Guide - Table of Contents</title>
+  </properties>
+
+  <body>
+      <section name="Table of Contents" href="toc">
+
+        <ul>
+            <li><a href="overview.html">0. Overview</a>
+            <ul>
+              <li><a href="overview.html#a0.1_About_the_User_Guide">0.1 About the User Guide</a></li>
+              <li><a href="overview.html#a0.2_Whats_in_commons-math">0.2 What's in commons-math</a></li>
+              <li><a href="overview.html#a0.3_How_commons-math_is_organized">0.3 How commons-math is organized</a></li>
+              <li><a href="overview.html#a0.4_How_interface_contracts_are_specified_in_commons-math_javadoc">0.4 How interface contracts are specified in commons-math javadoc</a></li>
+              <li><a href="overview.html#a0.5_Dependencies">0.5 Dependencies</a></li>
+            </ul></li>
+
+           <li><a href="stat.html">1. Statistics</a>
+                <ul>
+                <li><a href="stat.html#a1.1_Overview">1.1 Overview</a></li>
+                <li><a href="stat.html#a1.2_Descriptive_statistics">1.2 Descriptive statistics</a></li>
+                <li><a href="stat.html#a1.3_Frequency_distributions">1.3 Frequency distributions</a></li>
+                <li><a href="stat.html#a1.4_Simple_regression">1.4 Simple regression</a></li>
+                <li><a href="stat.html#a1.5_Multiple_linear_regression">1.5 Multiple Regression</a></li>
+                <li><a href="stat.html#a1.6_Rank_transformations">1.6 Rank transformations</a></li>
+                <li><a href="stat.html#a1.7_Covariance_and_correlation">1.7 Covariance and correlation</a></li>
+                <li><a href="stat.html#a1.8_Statistical_tests">1.8 Statistical Tests</a></li>
+                </ul></li>
+            <li><a href="random.html">2. Data Generation</a>
+                <ul>
+                <li><a href="random.html#a2.1_Overview">2.1 Overview</a></li>
+                <li><a href="random.html#a2.2_Random_numbers">2.2 Random numbers</a></li>
+                <li><a href="random.html#a2.3_Random_Vectors">2.3 Random Vectors</a></li>
+                <li><a href="random.html#a2.4_Random_Strings">2.4 Random Strings</a></li>
+                <li><a href="random.html#a2.5_Random_permutations_combinations_sampling">2.5 Random permutations, combinations, sampling</a></li>
+                <li><a href="random.html#a2.6_Generating_data_like_an_input_file">2.6 Generating data 'like' an input file</a></li>
+                <li><a href="random.html#a2.7_PRNG_Pluggability">2.7 PRNG Pluggability</a></li>
+                </ul></li>
+            <li><a href="linear.html">3. Linear Algebra</a>
+                <ul>
+                <li><a href="linear.html#a3.1_Overview">3.1 Overview</a></li>
+                <li><a href="linear.html#a3.2_Real_matrices">3.2 Real matrices</a></li>
+                <li><a href="linear.html#a3.3_Real_vectors">3.3 Real vectors</a></li>
+                <li><a href="linear.html#a3.4_Solving_linear_systems">3.4 Solving linear systems</a></li> 
+                <li><a href="linear.html#a3.5_Eigenvalueseigenvectors_and_singular_valuessingular_vectors">3.5 Eigenvalues/eigenvectors and singular values/singular vectors</a></li>
+                <li><a href="linear.html#a3.6_Non-real_fields_complex_fractions_...">3.6 Non-real fields (complex, fractions ...)</a></li>
+                </ul></li>             
+            <li><a href="analysis.html">4. Numerical Analysis</a>
+                <ul>
+                <li><a href="analysis.html#a4.1_Overview">4.1 Overview</a></li>
+                <li><a href="analysis.html#a4.2_Root-finding">4.2 Root-finding</a></li>                
+                <li><a href="analysis.html#a4.3_Interpolation">4.3 Interpolation</a></li>
+                <li><a href="analysis.html#a4.4_Integration">4.4 Integration</a></li>
+                <li><a href="analysis.html#a4.5_Polynomials">4.5 Polynomials</a></li>
+                </ul></li>     
+            <li><a href="special.html">5. Special Functions</a>
+                <ul>
+                <li><a href="special.html#a5.1_Overview">5.1 Overview</a></li>
+                <li><a href="special.html#a5.2_Erf_functions">5.2 Erf functions</a></li>
+                <li><a href="special.html#a5.3_Gamma_functions">5.3 Gamma functions</a></li>
+                <li><a href="special.html#a5.4_Beta_funtions">5.4 Beta funtions</a></li>
+                </ul></li>   
+        <li><a href="utilities.html">6. Utilities</a>
+                <ul>
+                <li><a href="utilities.html#a6.1_Overview">6.1 Overview</a></li>
+                <li><a href="utilities.html#a6.2_Double_array_utilities">6.2 Double array utilities</a></li>
+                <li><a href="utilities.html#a6.3_intdouble_hash_map">6.3 int/double hash map</a></li>
+                <li><a href="utilities.html#a6.4_Continued_Fractions">6.4 Continued Fractions</a></li>
+                <li><a href="utilities.html#a6.5_binomial_coefficients_factorials_and_other_common_math_functions">6.5 binomial coefficients, factorials and other common math functions</a></li>
+                </ul></li>                                 
+        <li><a href="complex.html">7. Complex Numbers</a>
+                <ul>
+                <li><a href="complex.html#a7.1_Overview">7.1 Overview</a></li>
+                <li><a href="complex.html#a7.2_Complex_Numbers">7.2 Complex Numbers</a></li>
+                <li><a href="complex.html#a7.3_Complex_Transcendental_Functions">7.3 Complex Transcendental Functions</a></li>
+                <li><a href="complex.html#a7.4_Complex_Formatting_and_Parsing">7.4 Complex Formatting and Parsing</a></li>
+                </ul></li>                                 
+        <li><a href="distribution.html">8. Probability Distributions</a>
+                <ul>
+                <li><a href="distribution.html#a8.1_Overview">8.1 Overview</a></li>
+                <li><a href="distribution.html#a8.2_Distribution_Framework">8.2 Distribution Framework</a></li>
+                <li><a href="distribution.html#a8.3_User_Defined_Distributions">8.3 User Defined Distributions</a></li>
+                </ul></li>                                 
+        <li><a href="fraction.html">9. Fractions</a>
+                <ul>
+                <li><a href="fraction.html#a9.1_Overview">9.1 Overview</a></li>
+                <li><a href="fraction.html#a9.2_Fraction_Numbers">9.2 Fraction Numbers</a></li>
+                <li><a href="fraction.html#a9.3_Fraction_Formatting_and_Parsing">9.3 Fraction Formatting and Parsing</a></li>
+                </ul></li>                                 
+        <li><a href="transform.html">10. Transform methods</a></li>                                 
+        <li><a href="geometry.html">11. Geometry</a>
+                <ul>
+                <li><a href="geometry.html#a11.1_Overview">11.1 Overview</a></li>
+                <li><a href="geometry.html#a11.2_Vectors">11.2 Vectors</a></li>
+                <li><a href="geometry.html#a11.3_Rotations">11.3 Rotations</a></li>
+                </ul></li>                                 
+        <li><a href="optimization.html">12. Optimization</a>
+                <ul>
+                <li><a href="optimization.html#a12.1_Overview">12.1 Overview</a></li>
+                <li><a href="analysis.html#a12.2_Univariate_Functions">12.2 Univariate Functions</a></li>
+                <li><a href="analysis.html#a12.3_Linear_Programming">12.3 Linear Programming</a></li>
+                <li><a href="optimization.html#a12.4_Direct_Methods">12.4 Direct Methods</a></li>
+                <li><a href="analysis.html#a12.5_General_Case">12.5 General Case</a></li>
+                <li><a href="analysis.html#a12.6_Curve_Fitting">12.6 Curve Fitting</a></li>
+                </ul></li>                                 
+        <li><a href="ode.html">13. Ordinary Differential Equations Integration</a>
+                <ul>
+                <li><a href="ode.html#a13.1_Overview">13.1 Overview</a></li>
+                <li><a href="ode.html#a13.2_Continuous_Output">13.2 Continuous Output</a></li>
+                <li><a href="ode.html#a13.3_Discrete_Events_Handling">13.3 Discrete Events Handling</a></li>
+                <li><a href="ode.html#a13.4_Available_Integrators">13.4 Available Integrators</a></li>
+                <li><a href="ode.html#a13.5_Derivatives">13.5 Derivatives</a></li>
+                </ul></li>
+        <li><a href="genetics.html">14. Genetic Algorithms</a>   
+                <ul>
+                <li><a href="genetics.html#a14.1_Overview">14.1 Overview</a></li>
+                <li><a href="genetics.html#a14.2_GA_Framework">14.2 GA Framework</a></li>
+                <li><a href="genetics.html#a14.3_Implementation">14.3 Implementation and Examples</a></li>  
+                </ul></li>                           
+        </ul>
+      </section>
+    
+  </body>
+  
+</document>
diff --git a/src/site/xdoc/userguide/linear.xml b/src/site/xdoc/userguide/linear.xml
new file mode 100644
index 0000000..8685c39
--- /dev/null
+++ b/src/site/xdoc/userguide/linear.xml
@@ -0,0 +1,205 @@
+<?xml version="1.0"?>
+
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+  
+<?xml-stylesheet type="text/xsl" href="./xdoc.xsl"?>
+<!-- $Revision: 783702 $ $Date: 2009-06-11 10:54:02 +0200 (jeu. 11 juin 2009) $ -->
+<document url="linear.html">
+
+  <properties>
+    <title>The Commons Math User Guide - Linear Algebra</title>
+  </properties>
+
+  <body>
+    <section name="3 Linear Algebra">
+      <subsection name="3.1 Overview" href="overview">
+        <p>
+           Linear algebra support in commons-math provides operations on real matrices
+           (both dense and sparse matrices are supported) and vectors. It features basic
+           operations (addition, subtraction ...) and decomposition algorithms that can
+           be used to solve linear systems either in exact sense and in least squares sense.
+        </p>
+      </subsection>
+      <subsection name="3.2 Real matrices" href="real_matrices">
+        <p>
+          The <a href="../apidocs/org/apache/commons/math/linear/RealMatrix.html">
+          RealMatrix</a> interface represents a matrix with real numbers as 
+          entries.  The following basic matrix operations are supported:
+          <ul>
+          <li>Matrix addition, subtraction, multiplication</li>
+          <li>Scalar addition and multiplication</li>
+          <li>transpose</li>
+          <li>Norm and Trace</li>
+          <li>Operation on a vector</li>
+          </ul>   
+        </p>
+        <p>
+         Example:
+         <source>
+// Create a real matrix with two rows and three columns
+double[][] matrixData = { {1d,2d,3d}, {2d,5d,3d}};
+RealMatrix m = new Array2DRowRealMatrix(matrixData);
+
+// One more with three rows, two columns
+double[][] matrixData2 = { {1d,2d}, {2d,5d}, {1d, 7d}};
+RealMatrix n = new Array2DRowRealMatrix(matrixData2);
+
+// Note: The constructor copies  the input double[][] array.
+
+// Now multiply m by n
+RealMatrix p = m.multiply(n);
+System.out.println(p.getRowDimension());    // 2
+System.out.println(p.getColumnDimension()); // 2
+
+// Invert p, using LU decomposition
+RealMatrix pInverse = new LUDecompositionImpl(p).getSolver().getInverse();
+         </source>
+        </p>
+        <p>
+        The three main implementations of the interface are <a
+        href="../apidocs/org/apache/commons/math/linear/Array2DRowRealMatrix.html">
+        Array2DRowRealMatrix</a> and <a
+        href="../apidocs/org/apache/commons/math/linear/BlockRealMatrix.html">
+        BlockRealMatrix</a> for dense matrices (the second one being more suited to
+        dimensions above 50 or 100) and <a
+        href="../apidocs/org/apache/commons/math/linear/SparseRealMatrix.html">
+        SparseRealMatrix</a> for sparse matrices.
+        </p>
+      </subsection>
+      <subsection name="3.3 Real vectors" href="real_vectors">
+        <p>
+          The <a href="../apidocs/org/apache/commons/math/linear/RealVector.html">
+          RealVector</a> interface represents a vector with real numbers as 
+          entries.  The following basic matrix operations are supported:
+          <ul>
+          <li>Vector addition, subtraction</li>
+          <li>Element by element multiplication, division</li>
+          <li>Scalar addition, subtraction, multiplication, division and power</li>
+          <li>Mapping of mathematical functions (cos, sin ...)</li>
+          <li>Dot product, outer product</li>
+          <li>Distance and norm according to norms L1, L2 and Linf</li>
+          </ul>
+        </p>
+        <p>
+          The <a href="../apidocs/org/apache/commons/math/linear/RealVectorFormat.html">
+          RealVectorFormat</a> class handles input/output of vectors in a customizable
+          textual format.
+        </p>
+      </subsection>
+      <subsection name="3.4 Solving linear systems" href="solve">
+        <p>
+          The <code>solve()</code> methods of the <a
+          href="../apidocs/org/apache/commons/math/linear/DecompositionSolver.html">DecompositionSolver</a>
+          interface support solving linear systems of equations of the form AX=B, either
+          in linear sense or in least square sense. A <code>RealMatrix</code> instance is
+          used to represent the coefficient matrix of the system. Solving the system is a
+          two phases process: first the coefficient matrix is decomposed in some way and
+          then a solver built from the decomposition solves the system. This allows to
+          compute the decomposition and build the solver only once if several systems have
+          to be solved with the same coefficient matrix.
+        </p>
+        <p>
+          For example, to solve the linear system
+          <pre>
+           2x + 3y - 2z = 1
+           -x + 7y + 6x = -2
+           4x - 3y - 5z = 1
+          </pre>
+          Start by decomposing the coefficient matrix A (in this case using LU decomposition)
+          and build a solver
+          <source>
+RealMatrix coefficients =
+    new Array2DRowRealMatrix(new double[][] { { 2, 3, -2 }, { -1, 7, 6 }, { 4, -3, -5 } },
+                       false);
+DecompositionSolver solver = new LUDecompositionImpl(coefficients).getSolver();
+          </source>
+          Next create a <code>RealVector</code> array to represent the constant
+          vector B and use <code>solve(RealVector)</code> to solve the system
+          <source>
+RealVector constants = new RealVectorImpl(new double[] { 1, -2, 1 }, false);
+RealVector solution = solver.solve(constants);
+          </source>
+          The <code>solution</code> vector will contain values for x
+          (<code>solution.getEntry(0)</code>), y (<code>solution.getEntry(1)</code>), 
+          and z (<code>solution.getEntry(2)</code>) that solve the system.
+        </p>
+        <p>
+          Each type of decomposition has its specific semantics and constraints on
+          the coefficient matrix as shown in the following table. For algorithms that
+          solve AX=B in least squares sense the value returned for X is such that the
+          residual AX-B has minimal norm. If an exact solution exist (i.e. if for some
+          X the residual AX-B is exactly 0), then this exact solution is also the solution
+          in least square sense. This implies that algorithms suited for least squares
+          problems can also be used to solve exact problems, but the reverse is not true. 
+        </p>
+        <p>
+          <table border="1" align="center">
+          <tr BGCOLOR="#CCCCFF"><td colspan="2"><font size="+2">Decomposition algorithms</font></td></tr>
+          <tr BGCOLOR="#EEEEFF"><font size="+1"><td>Name</td><td>coefficients matrix</td><td>problem type</td></font></tr>
+          <tr><td><a href="../apidocs/org/apache/commons/math/linear/LUDecomposition.html">LU</a></td><td>square</td><td>exact solution only</td></tr>
+          <tr><td><a href="../apidocs/org/apache/commons/math/linear/CholeskyDecomposition.html">Cholesky</a></td><td>symmetric positive definite</td><td>exact solution only</td></tr>
+          <tr><td><a href="../apidocs/org/apache/commons/math/linear/QRDecomposition.html">QR</a></td><td>any</td><td>least squares solution</td></tr>
+          <tr><td><a href="../apidocs/org/apache/commons/math/linear/EigenDecomposition.html">eigen decomposition</a></td><td>square</td><td>exact solution only</td></tr>
+          <tr><td><a href="../apidocs/org/apache/commons/math/linear/SingularValueDecomposition.html">SVD</a></td><td>any</td><td>least squares solution</td></tr>
+          </table>
+        </p>
+        <p>
+          It is possible to use a simple array of double instead of a <code>RealVector</code>.
+          In this case, the solution will be provided also as an array of double.
+        </p>
+        <p>
+          It is possible to solve multiple systems with the same coefficient matrix 
+          in one method call.  To do this, create a matrix whose column vectors correspond 
+          to the constant vectors for the systems to be solved and use <code>solve(RealMatrix),</code>
+          which returns a matrix with column vectors representing the solutions.
+        </p>
+      </subsection>
+      <subsection name="3.5 Eigenvalues/eigenvectors and singular values/singular vectors" href="eigen">
+        <p>
+          Decomposition algorithms may be used for themselves and not only for linear system solving.
+          This is of prime interest with eigen decomposition and singular value decomposition.
+        </p>
+        <p>
+          The <code>getEigenvalue()</code>, <code>getEigenvalues()</code>, <code>getEigenVector()</code>,
+          <code>getV()</code>, <code>getD()</code> and <code>getVT()</code> methods of the
+          <code>EigenDecomposition</code> interface support solving eigenproblems of the form
+          AX = lambda X where lambda is a real scalar.
+        </p>
+        <p>The <code>getSingularValues()</code>, <code>getU()</code>, <code>getS()</code> and
+        <code>getV()</code> methods of the <code>SingularValueDecomposition</code> interface
+        allow to solve singular values problems of the form AXi = lambda Yi where lambda is a
+        real scalar, and where the Xi and Yi vectors form orthogonal bases of their respective
+        vector spaces (which may have different dimensions).
+        </p>
+      </subsection>
+      <subsection name="3.6 Non-real fields (complex, fractions ...)" href="field">
+        <p>
+          In addition to the real field, matrices and vectors using non-real <a
+          href="../apidocs/org/apache/commons/math/FieldElement.html">field elements</a> can be used.
+          The fields already supported by the library are:
+          <ul>
+            <li><a href="../apidocs/org/apache/commons/math/complex/Complex.html">Complex</a></li>
+            <li><a href="../apidocs/org/apache/commons/math/fraction/Fraction.html">Fraction</a></li>
+            <li><a href="../apidocs/org/apache/commons/math/fraction/BigFraction.html">BigFraction</a></li>
+            <li><a href="../apidocs/org/apache/commons/math/util/BigReal.html">BigReal</a></li>
+          </ul>
+        </p>
+      </subsection>
+    </section>
+  </body>
+</document>
diff --git a/src/site/xdoc/userguide/ode.xml b/src/site/xdoc/userguide/ode.xml
new file mode 100644
index 0000000..71caf7b
--- /dev/null
+++ b/src/site/xdoc/userguide/ode.xml
@@ -0,0 +1,440 @@
+<?xml version="1.0"?>
+
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+  
+<?xml-stylesheet type="text/xsl" href="./xdoc.xsl"?>
+<!-- $Revision: 1037327 $ $Date: 2010-11-20 21:57:37 +0100 (sam. 20 nov. 2010) $ -->
+<document url="ode.html">
+
+  <properties>
+    <title>The Commons Math User Guide - Ordinary Differential Equations Integration</title>
+  </properties>
+
+  <body>
+    <section name="13 Ordinary Differential Equations Integration">
+      <subsection name="13.1 Overview" href="overview">
+        <p>
+          The ode package provides classes to solve Ordinary Differential Equations problems.
+        </p>
+        <p>
+          This package solves Initial Value Problems of the form y'=f(t,y) with t<sub>0</sub>
+          and y(t<sub>0</sub>)=y<sub>0</sub> known. The provided integrators compute an estimate
+          of y(t) from t=t<sub>0</sub> to t=t<sub>1</sub>.
+        </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 both the state and its derivative
+          between the time steps. They do so through classes extending the
+          <a href="../apidocs/org/apache/commons/math/ode/sampling/StepInterpolator.html">StepInterpolator</a>
+          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
+          (occurring when the sign of user-supplied <i>switching function</i> changes). 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>
+          All integrators support setting a maximal number of evaluations of differential
+          equations function. If this number is exceeded, an exception will be thrown during
+          integration. This can be used to prevent infinite loops if for example error control or
+          discrete events create a really large number of extremely small steps. By default, the
+          maximal number of evaluation is set to <code>Integer.MAX_VALUE</code> (i.e. 2<sup>31</sup>-1
+          or 2147483647). It is recommended to set this maximal number to a value suited to the ODE
+          problem, integration range, and step size or error control settings.
+        </p>
+        <p>
+          The user should describe his problem in his own classes which should implement the
+          <a href="../apidocs/org/apache/commons/math/ode/FirstOrderDifferentialEquations.html">FirstOrderDifferentialEquations</a>
+          interface. Then he should pass it to the integrator he prefers among all the classes that implement
+          the <a href="../apidocs/org/apache/commons/math/ode/FirstOrderIntegrator.html">FirstOrderIntegrator</a>
+          interface. The following example shows how to implement the simple two-dimensional problem:
+          <ul>
+            <li>y'<sub>0</sub>(t) = &#x3c9; &#xD7; (c<sub>1</sub> - y<sub>1</sub>(t))</li>
+            <li>y'<sub>1</sub>(t) = &#x3c9; &#xD7; (y<sub>0</sub>(t) - c<sub>0</sub>)</li>
+          </ul>
+          with some initial state y(t<sub>0</sub>) = (y<sub>0</sub>(t<sub>0</sub>), y<sub>1</sub>(t<sub>0</sub>)).
+          In fact, the exact solution of this problem is that y(t) moves along a circle
+          centered at c = (c<sub>0</sub>, c<sub>1</sub>) with constant angular rate &#x3c9;.
+        </p>
+        <source>
+private static class CircleODE implements FirstOrderDifferentialEquations {
+
+    private double[] c;
+    private double omega;
+
+    public CircleODE(double[] c, double omega) {
+        this.c     = c;
+        this.omega = omega;
+    }
+
+    public int getDimension() {
+        return 2;
+    }
+
+    public void computeDerivatives(double t, double[] y, double[] yDot) {
+        yDot[0] = omega * (c[1] - y[1]);
+        yDot[1] = omega * (y[0] - c[0]);
+    }
+
+}
+        </source>
+        <p>
+          Computing the state y(16.0) starting from y(0.0) = (0.0, 1.0) and integrating the ODE
+          is done as follows (using Dormand-Prince 8(5,3) integrator as an example):
+        </p>
+        <source>
+FirstOrderIntegrator dp853 = new DormandPrince853Integrator(1.0e-8, 100.0, 1.0e-10, 1.0e-10);
+FirstOrderDifferentialEquations ode = new CircleODE(new double[] { 1.0, 1.0 }, 0.1);
+double[] y = new double[] { 0.0, 1.0 }; // initial state
+dp853.integrate(ode, 0.0, y, 16.0, y); // now y contains final state at time t=16.0
+        </source>
+      </subsection>
+      <subsection name="13.2 Continuous Output" href="continuous">
+        <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 y array of the
+          <code>FirstOrderIntegrator.integrate</code> method, as shown by previous example. 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
+          <a href="../apidocs/org/apache/commons/math/ode/sampling/StepHandler.html">StepHandler</a> interface or a
+          <a href="../apidocs/org/apache/commons/math/ode/sampling/StepNormalizer.html">StepNormalizer</a> object wrapping
+          a user-specified object implementing the
+          <a href="../apidocs/org/apache/commons/math/ode/sampling/FixedStepHandler.html">FixedStepHandler</a> interface
+          into the integrator before calling the <code>FirstOrderIntegrator.integrate</code> 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. Considering again the previous example, we want to print the
+          trajectory of the point to check it really is a circle arc. We simply add the following before the call
+          to integrator.integrate:
+        </p>
+        <source>
+StepHandler stepHandler = new StepHandler() {
+    public void reset() {}
+            
+    public boolean requiresDenseOutput() { return false; }
+            
+    public void handleStep(StepInterpolator interpolator, boolean isLast) throws MathUserException {
+        double   t = interpolator.getCurrentTime();
+        double[] y = interpolator.getInterpolatedState();
+        System.out.println(t + " " + y[0] + " " + y[1]);
+    }
+};
+integrator.addStepHandler(stepHandler);
+        </source>
+        <p>
+          <a href="../apidocs/org/apache/commons/math/ode/ContinuousOutputModel.html">ContinuousOutputModel</a>
+          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 file system 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 <a href="../apidocs/org/apache/commons/math/ode/sampling/StepHandler.html">StepHandler</a>
+          interface are available for general needs
+          (<a href="../apidocs/org/apache/commons/math/ode/sampling/DummyStepHandler.html">DummyStepHandler</a>,
+          <a href="../apidocs/org/apache/commons/math/ode/sampling/StepNormalizer.html">StepNormalizer</a>) 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
+          <a href="../apidocs/org/apache/commons/math/ode/AdaptiveStepsizeIntegrator.html">AdaptiveStepsizeIntegrator</a>
+          abstract class). In this case, the step handler which is called after each successful step shows up
+          the variable stepsize. The <a href="../apidocs/org/apache/commons/math/ode/sampling/StepNormalizer.html">StepNormalizer</a>
+          class can be used to convert the variable stepsize into a fixed stepsize that can be handled by classes
+          implementing the <a href="../apidocs/org/apache/commons/math/ode/sampling/FixedStepHandler.html">FixedStepHandler</a>
+          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>
+      </subsection>
+      <subsection name="13.3 Discrete Events Handling" href="events">
+        <p>
+          ODE problems are continuous ones. However, sometimes discrete events must be
+          taken into account. The most frequent case is the stop condition of the integrator
+          is not defined by the time t but by a target condition on state y (say y[0] = 1.0
+          for example).
+        </p>
+        <p>
+          Discrete events detection is based on switching functions. The user provides
+          a simple <a href="../apidocs/org/apache/commons/math/ode/events/EventHandler.html">g(t, y)</a>
+          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). Note that
+          g function signs changes at the very beginning of the integration (from t<sub>0</sub>
+          to t<sub>0</sub> + &#x3b5; where &#x3b5; is the events detection convergence threshold)
+          are explicitely ignored. This prevents having the integration stuck at its
+          initial point when a new integration is restarted just at the same point a
+          previous one had been stopped by an event.
+        </p>
+        <p>
+          When an event is triggered, the event time, current state and an indicator
+          whether the switching function was increasing or decreasing at event time
+          are provided to the user. Several different options are available to him:
+        </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:
+        </p>
+        <source>
+public double g(double t, double[] y) {
+  return y[0] - targetConcentration;
+}
+
+public int eventOccurred(double t, double[] y, boolean increasing) {
+  return STOP;
+}
+       </source>
+
+       <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 maneuvers 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:
+       </p>
+       <source>
+public double g(double t, double[] y) {
+  return (t - tManeuverStart) * (t - tManeuverStop);
+}
+
+public int eventOccurred(double t, double[] y, boolean increasing) {
+  return RESET_DERIVATIVES;
+}
+        </source>
+
+        <p>
+          The third case is useful mainly for monitoring purposes, a simple example is:
+        </p>
+        <source>
+public double g(double t, double[] y) {
+  return y[0] - y[1];
+}
+
+public int eventOccurred(double t, double[] y, boolean increasing) {
+  logger.log("y0(t) and y1(t) curves cross at t = " + t);
+  return CONTINUE;
+}
+        </source>
+      </subsection>
+      <subsection name="13.4 Available Integrators" href="integrators">
+        <p>
+          The tables below show the various integrators available for non-stiff problems. Note that the
+          implementations of Adams-Bashforth and Adams-Moulton are adaptive stepsize, not fixed stepsize
+          as is usual for these multi-step integrators. This is due to the fact the implementation relies
+          on the Nordsieck vector representation of the state.
+        </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><a href="../apidocs/org/apache/commons/math/ode/nonstiff/EulerIntegrator.html">Euler</a></td><td>1</td></tr>
+          <tr><td><a href="../apidocs/org/apache/commons/math/ode/nonstiff/MidpointIntegrator.html">Midpoint</a></td><td>2</td></tr>
+          <tr><td><a href="../apidocs/org/apache/commons/math/ode/nonstiff/ClassicalRungeKuttaIntegrator.html">Classical Runge-Kutta</a></td><td>4</td></tr>
+          <tr><td><a href="../apidocs/org/apache/commons/math/ode/nonstiff/GillIntegrator.html">Gill</a></td><td>4</td></tr>
+          <tr><td><a href="../apidocs/org/apache/commons/math/ode/nonstiff/ThreeEighthesIntegrator.html">3/8</a></td><td>4</td></tr>
+          </table>
+        </p>
+        <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><a href="../apidocs/org/apache/commons/math/ode/nonstiff/HighamHall54Integrator.html">Higham and Hall</a></td><td>5</td><td>4</td></tr>
+          <tr><td><a href="../apidocs/org/apache/commons/math/ode/nonstiff/DormandPrince54Integrator.html">Dormand-Prince 5(4)</a></td><td>5</td><td>4</td></tr>
+          <tr><td><a href="../apidocs/org/apache/commons/math/ode/nonstiff/DormandPrince853Integrator.html">Dormand-Prince 8(5,3)</a></td><td>8</td><td>5 and 3</td></tr>
+          <tr><td><a href="../apidocs/org/apache/commons/math/ode/nonstiff/GraggBulirschStoerIntegrator.html">Gragg-Bulirsch-Stoer</a></td><td>variable (up to 18 by default)</td><td>variable</td></tr>
+          <tr><td><a href="../apidocs/org/apache/commons/math/ode/nonstiff/AdamsBashforthIntegrator.html">Adams-Bashforth</a></td><td>variable</td><td>variable</td></tr>
+          <tr><td><a href="../apidocs/org/apache/commons/math/ode/nonstiff/AdamsMoultonIntegrator.html">Adams-Moulton</a></td><td>variable</td><td>variable</td></tr>
+          </table>
+        </p>
+      </subsection>
+      <subsection name="13.5 Derivatives" href="derivatives">
+        <p>
+          If in addition to state y(t) the user needs to compute the sensitivity of the state to
+          the initial state or some parameter of the ODE, he will use the classes and interfaces
+          from the <a
+          href="../apidocs/org/apache/commons/math/ode/jacobians/package-summary.html">org.apache.commons.ode.jacobians</a>
+          package instead of the top level ode package. These classes compute the jacobians
+          dy(t)/dy<sub>0</sub> and dy(t)/dp where y<sub>0</sub> is the initial state
+          and p is some ODE parameter.
+        </p>
+        <p>
+          The classes and interfaces in this package mimic the behavior of the classes and
+          interfaces of the top level ode package, only adding parameters arrays for the jacobians.
+          The behavior of these classes is to create a compound state vector z containing both
+          the state y(t) and its derivatives dy(t)/dy<sub>0</sub> and dy(t)/dp and
+          to set up an extended problem by adding the equations for the jacobians automatically.
+          These extended state and problems are then provided to a classical underlying integrator
+          chosen by user.
+        </p>
+        <p>
+          This behavior imply there will be a top level integrator knowing about state and jacobians
+          and a low level integrator knowing only about compound state (which may be big). If the user
+          wants to deal with the top level only, he will use the specialized step handler and event
+          handler classes registered at top level. He can also register classical step handlers and
+          event handlers, but in this case will see the big compound state. This 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 will be the jacobian with respect to parameters. Dealing with low level
+          step handlers and 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.
+        </p>
+        <p>
+          In order to compute dy(t)/dy<sub>0</sub> and dy(t/dp for any t, the algorithm
+          needs not only the ODE function f such that y'=f(t,y) but also its local jacobians
+          df(t, y, p)/dy and df(t, y, p)/dp.
+        </p>
+        <p>
+          If the function f is too complex, the user can simply rely on internal differentiation
+          using finite differences to compute these local jacobians. So rather than the <a
+          href="../apidocs/org/apache/commons/math/ode/FirstOrderDifferentialEquations.html">FirstOrderDifferentialEquations</a>
+          interface he will implement the <a
+          href="../apidocs/org/apache/commons/math/ode/jacobians/ParameterizedODE.html">ParameterizedODE</a>
+          interface. Considering again our example where only &#x3c9; is considered a parameter, we get:
+        </p>
+        <source>
+public class BasicCircleODE implements ParameterizedODE {
+
+    private double[] c;
+    private double omega;
+
+    public BasicCircleODE(double[] c, double omega) {
+        this.c     = c;
+        this.omega = omega;
+    }
+
+    public int getDimension() {
+        return 2;
+    }
+
+    public void computeDerivatives(double t, double[] y, double[] yDot) {
+        yDot[0] = omega * (c[1] - y[1]);
+        yDot[1] = omega * (y[0] - c[0]);
+    }
+
+    public int getParametersDimension() {
+        // we are only interested in the omega parameter
+        return 1;
+    }
+
+    public void setParameter(int i, double value) {
+        omega = value;
+    }
+
+}
+        </source>
+        <p>
+          This ODE is provided to the specialized integrator with two arrays specifying the
+          step sizes to use for finite differences (one array for derivation with respect to
+          state y, one array for derivation with respect to parameters p):
+        </p>
+        <source>
+double[] hY = new double[] { 0.001, 0.001 };
+double[] hP = new double[] { 1.0e-6 };
+FirstOrderIntegratorWithJacobians integrator = new FirstOrderIntegratorWithJacobians(dp853, ode, hY, hP);
+integrator.integrate(t0, y0, dy0dp, t, y, dydy0, dydp);
+        </source>
+        <p>
+          If the function f is simple, the user can simply provide the local jacobians
+          by himself. So rather than the <a
+          href="../apidocs/org/apache/commons/math/ode/FirstOrderDifferentialEquations.html">FirstOrderDifferentialEquations</a>
+          interface he will implement the <a
+          href="../apidocs/org/apache/commons/math/ode/jacobians/ODEWithJacobians.html">ODEWithJacobians</a>
+          interface. Considering again our example where only &#x3c9; is considered a parameter, we get:
+        </p>
+        <source>
+public class EnhancedCircleODE implements ODEWithJacobians {
+
+    private double[] c;
+    private double omega;
+
+    public EnhancedCircleODE(double[] c, double omega) {
+        this.c     = c;
+        this.omega = omega;
+    }
+
+    public int getDimension() {
+        return 2;
+    }
+
+    public void computeDerivatives(double t, double[] y, double[] yDot) {
+        yDot[0] = omega * (c[1] - y[1]);
+        yDot[1] = omega * (y[0] - c[0]);
+    }
+
+    public int getParametersDimension() {
+        // we are only interested in the omega parameter
+        return 1;
+    }
+
+    public void computeJacobians(double t, double[] y, double[] yDot, double[][] dFdY, double[][] dFdP) {
+
+        dFdY[0][0] = 0;
+        dFdY[0][1] = -omega;
+        dFdY[1][0] = omega;
+        dFdY[1][1] = 0;
+
+        dFdP[0][0] = 0;
+        dFdP[0][1] = omega;
+        dFdP[0][2] = c[1] - y[1];
+        dFdP[1][0] = -omega;
+        dFdP[1][1] = 0;
+        dFdP[1][2] = y[0] - c[0];
+ 
+    }
+
+}
+        </source>
+        <p>
+          This ODE is provided to the specialized integrator as is:
+        </p>
+        <source>
+FirstOrderIntegratorWithJacobians integrator = new FirstOrderIntegratorWithJacobians(dp853, ode);
+integrator.integrate(t0, y0, dy0dp, t, y, dydy0, dydp);
+        </source>
+      </subsection>
+     </section>
+  </body>
+</document>
diff --git a/src/site/xdoc/userguide/optimization.xml b/src/site/xdoc/userguide/optimization.xml
new file mode 100644
index 0000000..66f3888
--- /dev/null
+++ b/src/site/xdoc/userguide/optimization.xml
@@ -0,0 +1,271 @@
+<?xml version="1.0"?>
+
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+  
+<?xml-stylesheet type="text/xsl" href="./xdoc.xsl"?>
+<!-- $Revision: 980039 $ $Date: 2010-07-28 14:48:48 +0200 (mer. 28 juil. 2010) $ -->
+<document url="optimization.html">
+
+  <properties>
+    <title>The Commons Math User Guide - Optimization</title>
+  </properties>
+
+  <body>
+    <section name="12 Optimization">
+      <subsection name="12.1 Overview" href="overview">
+        <p>
+          The optimization package provides algorithms to optimize (i.e. either minimize
+          or maximize) some objective or cost function. The package is split in several
+          sub-packages dedicated to different kind of functions or algorithms.
+          <ul>
+            <li>the univariate package handles univariate scalar functions,</li>
+            <li>the linear package handles multivariate vector linear functions
+                with linear constraints,</li>
+            <li>the direct package handles multivariate scalar functions
+            using direct search methods (i.e. not using derivatives),</li>
+            <li>the general package handles multivariate scalar or vector functions
+            using derivatives.</li>
+            <li>the fitting package handles curve fitting by univariate real functions</li>
+          </ul>
+        </p>
+        <p>
+        The top level optimization package provides common interfaces for the optimization
+        algorithms provided in sub-packages. The main interfaces defines 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>
+        The type of goal, i.e. minimization or maximization, is defined by the enumerated
+        <a href="../apidocs/org/apache/commons/math/optimization/GoalType.html">
+        GoalType</a> which has only two values: <code>MAXIMIZE</code> and <code>MINIMIZE</code>.
+        </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><a href="../apidocs/org/apache/commons/math/optimization/UnivariateRealOptimizer.html">
+              UnivariateRealOptimizer</a> for <a
+              href="../apidocs/org/apache/commons/math/analysis/UnivariateRealFunction.html">
+              univariate real functions</a></li>
+          <li><a href="../apidocs/org/apache/commons/math/optimization/MultivariateRealOptimizer.html">
+              MultivariateRealOptimizer</a> for <a
+              href="../apidocs/org/apache/commons/math/analysis/MultivariateRealFunction.html">
+              multivariate real functions</a></li>
+          <li><a href="../apidocs/org/apache/commons/math/optimization/DifferentiableMultivariateRealOptimizer.html">
+              DifferentiableMultivariateRealOptimizer</a> for <a
+              href="../apidocs/org/apache/commons/math/analysis/DifferentiableMultivariateRealFunction.html">
+              differentiable multivariate real functions</a></li>
+          <li><a href="../apidocs/org/apache/commons/math/optimization/DifferentiableMultivariateVectorialOptimizer.html">
+              DifferentiableMultivariateVectorialOptimizer</a> for <a
+              href="../apidocs/org/apache/commons/math/analysis/DifferentiableMultivariateVectorialFunction.html">
+              differentiable multivariate vectorial functions</a></li>
+        </ul>
+        </p>
+
+        <p>
+        Despite there are only four types of supported optimizers, it is possible to optimize
+        a transform a <a
+        href="../apidocs/org/apache/commons/math/analysis/MultivariateVectorialFunction.html">
+        non-differentiable multivariate vectorial function</a> by converting it to a <a
+        href="../apidocs/org/apache/commons/math/analysis/MultivariateRealFunction.html">
+        non-differentiable multivariate real function</a> thanks to the <a
+        href="../apidocs/org/apache/commons/math/optimization/LeastSquaresConverter.html">
+        LeastSquaresConverter</a> helper class. The transformed function can be optimized using
+        any implementation of the <a
+        href="../apidocs/org/apache/commons/math/optimization/MultivariateRealOptimizer.html">
+        MultivariateRealOptimizer</a> 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>
+      </subsection>
+      <subsection name="12.2 Univariate Functions" href="univariate">
+        <p>
+          A <a href="../apidocs/org/apache/commons/math/optimization/UnivariateRealOptimizer.html">
+          UnivariateRealOptimizer</a> is used to find the minimal values of a univariate real-valued
+          function <code>f</code>.
+        </p>
+        <p>
+          These algorithms usage is very similar to root-finding algorithms usage explained
+          in the analysis package. The main difference is that the <code>solve</code> methods in root
+          finding algorithms is replaced by <code>optimize</code> methods.
+        </p>
+      </subsection>
+      <subsection name="12.3 Linear Programming" href="linear">
+        <p>
+          This package provides an implementation of George Dantzig's simplex algorithm
+          for solving linear optimization problems with linear equality and inequality
+          constraints.
+        </p>
+      </subsection>
+      <subsection name="12.4 Direct Methods" href="direct">
+        <p>
+          Direct search methods only use cost 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 cost 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 instances can be built either in single-start or in
+          multi-start mode. Multi-start is a traditional way to try to avoid
+          being trapped in a local minimum and miss the global minimum of a
+          function. It can also be used to verify the convergence of an
+          algorithm. In multi-start mode, the <code>minimizes</code>method
+          returns the best minimum found after all starts, and the <code>etMinima</code>
+          method can be used to retrieve all minima from all starts (including the one
+          already provided by the <code>minimizes</code> method).
+        </p>
+        <p>
+          The <code>direct</code> package provides two solvers. The first one is the classical
+          <a href="../apidocs/org/apache/commons/math/optimization/direct/NelderMead.html">
+          Nelder-Mead</a> method. The second one is Virginia Torczon's
+          <a href="../apidocs/org/apache/commons/math/optimization/direct/MultiDirectional.html">
+          multi-directional</a> method.
+        </p>
+      </subsection>
+      <subsection name="12.5 General Case" href="general">
+        <p>
+          The general package deals with non-linear vectorial optimization problems when
+          the partial derivatives of the objective function are available.
+        </p>
+        <p>
+          One important class of estimation problems is weighted least
+          squares problems. They basically consist in finding the values
+          for some parameters p<sub>k</sub> such that a cost function
+          J = sum(w<sub>i</sub>(mes<sub>i</sub> - mod<sub>i</sub>)<sup>2</sup>) is
+          minimized. The various (target<sub>i</sub> - model<sub>i</sub>(p<sub>k</sub>))
+          terms are called residuals. They represent the deviation between a set of
+          target values target<sub>i</sub> and theoretical values computed from
+          models model<sub>i</sub> depending on free parameters p<sub>k</sub>.
+          The w<sub>i</sub> factors are weights. One classical use case is when the
+          target values are experimental observations or measurements.
+        </p>
+        <p>
+          Solving a least-squares problem is finding the free parameters p<sub>k</sub>
+          of the theoretical models such that they are close to the target values, i.e.
+          when the residual are small.
+        </p>
+        <p>
+          Two optimizers are available in the general package, both devoted to least-squares
+          problems. The first one is based on the <a
+          href="../apidocs/org/apache/commons/math/optimization/general/GaussNewtonOptimizer.html">
+          Gauss-Newton</a> method. The second one is the <a
+          href="../apidocs/org/apache/commons/math/optimization/general/LevenbergMarquardtOptimizer.html">
+          Levenberg-Marquardt</a> method.
+        </p>
+        <p>
+          In order to solve a vectorial optimization problem, the user must provide it as
+          an object implementing the <a
+          href="../apidocs/org/apache/commons/math/analysis/DifferentiableMultivariateVectorialFunction.html">
+          DifferentiableMultivariateVectorialFunction</a> interface. The object will be provided to
+          the <code>estimate</code> method of the optimizer, along with the target and weight arrays,
+          thus allowing the optimizer to compute the residuals at will. The last parameter to the
+          <code>estimate</code> method is the point from which the optimizer will start its
+          search for the optimal point.
+        </p>
+        <p>
+          In addition to least squares solving, the <a
+          href="../apidocs/org/apache/commons/math/optimization/general/NonLinearConjugateGradientOptimizer.html">
+          NonLinearConjugateGradientOptimizer</a> class provides a non-linear conjugate gradient algorithm
+          to optimize <a
+          href="../apidocs/org/apache/commons/math/optimization/DifferentiableMultivariateRealFunction.html">
+          DifferentiableMultivariateRealFunction</a>. Both the Fletcher-Reeves and the Polak-Ribière
+          search direction update methods are supported. It is also possible to set up a preconditioner
+          or to change the line-search algorithm of the inner loop if desired (the default one is a Brent
+          solver).
+        </p>
+        <p>
+          The <a href="../apidocs/org/apache/commons/math/optimization/general/PowellOptimizer.html">
+          PowellOptimizer</a> provides an optimization method for non-differentiable functions.
+        </p>
+      </subsection>
+      <subsection name="12.6 Curve Fitting" href="fitting">
+        <p>
+          The fitting package deals with curve fitting for univariate real functions.
+          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>, curve fitting 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>
+        <p>
+          For all provided curve fitters, the operating principle is the same. Users must first
+          create an instance of the fitter, then add the observed points and once the complete
+          sample of observed points has been added they must call the <code>fit</code> method
+          which will compute the parameters that best fit the sample. A weight is associated
+          with each observed point, this allows to take into account uncertainty on some points
+          when they come from loosy measurements for example. If no such information exist and
+          all points should be treated the same, it is safe to put 1.0 as the weight for all points.
+        </p>
+        <p>
+          The <a
+          href="../apidocs/org/apache/commons/math/optimization/fitting/CurveFitter.html">
+          CurveFitter</a> class provides curve fitting for general curves. Users must
+          provide their own implementation of the curve template as a class implementing
+          the <a
+          href="../apidocs/org/apache/commons/math/optimization/fitting/ParametricRealFunction.html">
+          ParametricRealFunction</a> interface and they must provide the initial guess of the
+          parameters. The more specialized <a
+          href="../apidocs/org/apache/commons/math/optimization/fitting/PolynomialFitter.html">
+          PolynomialFitter</a> and <a
+          href="../apidocs/org/apache/commons/math/optimization/fitting/HarmonicFitter.html">
+          HarmonicFitter</a> classes require neither an implementation of the parametric real function
+          not an initial guess as they are able to compute them by themselves.
+        </p>
+        <p>
+          An example of fitting a polynomial is given here:
+        </p>
+        <source>PolynomialFitter fitter = new PolynomialFitter(degree, new LevenbergMarquardtOptimizer());
+fitter.addObservedPoint(-1.00,  2.021170021833143);
+fitter.addObservedPoint(-0.99   2.221135431136975);
+fitter.addObservedPoint(-0.98   2.09985277659314);
+fitter.addObservedPoint(-0.97   2.0211192647627025);
+// lots of lines ommitted
+fitter.addObservedPoint( 0.99, -2.4345814727089854);
+PolynomialFunction fitted = fitter.fit();
+        </source>
+      </subsection>
+     </section>
+  </body>
+</document>
diff --git a/src/site/xdoc/userguide/overview.xml b/src/site/xdoc/userguide/overview.xml
new file mode 100644
index 0000000..e3fc0d4
--- /dev/null
+++ b/src/site/xdoc/userguide/overview.xml
@@ -0,0 +1,163 @@
+<?xml version="1.0"?>
+
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+  
+<?xml-stylesheet type="text/xsl" href="./xdoc.xsl"?>
+<!-- $Revision: 982950 $ $Date: 2010-08-06 15:29:01 +0200 (ven. 06 août 2010) $ -->
+<document>
+  <properties>
+    <title>User Guide - Overview</title>
+  </properties>
+  
+<body>
+
+<section name="Overview">
+
+<subsection name="0.1 About The User Guide" href="about">
+    <p>
+    This guide is intended to help programmers quickly find what they need to develop
+    solutions using Commons Math.  It also provides a supplement to the javadoc API documentation,
+    providing a little more explanation of the mathematical objects and functions included
+    in the package.    
+    </p>
+</subsection>
+
+<subsection name="0.2 What's in commons-math" href="summary">
+    <p>
+    Commons Math is made up of a small set of math/stat utilities addressing 
+    programming problems like the ones in the list below.  This list is not exhaustive, 
+    it's just meant to give a feel for the kinds of things that Commons Math provides.  
+    <ul>
+        <li>Computing means, variances and other summary statistics for a list of numbers</li>
+        <li>Fitting a line to a set of data points using linear regression</li>
+        <li>Finding a smooth curve that passes through a collection of points (interpolation)</li>
+        <li>Fitting a parametric model to a set of measurements using least-squares methods</li>
+        <li>Solving equations involving real-valued functions (i.e. root-finding)</li> 
+        <li>Solving systems of linear equations</li>
+        <li>Solving Ordinary Differential Equations</li>
+        <li>Minimizing multi-dimensional functions</li>
+        <li>Generating random numbers with more restrictions (e.g distribution, range) than what
+            is possible using the JDK</li>
+        <li>Generating random samples and/or datasets that are "like" the data in an input file</li>
+        <li>Performing statistical significance tests</li>
+        <li>Miscellaneous mathematical functions such as factorials, binomial
+            coefficients and "special functions" (e.g. gamma, beta functions)</li>
+    </ul></p> 
+    <p>
+    We are actively seeking ideas for additional components that fit into the 
+    <a href="../index.html#summary">Commons Math vision</a> of a set of lightweight, 
+    self-contained math/stat components useful for solving common programming problems.
+    Suggestions for new components or enhancements to existing functionality are always welcome! 
+    All feedback/suggestions for improvement should be sent to the 
+    <a href="http://commons.apache.org/mail-lists.html">commons-dev mailing list</a> with
+    [math] at the beginning of the subject line.
+    </p>
+</subsection>
+
+<subsection name="0.3 How commons-math is organized" href="organization">
+    <p>
+    Commons Math is divided into fourteen subpackages, based on functionality provided.
+    <ol>
+      <li><a href="stat.html">org.apache.commons.math.stat</a> - statistics, statistical tests</li>
+      <li><a href="analysis.html">org.apache.commons.math.analysis</a> - rootfinding, integration, interpolation, polynomials</li>
+      <li><a href="random.html">org.apache.commons.math.random</a> - random numbers, strings and data generation</li>
+      <li><a href="special.html">org.apache.commons.math.special</a> - special functions (Gamma, Beta) </li>
+      <li><a href="linear.html">org.apache.commons.math.linear</a> - matrices, solving linear systems </li>
+      <li><a href="utilities.html">org.apache.commons.math.util</a> - common math/stat functions extending java.lang.Math </li>
+      <li><a href="complex.html">org.apache.commons.math.complex</a> - complex numbers</li>
+      <li><a href="distribution.html">org.apache.commons.math.distribution</a> - probability distributions</li>
+      <li><a href="fraction.html">org.apache.commons.math.fraction</a> - rational numbers</li>
+      <li><a href="transform.html">org.apache.commons.math.transform</a> - transform methods (Fast Fourier)</li>
+      <li><a href="geometry.html">org.apache.commons.math.geometry</a> - 3D geometry (vectors and rotations)</li>
+      <li><a href="optimization.html">org.apache.commons.math.optimization</a> - function maximization or minimization</li>
+      <li><a href="ode.html">org.apache.commons.math.ode</a> - Ordinary Differential Equations integration</li>
+      <li><a href="genetics.html">org.apache.commons.math.genetics</a> - Genetic Algorithms</li>
+    </ol>
+    Package javadocs are <a href="../apidocs/index.html">here</a>
+    </p>
+</subsection>
+
+<subsection name="0.4 How interface contracts are specified in commons-math javadoc" href="contracts">
+  <p>
+    You should always read the javadoc class and method comments carefully when using 
+    Commons Math components in your programs.  The javadoc provides references to the algorithms
+    that are used, usage notes about limitations, performance, etc. as well as interface contracts.
+    Interface contracts are specified in terms of preconditions (what has to be true in order
+    for the method to return valid results), special values returned (e.g. Double.NaN) 
+    or exceptions that may be thrown if the preconditions are not met, and definitions for returned
+    values/objects or state changes.</p>
+  <p>
+    When the actual parameters provided to a method or the internal state of an object 
+    make a computation meaningless, an IllegalArgumentException or IllegalStateException may
+    be thrown. Exact conditions under which runtime exceptions (and any other exceptions) are 
+    thrown are specified in the javadoc method comments.  In some cases, to be consistent with 
+    the <a href="http://grouper.ieee.org/groups/754/">IEEE 754 standard</a> for floating point 
+    arithmetic and with java.lang.Math, Commons Math methods return Double.NaN values.
+    Conditions under which Double.NaN or other special values are returned are fully specified
+    in the javadoc method comments.
+  </p>
+  <p>
+    As of version 2.2, the policy for dealing with null references is as
+    follows: When an argument is unexpectedly null, a
+    <a href="../apidocs/org/apache/commons/math/exception/NullArgumentException.html">
+      NullArgumentException</a> is raised for signalling the
+    illegal argument. Note that this class does not inherit from the
+    standard <code>NullPointerException</code> but is a subclass of
+    <code>IllegalArgumentException</code>.
+    No <code>NullPointerException</code> should be propagated from within
+    Commons-Math.
+  </p>
+</subsection>
+
+<subsection name="0.5 Dependencies" href="dependencies">
+    <p>
+    Commons Math requires JDK 1.5+ and has no runtime dependencies.
+    </p>
+</subsection>
+
+<subsection name="0.6 License" href="license">
+    <p>
+    Commons Math is distributed under the terms of the Apache License, Version 2.0:
+    <a href="http://www.apache.org/licenses/LICENSE-2.0"/>.
+    </p>
+
+    <p>
+    This product includes software developed by the University of Chicago, as Operator
+    of Argonne National Laboratory. This corresponds to software translated from the lmder,
+    lmpar and qrsolv Fortran routines from the Minpack package and distributed under the
+    following disclaimer: <a href="http://www.netlib.org/minpack/disclaimer"/>.
+    </p>
+
+    <p>
+    This product includes software translated from the odex Fortran routine developed by
+    E. Hairer and G. Wanner and distributed under the following license:
+    <a href="http://www.unige.ch/~hairer/prog/licence.txt"/>.
+    </p>
+
+    <p>
+    This product includes software translated from some LAPACK Fortran routines and
+    distributed under the following license: <a href="http://www.netlib.org/lapack/LICENSE"/>.
+    </p>
+
+</subsection>
+
+</section>
+
+</body>
+</document>
+
diff --git a/src/site/xdoc/userguide/random.xml b/src/site/xdoc/userguide/random.xml
new file mode 100644
index 0000000..585c244
--- /dev/null
+++ b/src/site/xdoc/userguide/random.xml
@@ -0,0 +1,538 @@
+<?xml version="1.0"?>
+
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+  
+<?xml-stylesheet type="text/xsl" href="./xdoc.xsl"?>
+<!-- $Revision: 1067592 $ $Date: 2011-02-06 06:42:35 +0100 (dim. 06 févr. 2011) $ -->
+<document url="random.html">
+
+<properties>
+    <title>The Commons Math User Guide - Data Generation</title>
+</properties>
+
+<body>
+
+<section name="2 Data Generation">
+
+<subsection name="2.1 Overview" href="overview">
+    <p>
+    The Commons Math random package includes utilities for
+    <ul>
+        <li>generating random numbers</li>
+        <li>generating random vectors</li>
+        <li>generating random strings</li>
+        <li>generating cryptographically secure sequences of random numbers or
+         strings</li>
+        <li>generating random samples and permutations</li>
+        <li>analyzing distributions of values in an input file and generating
+         values "like" the values in the file</li>
+        <li>generating data for grouped frequency distributions or
+         histograms</li>
+    </ul></p>
+    <p>
+     The source of random data used by the data generation utilities is
+     pluggable.  By default, the JDK-supplied PseudoRandom Number Generator
+     (PRNG) is used, but alternative generators can be "plugged in" using an
+     adaptor framework, which provides a generic facility for replacing 
+     <code>java.util.Random</code> with an alternative PRNG. Other very
+     good PRNG suitable for Monte-Carlo analysis (but <strong>not</strong>
+     for cryptography) provided by the library are the Mersenne twister from
+     Makoto Matsumoto and Takuji Nishimura and the more recent WELL generators
+     (Well Equidistributed Long-period Linear) from François Panneton, Pierre
+     L'Ecuyer and Makoto Matsumoto.
+    </p>
+    <p>
+     Sections 2.2-2.6 below show how to use the commons math API to generate
+     different kinds of random data.  The examples all use the default
+     JDK-supplied PRNG.  PRNG pluggability is covered in 2.7.  The only 
+     modification required to the examples to use alternative PRNGs is to
+     replace the argumentless constructor calls with invocations including
+     a <code>RandomGenerator</code> instance as a parameter.
+    </p>  
+</subsection>
+
+<subsection name="2.2 Random numbers" href="deviates">
+    <p>
+    The <a href="../apidocs/org/apache/commons/math/random/RandomData.html">
+    RandomData</a> interface defines methods for generating random sequences
+    of numbers. The API contracts of these methods use the following concepts:
+    <dl>
+    <dt>Random sequence of numbers from a probability distribution</dt>
+    <dd>There is no such thing as a single "random number."  What can be
+    generated  are <i>sequences</i> of numbers that appear to be random.  When
+    using the built-in JDK function <code>Math.random(),</code> sequences of 
+    values generated follow the 
+    <a href="http://www.itl.nist.gov/div898/handbook/eda/section3/eda3662.htm">
+    Uniform Distribution</a>, which means that the values are evenly spread
+    over the interval  between 0 and 1, with no sub-interval having a greater
+    probability of containing generated values than any other interval of the
+    same length.  The mathematical concept of a
+    <a href="http://www.itl.nist.gov/div898/handbook/eda/section3/eda36.htm">
+    probability distribution</a> basically amounts to asserting that different
+    ranges in the set  of possible values of a random variable have
+    different probabilities of containing the value.  Commons Math supports
+    generating random sequences from each of the distributions in the
+    <a href="../apidocs/org/apache/commons/math/distribution/package-summary.html">
+    distributions</a> package.
+    The javadoc for the <code>nextXxx</code> methods in 
+    <a href="../apidocs/org/apache/commons/math/random/RandomDataImpl.html">
+    RandomDataImpl</a> describes the algorithms used to generate
+    random deviates.   
+    </dd>
+    <dt>Cryptographically secure random sequences</dt>
+    <dd>It is possible for a sequence of numbers to appear random, but
+    nonetheless to be predictable based on the algorithm used to generate the
+    sequence. If in addition to randomness, strong unpredictability is
+    required, it is best to use a  
+    <a href="http://www.wikipedia.org/wiki/Cryptographically_secure_pseudo-random_number_generator">
+    secure random number generator</a> to generate values (or strings). The
+    nextSecureXxx methods in the <code>RandomDataImpl</code> implementation of
+    the <code>RandomData</code> interface use the JDK <code>SecureRandom</code>
+    PRNG to generate cryptographically secure sequences.  The 
+    <code>setSecureAlgorithm</code> method allows you to change the underlying
+    PRNG. These methods are <strong>much slower</strong> than the corresponding
+    "non-secure" versions, so they should only be used when cryptographic
+    security is required.</dd>
+    <dt>Seeding pseudo-random number generators</dt>
+    <dd>By default, the implementation provided in <code>RandomDataImpl</code>
+    uses the JDK-provided PRNG.  Like most other PRNGs, the JDK generator
+    generates sequences of random numbers based on an initial "seed value".
+    For the non-secure methods, starting with the same seed always produces the
+    same sequence of values.  Secure sequences started with the same seeds will
+    diverge. When a new <code>RandomDataImpl</code> is created, the underlying
+    random number generators are <strong>not</strong> initialized.  The first
+    call to a data generation method, or to a  <code>reSeed()</code> method
+    initializes the appropriate generator.  If you do not explicitly seed the
+    generator, it is by default seeded with the current time in milliseconds.
+    Therefore, to generate sequences of random data values, you should always
+    instantiate <strong>one</strong> <code> RandomDataImpl</code> and use it
+    repeatedly instead of creating new instances for subsequent values in the
+    sequence.  For example, the following will generate a random sequence of 50
+    long integers between 1 and 1,000,000, using the current time in
+    milliseconds as the seed for the JDK PRNG:
+    <source>
+RandomData randomData = new RandomDataImpl(); 
+for (int i = 0; i < 1000; i++) {
+    value = randomData.nextLong(1, 1000000);
+}
+    </source>
+    The following will not in general produce a good random sequence, since the
+    PRNG is reseeded each time through the loop with the current time in
+    milliseconds:
+    <source>
+for (int i = 0; i < 1000; i++) {
+    RandomDataImpl randomData = new RandomDataImpl(); 
+    value = randomData.nextLong(1, 1000000);
+}
+    </source>
+    The following will produce the same random sequence each time it is
+    executed:
+    <source>
+RandomData randomData = new RandomDataImpl(); 
+randomData.reSeed(1000);
+for (int i = 0; i = 1000; i++) {
+    value = randomData.nextLong(1, 1000000);
+}
+    </source>
+    The following will produce a different random sequence each time it is
+     executed. 
+    <source>
+RandomData randomData = new RandomDataImpl(); 
+randomData.reSeedSecure(1000);
+for (int i = 0; i < 1000; i++) {
+    value = randomData.nextSecureLong(1, 1000000);
+}
+    </source>
+    </dd></dl>
+    </p>
+</subsection>
+
+<subsection name="2.3 Random Vectors" href="vectors">
+    <p>
+    Some algorithms require random vectors instead of random scalars. When the
+    components of these vectors are uncorrelated, they may be generated simply
+    one at a time and packed together in the vector. The <a
+    href="../apidocs/org/apache/commons/math/random/UncorrelatedRandomVectorGenerator.html">
+    UncorrelatedRandomVectorGenerator</a> class simplifies this
+    process by setting the mean and deviation of each component once and
+    generating complete vectors. When the components are correlated however,
+    generating them is much more difficult. The <a href="../apidocs/org/apache/commons/math/random/CorrelatedRandomVectorGenerator.html">
+    CorrelatedRandomVectorGenerator</a> class provides this service. In this
+    case, the user must set up a complete covariance matrix instead of a simple
+    standard deviations vector. This matrix gathers both the variance and the
+    correlation information of the probability law.
+    </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
+    common 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>.
+    </p>
+ <p><dl>
+    <dt>Generating random vectors from a bivariate normal distribution</dt><dd>
+    <source>
+// Create and seed a RandomGenerator (could use any of the generators in the random package here)
+RandomGenerator rg = new JDKRandomGenerator();
+rg.setSeed(17399225432l);  // Fixed seed means same results every time
+
+// Create a GassianRandomGenerator using rg as its source of randomness
+GaussianRandomGenerator rawGenerator = new GaussianRandomGenerator(rg);
+
+// Create a CorrelatedRandomVectorGenerator using rawGenerator for the components
+CorrelatedRandomVectorGenerator generator = 
+    new CorrelatedRandomVectorGenerator(mean, covariance, 1.0e-12 * covariance.getNorm(), rawGenerator);
+
+// Use the generator to generate correlated vectors
+double[] randomVector = generator.nextVector();
+... </source>
+    The <code>mean</code> argument is a double[] array holding the means of the random vector
+    components.  In the bivariate case, it must have length 2.  The <code>covariance</code> argument
+    is a RealMatrix, which needs to be 2 x 2.  The main diagonal elements are the
+    variances of the vector components and the off-diagonal elements are the covariances.
+    For example, if the means are 1 and 2 respectively, and the desired standard deviations
+    are 3 and 4, respectively, then we need to use
+    <source>
+double[] mean = {1, 2};
+double[][] cov = {{9, c}, {c, 16}};
+RealMatrix covariance = MatrixUtils.createRealMatrix(cov); </source>
+    where c is the desired covariance. If you are starting with a desired correlation,
+    you need to translate this to a covariance by multiplying it by the product of the
+    standard deviations.  For example, if you want to generate data that will give Pearson's
+    R of 0.5, you would use c = 3 * 4 * .5 = 6.
+    </dd></dl></p>
+    <p>
+    In addition to multivariate normal distributions, correlated vectors from multivariate uniform
+    distributions can be generated by creating a
+    <a href="../apidocs/org/apache/commons/math/random/UniformRandomGenerator.html">UniformRandomGenerator</a>
+    in place of the 
+    <code>GaussianRandomGenerator</code> above.  More generally, any
+    <a href="../apidocs/org/apache/commons/math/random/NormalizedRandomGenerator.html">NormalizedRandomGenerator</a>
+    may be used.
+    </p>
+</subsection>
+
+<subsection name="2.4 Random Strings" href="strings">
+    <p>
+    The methods <code>nextHexString</code> and <code>nextSecureHexString</code>
+    can be used to generate random strings of hexadecimal characters.  Both
+    of these methods produce sequences of strings with good dispersion
+    properties.  The difference between the two methods is that the second is
+    cryptographically secure.  Specifically, the implementation of 
+    <code>nextHexString(n)</code> in <code>RandomDataImpl</code> uses the
+    following simple algorithm to generate a string of <code>n</code> hex digits:
+    <ol>
+    <li>n/2+1 binary bytes are generated using the underlying Random</li>
+    <li>Each binary byte is translated into 2 hex digits</li></ol>
+    The <code>RandomDataImpl</code> implementation of the "secure" version, 
+    <code>nextSecureHexString</code> generates hex characters in 40-byte
+    "chunks" 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>
+    Similarly to the secure random number generation methods, 
+    <code>nextSecureHexString</code> is <strong>much slower</strong> than
+    the non-secure version.  It should be used only for applications such as 
+    generating unique session or transaction ids where predictability of
+    subsequent ids based on observation of previous values is a security
+    concern.  If all that is needed is an even distribution of hex characters
+    in the generated strings, the non-secure method should be used.        
+    </p>
+</subsection>
+
+<subsection name="2.5 Random permutations, combinations, sampling"
+ href="combinatorics">
+    <p>
+    To select a random sample of objects in a collection, you can use the
+    <code>nextSample</code> method in the <code>RandomData</code> interface.
+    Specifically,  if <code>c</code> is a collection containing at least 
+    <code>k</code> objects, and <code>randomData</code> is a 
+    <code>RandomData</code> instance <code>randomData.nextSample(c, k)</code>
+    will return an <code>object[]</code> array of length <code>k</code>
+    consisting of elements randomly selected from the collection.  If 
+    <code>c</code> contains duplicate references, there may be duplicate
+    references in the returned array; otherwise returned elements will be
+    unique -- i.e., the sampling is without replacement among the object
+    references in the collection. </p>
+    <p>
+    If <code>randomData</code> is a <code>RandomData</code> instance, and 
+    <code>n</code> and <code>k</code> are integers with 
+    <code> k <= n</code>,  then 
+    <code>randomData.nextPermutation(n, k)</code> returns an <code>int[]</code>
+    array of length <code>k</code> whose whose entries are selected randomly, 
+    without repetition, from the integers <code>0</code> through
+    <code>n-1</code> (inclusive), i.e., 
+    <code>randomData.nextPermutation(n, k)</code> returns a random
+    permutation of  <code>n</code> taken <code>k</code> at a time.   
+    </p>
+</subsection>
+
+<subsection name="2.6 Generating data 'like' an input file" href="empirical">
+    <p>
+    Using the <code>ValueServer</code> class, you can generate data based on
+    the values in an input file in one of two ways:
+    <dl>
+      <dt>Replay Mode</dt>
+      <dd> The following code will read data from <code>url</code> 
+      (a <code>java.net.URL</code> instance), cycling through the values in the 
+      file in sequence, reopening and starting at the beginning again when all 
+      values have been read.
+      <source>
+      ValueServer vs = new ValueServer();
+      vs.setValuesFileURL(url); 
+      vs.setMode(ValueServer.REPLAY_MODE);
+      vs.resetReplayFile();
+      double value = vs.getNext();
+      // ...Generate and use more values...
+      vs.closeReplayFile();
+      </source>
+      The values in the file are not stored in memory, so it does not matter
+      how large the file is, but you do need to explicitly close the file
+      as above.  The expected file format is \n -delimited (i.e. one per line)
+      strings representing valid floating point numbers.
+      </dd>
+      <dt>Digest Mode</dt>
+      <dd>When used in Digest Mode, the ValueServer reads the entire input file
+      and estimates a probability density function based on data from the file.
+      The estimation method is essentially the 
+      <a href="http://nedwww.ipac.caltech.edu/level5/March02/Silverman/Silver2_6.html">
+      Variable Kernel Method</a> with Gaussian smoothing.  Once the density
+      has been estimated, <code>getNext()</code> returns random values whose
+      probability distribution matches the empirical distribution -- i.e., if
+      you generate a large number of such values, their distribution should
+      "look like" the distribution of the values in the input file.  The values
+      are not stored in memory in this case either, so there is no limit to the
+      size of the input file.  Here is an example:
+      <source>
+      ValueServer vs = new ValueServer();
+      vs.setValuesFileURL(url); 
+      vs.setMode(ValueServer.DIGEST_MODE);
+      vs.computeDistribution(500); //Read file and estimate distribution using 500 bins
+      double value = vs.getNext();
+      // ...Generate and use more values...
+      </source>
+      See the javadoc for <code>ValueServer</code> and 
+      <code>EmpiricalDistribution</code> for more details.  Note that 
+      <code>computeDistribution()</code> opens and closes the input file
+       by itself. 
+      </dd>
+    </dl>
+  </p>
+</subsection>
+
+<subsection name="2.7 PRNG Pluggability" href="pluggability">
+  <p>
+      To enable alternative PRNGs to be "plugged in" to the commons-math data
+      generation utilities and to provide a generic means to replace 
+      <code>java.util.Random</code> in applications, a random generator 
+      adaptor framework has been added to commons-math.  The
+      <a href="../apidocs/org/apache/commons/math/random/RandomGenerator.html">
+      RandomGenerator</a> interface abstracts the public interface of
+      <code>java.util.Random</code> and any implementation of this
+      interface can be used as the source of random data for the commons-math 
+      data generation classes.  An abstract base class, 
+      <a href="../apidocs/org/apache/commons/math/random/AbstractRandomGenerator.html">
+      AbstractRandomGenerator</a> is provided to make implementation easier.
+      This class provides default implementations of "derived" data generation
+      methods based on the primitive,  <code>nextDouble()</code>.
+      To support generic replacement of <code>java.util.Random</code>, the 
+      <a href="../apidocs/org/apache/commons/math/random/RandomAdaptor.html">
+      RandomAdaptor</a> class is provided, which extends
+      <code>java.util.Random</code> and wraps and delegates calls to
+      a <code>RandomGenerator</code> instance.   
+  </p>
+
+      <p>Commons-math provides by itself several implementations of the <a
+      href="../apidocs/org/apache/commons/math/random/RandomGenerator.html">
+      RandomGenerator</a> interface:
+      <ul>
+        <li><a href="../apidocs/org/apache/commons/math/random/JDKRandomGenerator.html">JDKRandomGenerator</a>
+            that extends the JDK provided generator</li>
+        <li><a href="../apidocs/org/apache/commons/math/random/AbstractRandomGenerator.html">
+            AbstractRandomGenerator</a> as a helper for users generators</li>
+        <li><a href="../apidocs/org/apache/commons/math/random/BitStreamGenerator.html">
+            BitStreamGenerator</a> which is an abstract class for several generators and
+            which in turn is extended by:
+            <ul>
+              <li><a href="../apidocs/org/apache/commons/math/random/MersenneTwister.html">MersenneTwister</a></li>
+              <li><a href="../apidocs/org/apache/commons/math/random/Well512a.html">Well512a</a></li>
+              <li><a href="../apidocs/org/apache/commons/math/random/Well1024a.html">Well1024a</a></li>
+              <li><a href="../apidocs/org/apache/commons/math/random/Well19937a.html">Well19937a</a></li>
+              <li><a href="../apidocs/org/apache/commons/math/random/Well19937c.html">Well19937c</a></li>
+              <li><a href="../apidocs/org/apache/commons/math/random/Well44497a.html">Well44497a</a></li>
+              <li><a href="../apidocs/org/apache/commons/math/random/Well44497b.html">Well44497b</a></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ç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 between 25 and 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><a href="../apidocs/org/apache/commons/math/random/MersenneTwister.html">MersenneTwister</a></td><td>1</td></tr>
+          <tr><td><a href="../apidocs/org/apache/commons/math/random/JDKRandomGenerator.html">JDKRandomGenerator</a></td><td>between 0.96 and 1.16</td></tr>
+          <tr><td><a href="../apidocs/org/apache/commons/math/random/Well512a.html">Well512a</a></td><td>between 0.85 and 0.88</td></tr>
+          <tr><td><a href="../apidocs/org/apache/commons/math/random/Well1024a.html">Well1024a</a></td><td>between 0.63 and 0.73</td></tr>
+          <tr><td><a href="../apidocs/org/apache/commons/math/random/Well19937a.html">Well19937a</a></td><td>between 0.70 and 0.71</td></tr>
+          <tr><td><a href="../apidocs/org/apache/commons/math/random/Well19937c.html">Well19937c</a></td><td>between 0.57 and 0.71</td></tr>
+          <tr><td><a href="../apidocs/org/apache/commons/math/random/Well44497a.html">Well44497a</a></td><td>between 0.69 and 0.71</td></tr>
+          <tr><td><a href="../apidocs/org/apache/commons/math/random/Well44497b.html">Well44497b</a></td><td>between 0.65 and 0.71</td></tr>
+        </table>
+      </p>
+
+      <p>
+      So for most simulation problems, the better generators like <a
+      href="../apidocs/org/apache/commons/math/random/Well19937c.html">Well19937c</a> and <a
+      href="../apidocs/org/apache/commons/math/random/Well44497b.html">Well44497b</a> 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>
+
+  <p>
+     Examples:
+     <dl>
+      <dt>Create a RandomGenerator based on RngPack's Mersenne Twister</dt>
+      <dd>To create a RandomGenerator using the RngPack Mersenne Twister PRNG
+       as the source of randomness, extend <code>AbstractRandomGenerator</code>
+       overriding the derived methods that the RngPack implementation provides:
+       <source>
+import edu.cornell.lassp.houle.RngPack.RanMT;
+/**
+ * AbstractRandomGenerator based on RngPack RanMT generator.
+ */
+public class RngPackGenerator extends AbstractRandomGenerator {
+    
+    private RanMT random = new RanMT();
+    
+    public void setSeed(long seed) {
+       random = new RanMT(seed);
+    }
+    
+    public double nextDouble() {
+        return random.raw();
+    }
+    
+    public double nextGaussian() {
+        return random.gaussian();
+    }
+    
+    public int nextInt(int n) {
+        return random.choose(n);
+    }
+    
+    public boolean nextBoolean() {
+        return random.coin();
+    }
+}
+      </source>
+      </dd>
+      <dt>Use the Mersenne Twister RandomGenerator in place of 
+      <code>java.util.Random</code> in <code>RandomData</code></dt>
+      <dd>
+      <source>
+RandomData randomData = new RandomDataImpl(new RngPackGenerator());
+      </source>
+      </dd>
+      <dt>Create an adaptor instance based on the Mersenne Twister generator
+      that can be used in place of a <code>Random</code></dt>
+      <dd>
+      <source>
+ RandomGenerator generator = new RngPackGenerator();
+ Random random = RandomAdaptor.createAdaptor(generator);
+ // random can now be used in place of a Random instance, data generation
+ // calls will be delegated to the wrapped Mersenne Twister
+      </source>
+      </dd>
+  </dl>
+ </p>
+</subsection>
+
+</section>
+
+</body>
+</document>
diff --git a/src/site/xdoc/userguide/special.xml b/src/site/xdoc/userguide/special.xml
new file mode 100644
index 0000000..885d273
--- /dev/null
+++ b/src/site/xdoc/userguide/special.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0"?>
+
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+  
+<?xml-stylesheet type="text/xsl" href="./xdoc.xsl"?>
+<!-- $Revision: 937893 $ $Date: 2010-04-25 23:42:47 +0200 (dim. 25 avril 2010) $ -->
+<document url="special.html">
+  <properties>
+    <title>The Commons Math User Guide - Special Functions</title>
+  </properties>
+  <body>
+    <section name="5 Special Functions">
+      <subsection name="5.1 Overview" href="overview">
+        <p>
+          The special functions portion of Commons-Math contains several useful functions not
+          provided by <code>java.lang.Math</code>.  These functions mostly serve as building blocks
+          for other portions of Commons-Math but, as others may find them useful as stand-alone
+          methods, these special functions were included as part of the Commons-Math public API.
+        </p>
+      </subsection>
+      <subsection name="5.2 Erf functions" href="erf">
+        <p>
+          <a href="../apidocs/org/apache/commons/math/special/Erf.html">Erf</a> contains
+          several useful functions involving the Error Function, Erf.
+          <table>
+            <tr><th>Function</th><th>Method</th><th>Reference</th></tr>
+            <tr><td>Error Function</td><td>erf</td><td>See <a href="http://mathworld.wolfram.com/Erf.html">Erf</a> from MathWorld</td></tr>
+          </table>
+        </p>
+      </subsection>
+      <subsection name="5.3 Gamma functions" href="gamma">
+        <p>
+          <a href="../apidocs/org/apache/commons/math/special/Gamma.html">Gamma</a>
+          contains several useful functions involving the Gamma Function.
+          <table>
+            <tr><th>Function</th><th>Method</th><th>Reference</th></tr>
+            <tr><td>Log Gamma</td><td>logGamma</td><td>See <a href="http://mathworld.wolfram.com/GammaFunction.html">Gamma Function</a> from MathWorld</td></tr>
+            <tr><td>Regularized Gamma</td><td>regularizedGammaP</td><td>See <a href="http://mathworld.wolfram.com/RegularizedGammaFunction.html">Regularized Gamma Function</a> from MathWorld</td></tr>
+          </table>
+        </p>
+      </subsection>
+      <subsection name="5.4 Beta funtions" href="beta">
+        <p>
+          <a href="../apidocs/org/apache/commons/math/special/Beta.html">Beta</a>
+          contains several useful functions involving the Beta Function.
+          <table>
+            <tr><th>Function</th><th>Method</th><th>Reference</th></tr>
+            <tr><td>Log Beta</td><td>logBeta</td><td>See <a href="http://mathworld.wolfram.com/BetaFunction.html">Beta Function</a> from MathWorld</td></tr>
+            <tr><td>Regularized Beta</td><td>regularizedBeta</td><td>See <a href="http://mathworld.wolfram.com/RegularizedBetaFunction.html">Regularized Beta Function</a> from MathWorld</td></tr>
+          </table>
+        </p>
+      </subsection>
+    </section>
+  </body>
+</document>
diff --git a/src/site/xdoc/userguide/stat.xml b/src/site/xdoc/userguide/stat.xml
new file mode 100644
index 0000000..5a10868
--- /dev/null
+++ b/src/site/xdoc/userguide/stat.xml
@@ -0,0 +1,1061 @@
+<?xml version="1.0"?>
+
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+
+<?xml-stylesheet type="text/xsl" href="./xdoc.xsl"?>
+<!-- $Revision: 998761 $ $Date: 2010-09-20 03:57:03 +0200 (lun. 20 sept. 2010) $ -->
+<document url="stat.html">
+  <properties>
+    <title>The Commons Math User Guide - Statistics</title>
+  </properties>
+  <body>
+    <section name="1 Statistics">
+      <subsection name="1.1 Overview">
+        <p>
+          The statistics package provides frameworks and implementations for
+          basic Descriptive statistics, frequency distributions, bivariate regression,
+          and t-, chi-square and ANOVA test statistics.
+        </p>
+        <p>
+         <a href="#a1.2_Descriptive_statistics">Descriptive statistics</a><br></br>
+         <a href="#a1.3_Frequency_distributions">Frequency distributions</a><br></br>
+         <a href="#a1.4_Simple_regression">Simple Regression</a><br></br>
+         <a href="#a1.5_Multiple_linear_regression">Multiple Regression</a><br></br>
+         <a href="#a1.6_Rank_transformations">Rank transformations</a><br></br>
+         <a href="#a1.7_Covariance_and_correlation">Covariance and correlation</a><br></br>
+         <a href="#a1.8_Statistical_tests">Statistical Tests</a><br></br>
+        </p>
+      </subsection>
+      <subsection name="1.2 Descriptive statistics">
+        <p>
+          The stat package includes a framework and default implementations for
+           the following Descriptive statistics:
+          <ul>
+            <li>arithmetic and geometric means</li>
+            <li>variance and standard deviation</li>
+            <li>sum, product, log sum, sum of squared values</li>
+            <li>minimum, maximum, median, and percentiles</li>
+            <li>skewness and kurtosis</li>
+            <li>first, second, third and fourth moments</li>
+          </ul>
+        </p>
+        <p>
+          With the exception of percentiles and the median, all of these
+          statistics can be computed without maintaining the full list of input
+          data values in memory.  The stat package provides interfaces and
+          implementations that do not require value storage as well as
+          implementations that operate on arrays of stored values.
+        </p>
+        <p>
+          The top level interface is
+          <a href="../apidocs/org/apache/commons/math/stat/descriptive/UnivariateStatistic.html">
+          UnivariateStatistic</a>.
+          This interface, implemented by all statistics, consists of
+          <code>evaluate()</code> methods that take double[] arrays as arguments
+          and return the value of the statistic.   This interface is extended by
+          <a href="../apidocs/org/apache/commons/math/stat/descriptive/StorelessUnivariateStatistic.html">
+          StorelessUnivariateStatistic</a>, which adds <code>increment(),</code>
+          <code>getResult()</code> and associated methods to support
+          "storageless" implementations that maintain counters, sums or other
+          state information as values are added using the <code>increment()</code>
+          method.
+        </p>
+        <p>
+          Abstract implementations of the top level interfaces are provided in
+          <a href="../apidocs/org/apache/commons/math/stat/descriptive/AbstractUnivariateStatistic.html">
+          AbstractUnivariateStatistic</a> and
+          <a href="../apidocs/org/apache/commons/math/stat/descriptive/AbstractStorelessUnivariateStatistic.html">
+          AbstractStorelessUnivariateStatistic</a> respectively.
+        </p>
+        <p>
+          Each statistic is implemented as a separate class, in one of the
+          subpackages (moment, rank, summary) and each extends one of the abstract
+          classes above (depending on whether or not value storage is required to
+          compute the statistic). There are several ways to instantiate and use statistics.
+          Statistics can be instantiated and used directly,  but it is generally more convenient
+          (and efficient) to access them using the provided aggregates,
+          <a href="../apidocs/org/apache/commons/math/stat/descriptive/DescriptiveStatistics.html">
+           DescriptiveStatistics</a> and
+           <a href="../apidocs/org/apache/commons/math/stat/descriptive/SummaryStatistics.html">
+           SummaryStatistics.</a>
+        </p>
+        <p>
+           <code>DescriptiveStatistics</code> maintains the input data in memory
+           and has the capability of producing "rolling" statistics computed from a
+           "window" consisting of the most recently added values.
+        </p>
+        <p>
+           <code>SummaryStatistics</code> does not store the input data values
+           in memory, so the statistics included in this aggregate are limited to those
+           that can be computed in one pass through the data without access to
+           the full array of values.
+        </p>
+        <p>
+          <table>
+            <tr><th>Aggregate</th><th>Statistics Included</th><th>Values stored?</th>
+            <th>"Rolling" capability?</th></tr><tr><td>
+            <a href="../apidocs/org/apache/commons/math/stat/descriptive/DescriptiveStatistics.html">
+            DescriptiveStatistics</a></td><td>min, max, mean, geometric mean, n,
+            sum, sum of squares, standard deviation, variance, percentiles, skewness,
+            kurtosis, median</td><td>Yes</td><td>Yes</td></tr><tr><td>
+            <a href="../apidocs/org/apache/commons/math/stat/descriptive/SummaryStatistics.html">
+            SummaryStatistics</a></td><td>min, max, mean, geometric mean, n,
+            sum, sum of squares, standard deviation, variance</td><td>No</td><td>No</td></tr>
+          </table>
+        </p>
+        <p>
+          <code>SummaryStatistics</code> can be aggregated using 
+          <a href="../apidocs/org/apache/commons/math/stat/descriptive/AggregateSummaryStatistics.html">
+          AggregateSummaryStatistics.</a>  This class can be used to concurrently
+          gather statistics for multiple datasets as well as for a combined sample
+          including all of the data.
+       </p>
+        <p>
+           <code>MultivariateSummaryStatistics</code> is similar to
+           <code>SummaryStatistics</code> but handles n-tuple values instead of
+           scalar values. It can also compute the full covariance matrix for the
+           input data.
+        </p>
+        <p>
+           Neither <code>DescriptiveStatistics</code> nor <code>SummaryStatistics</code>
+           is thread-safe.
+           <a href="../apidocs/org/apache/commons/math/stat/descriptive/SynchronizedDescriptiveStatistics.html">
+           SynchronizedDescriptiveStatistics</a> and
+           <a href="../apidocs/org/apache/commons/math/stat/descriptive/SynchronizedSummaryStatistics.html"> 
+           SynchronizedSummaryStatistics</a>, respectively, provide thread-safe
+           versions for applications that require concurrent access to statistical
+           aggregates by multiple threads.
+           <a href="../apidocs/org/apache/commons/math/stat/descriptive/SynchronizedMultiVariateSummaryStatistics.html"> 
+           SynchronizedMultivariateSummaryStatistics</a> provides thread-safe
+           <code>MultivariateSummaryStatistics.</code>
+        </p>
+        <p>
+          There is also a utility class,
+          <a href="../apidocs/org/apache/commons/math/stat/StatUtils.html">
+          StatUtils</a>, that provides static methods for computing statistics
+          directly from double[] arrays.
+        </p>
+        <p>
+          Here are some examples showing how to compute Descriptive statistics.
+          <dl>
+          <dt>Compute summary statistics for a list of double values</dt>
+          <br></br>
+          <dd>Using the <code>DescriptiveStatistics</code> aggregate
+          (values are stored in memory):
+        <source>
+// Get a DescriptiveStatistics instance
+DescriptiveStatistics stats = new DescriptiveStatistics();
+
+// Add the data from the array
+for( int i = 0; i < inputArray.length; i++) {
+        stats.addValue(inputArray[i]);
+}
+
+// Compute some statistics
+double mean = stats.getMean();
+double std = stats.getStandardDeviation();
+double median = stats.getMedian();
+        </source>
+        </dd>
+        <dd>Using the <code>SummaryStatistics</code> aggregate (values are
+        <strong>not</strong> stored in memory):
+       <source>
+// Get a SummaryStatistics instance
+SummaryStatistics stats = new SummaryStatistics();
+
+// Read data from an input stream,
+// adding values and updating sums, counters, etc.
+while (line != null) {
+        line = in.readLine();
+        stats.addValue(Double.parseDouble(line.trim()));
+}
+in.close();
+
+// Compute the statistics
+double mean = stats.getMean();
+double std = stats.getStandardDeviation();
+//double median = stats.getMedian(); <-- NOT AVAILABLE
+        </source>
+        </dd>
+         <dd>Using the <code>StatUtils</code> utility class:
+       <source>
+// Compute statistics directly from the array
+// assume values is a double[] array
+double mean = StatUtils.mean(values);
+double std = StatUtils.variance(values);
+double median = StatUtils.percentile(50);
+
+// Compute the mean of the first three values in the array
+mean = StatUtils.mean(values, 0, 3);
+        </source>
+        </dd>
+        <dt>Maintain a "rolling mean" of the most recent 100 values from
+        an input stream</dt>
+        <br></br>
+        <dd>Use a <code>DescriptiveStatistics</code> instance with
+        window size set to 100
+        <source>
+// Create a DescriptiveStats instance and set the window size to 100
+DescriptiveStatistics stats = new DescriptiveStatistics();
+stats.setWindowSize(100);
+
+// Read data from an input stream,
+// displaying the mean of the most recent 100 observations
+// after every 100 observations
+long nLines = 0;
+while (line != null) {
+        line = in.readLine();
+        stats.addValue(Double.parseDouble(line.trim()));
+        if (nLines == 100) {
+                nLines = 0;
+                System.out.println(stats.getMean());
+       }
+}
+in.close();
+        </source>
+        </dd>
+        <dt>Compute statistics in a thread-safe manner</dt>
+        <br/>
+        <dd>Use a <code>SynchronizedDescriptiveStatistics</code> instance
+        <source>
+// Create a SynchronizedDescriptiveStatistics instance and
+// use as any other DescriptiveStatistics instance
+DescriptiveStatistics stats = new SynchronizedDescriptiveStatistics();
+        </source>
+        </dd>
+        <dt>Compute statistics for multiple samples and overall statistics concurrently</dt>
+        <br/>
+        <dd>There are two ways to do this using <code>AggregateSummaryStatistics.</code> 
+        The first is to use an <code>AggregateSummaryStatistics</code> instance
+        to accumulate overall statistics contributed by <code>SummaryStatistics</code>
+        instances created using
+        <a href="../apidocs/org/apache/commons/math/stat/descriptive/AggregateSummaryStatistics.html#createContributingStatistics()">
+        AggregateSummaryStatistics.createContributingStatistics()</a>:
+        <source>
+// Create a AggregateSummaryStatistics instance to accumulate the overall statistics 
+// and AggregatingSummaryStatistics for the subsamples
+AggregateSummaryStatistics aggregate = new AggregateSummaryStatistics();
+SummaryStatistics setOneStats = aggregate.createContributingStatistics();
+SummaryStatistics setTwoStats = aggregate.createContributingStatistics();
+// Add values to the subsample aggregates
+setOneStats.addValue(2);
+setOneStats.addValue(3);
+setTwoStats.addValue(2);
+setTwoStats.addValue(4);
+...
+// Full sample data is reported by the aggregate
+double totalSampleSum = aggregate.getSum();
+        </source>
+        The above approach has the disadvantages that the <code>addValue</code> calls must be synchronized on the
+        <code>SummaryStatistics</code> instance maintained by the aggregate and each value addition updates the
+        aggregate as well as the subsample. For applications that can wait to do the aggregation until all values
+        have been added, a static
+        <a href="../apidocs/org/apache/commons/math/stat/descriptive/AggregateSummaryStatistics.html#aggregate(java.util.Collection)">
+          aggregate</a> method is available, as shown in the following example.
+        This method should be used when aggregation needs to be done across threads.
+        <source>
+// Create SummaryStatistics instances for the subsample data
+SummaryStatistics setOneStats = new SummaryStatistics();
+SummaryStatistics setTwoStats = new SummaryStatistics();
+// Add values to the subsample SummaryStatistics instances
+setOneStats.addValue(2);
+setOneStats.addValue(3);
+setTwoStats.addValue(2);
+setTwoStats.addValue(4);
+...
+// Aggregate the subsample statistics
+Collection<SummaryStatistics> aggregate = new ArrayList<SummaryStatistics>();
+aggregate.add(setOneStats);
+aggregate.add(setTwoStats);
+StatisticalSummary aggregatedStats = AggregateSummaryStatistics.aggregate(aggregate);
+
+// Full sample data is reported by aggregatedStats
+double totalSampleSum = aggregatedStats.getSum();
+        </source>
+        </dd>
+        </dl>
+       </p>
+      </subsection>
+      <subsection name="1.3 Frequency distributions">
+        <p>
+          <a href="../apidocs/org/apache/commons/math/stat/Frequency.html">
+          Frequency</a>
+          provides a simple interface for maintaining counts and percentages of discrete
+          values.
+        </p>
+        <p>
+          Strings, integers, longs and chars are all supported as value types,
+          as well as instances of any class that implements <code>Comparable.</code>
+          The ordering of values used in computing cumulative frequencies is by
+          default the <i>natural ordering,</i> but this can be overriden by supplying a
+          <code>Comparator</code> to the constructor. Adding values that are not
+          comparable to those that have already been added results in an
+          <code>IllegalArgumentException.</code>
+        </p>
+        <p>
+          Here are some examples.
+          <dl>
+          <dt>Compute a frequency distribution based on integer values</dt>
+          <br></br>
+          <dd>Mixing integers, longs, Integers and Longs:
+          <source>
+ Frequency f = new Frequency();
+ f.addValue(1);
+ f.addValue(new Integer(1));
+ f.addValue(new Long(1));
+ f.addValue(2);
+ f.addValue(new Integer(-1));
+ System.out.prinltn(f.getCount(1));   // displays 3
+ System.out.println(f.getCumPct(0));  // displays 0.2
+ System.out.println(f.getPct(new Integer(1)));  // displays 0.6
+ System.out.println(f.getCumPct(-2));   // displays 0
+ System.out.println(f.getCumPct(10));  // displays 1
+          </source>
+          </dd>
+          <dt>Count string frequencies</dt>
+          <br></br>
+          <dd>Using case-sensitive comparison, alpha sort order (natural comparator):
+          <source>
+Frequency f = new Frequency();
+f.addValue("one");
+f.addValue("One");
+f.addValue("oNe");
+f.addValue("Z");
+System.out.println(f.getCount("one")); // displays 1
+System.out.println(f.getCumPct("Z"));  // displays 0.5
+System.out.println(f.getCumPct("Ot")); // displays 0.25
+          </source>
+          </dd>
+          <dd>Using case-insensitive comparator:
+          <source>
+Frequency f = new Frequency(String.CASE_INSENSITIVE_ORDER);
+f.addValue("one");
+f.addValue("One");
+f.addValue("oNe");
+f.addValue("Z");
+System.out.println(f.getCount("one"));  // displays 3
+System.out.println(f.getCumPct("z"));  // displays 1
+          </source>
+         </dd>
+       </dl>
+      </p>
+      </subsection>
+      <subsection name="1.4 Simple regression">
+        <p>
+         <a href="../apidocs/org/apache/commons/math/stat/regression/SimpleRegression.html">
+          SimpleRegression</a> provides ordinary least squares regression with
+         one independent variable estimating the linear model:
+         </p>
+         <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>
+        <p>
+           <strong>Implementation Notes</strong>: <ul>
+           <li> As observations are added to the model, the sum of x values, y values,
+           cross products (x times y), and squared deviations of x and y from their
+           respective means are updated using updating formulas 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.  All regression
+           statistics are computed from these sums.</li>
+           <li> Inference statistics (confidence intervals, parameter significance levels)
+           are based on 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></li>
+          </ul>
+        </p>
+        <p>
+        Here are some examples.
+        <dl>
+          <dt>Estimate a model based on observations added one at a time</dt>
+          <br></br>
+          <dd>Instantiate a regression instance and add data points
+          <source>
+regression = new SimpleRegression();
+regression.addData(1d, 2d);
+// At this point, with only one observation,
+// all regression statistics will return NaN
+
+regression.addData(3d, 3d);
+// With only two observations,
+// slope and intercept can be computed
+// but inference statistics will return NaN
+
+regression.addData(3d, 3d);
+// Now all statistics are defined.
+         </source>
+         </dd>
+         <dd>Compute some statistics based on observations added so far
+         <source>
+System.out.println(regression.getIntercept());
+// displays intercept of regression line
+
+System.out.println(regression.getSlope());
+// displays slope of regression line
+
+System.out.println(regression.getSlopeStdErr());
+// displays slope standard error
+         </source>
+         </dd>
+         <dd>Use the regression model to predict the y value for a new x value
+         <source>
+System.out.println(regression.predict(1.5d)
+// displays predicted y value for x = 1.5
+         </source>
+         More data points can be added and subsequent getXxx calls will incorporate
+         additional data in statistics.
+         </dd>
+         <dt>Estimate a model from a double[][] array of data points</dt>
+          <br></br>
+          <dd>Instantiate a regression object and load dataset
+          <source>
+double[][] data = { { 1, 3 }, {2, 5 }, {3, 7 }, {4, 14 }, {5, 11 }};
+SimpleRegression regression = new SimpleRegression();
+regression.addData(data);
+          </source>
+          </dd>
+          <dd>Estimate regression model based on data
+         <source>
+System.out.println(regression.getIntercept());
+// displays intercept of regression line
+
+System.out.println(regression.getSlope());
+// displays slope of regression line
+
+System.out.println(regression.getSlopeStdErr());
+// displays slope standard error
+         </source>
+         More data points -- even another double[][] array -- can be added and subsequent
+         getXxx calls will incorporate additional data in statistics.
+         </dd>
+         </dl>
+        </p>
+      </subsection>
+      <subsection name="1.5 Multiple linear regression">
+        <p>
+         <a href="../apidocs/org/apache/commons/math/stat/regression/OLSMultipleLinearRegression.html">
+         OLSMultipleLinearRegression</a> and
+         <a href="../apidocs/org/apache/commons/math/stat/regression/GLSMultipleLinearRegression.html">
+         GLSMultipleLinearRegression</a> provide least squares regression to fit the linear model:
+         </p>
+         <p>
+           <code> Y=X*b+u </code>
+         </p>
+         <p>
+         where Y is an n-vector <b>regressand</b>, X is a [n,k] matrix whose k columns are called
+         <b>regressors</b>, b is k-vector of <b>regression parameters</b> and u is an n-vector 
+         of <b>error terms</b> or <b>residuals</b>.
+         </p>
+         <p>
+          <a href="../apidocs/org/apache/commons/math/stat/regression/OLSMultipleLinearRegression.html">
+          OLSMultipleLinearRegression</a> provides Ordinary Least Squares Regression, and 
+          <a href="../apidocs/org/apache/commons/math/stat/regression/GLSMultipleLinearRegression.html">
+          GLSMultipleLinearRegression</a> implements Generalized Least Squares.  See the javadoc for these
+          classes for details on the algorithms and forumlas used.
+         </p>
+         <p>
+           Data for OLS models can be loaded in a single double[] array, consisting of concatenated rows of data, each containing
+           the regressand (Y) value, followed by regressor values; or using a double[][] array with rows corresponding to
+           observations. GLS models also require a double[][] array representing the covariance matrix of the error terms.  See
+           <a href="../apidocs/org/apache/commons/math/stat/regression/AbstractMultipleLinearRegression.html#newSampleData(double[], int, int)">
+           AbstractMultipleLinearRegression#newSampleData(double[],int,int)</a>,  
+           <a href="../apidocs/org/apache/commons/math/stat/regression/OLSMultipleLinearRegression.html#newSampleData(double[], double[][])">
+           OLSMultipleLinearRegression#newSampleData(double[], double[][])</a> and 
+           <a href="../apidocs/org/apache/commons/math/stat/regression/GLSMultipleLinearRegression.html#newSampleData(double[], double[][], double[][])">
+           GLSMultipleLinearRegression#newSampleData(double[],double[][],double[][])</a> for details.
+         </p>
+         <p>
+           <strong>Usage Notes</strong>: <ul>
+           <li> Data are validated when invoking any of the newSample, newX, newY or newCovariance methods and
+           <code>IllegalArgumentException</code> is thrown when input data arrays do not have matching dimensions
+           or do not contain sufficient data to estimate the model. 
+           </li>
+           <li> By default, regression models are estimated with intercept terms.  In the notation above, this implies that the
+           X matrix contains an initial row identically equal to 1.  X data supplied to the newX or newSample methods should not
+           include this column - the data loading methods will create it automatically.  To estimate a model without an intercept
+           term, set the <code>noIntercept</code> property to <code>true.</code></li>
+          </ul>
+        </p>
+        <p>
+        Here are some examples.
+        <dl>
+         <dt>OLS regression</dt>
+          <br></br>
+          <dd>Instantiate an OLS regression object and load a dataset:
+          <source>
+OLSMultipleLinearRegression regression = new OLSMultipleLinearRegression();
+double[] y = new double[]{11.0, 12.0, 13.0, 14.0, 15.0, 16.0};
+double[] x = new double[6][];
+x[0] = new double[]{0, 0, 0, 0, 0};
+x[1] = new double[]{2.0, 0, 0, 0, 0};
+x[2] = new double[]{0, 3.0, 0, 0, 0};
+x[3] = new double[]{0, 0, 4.0, 0, 0};
+x[4] = new double[]{0, 0, 0, 5.0, 0};
+x[5] = new double[]{0, 0, 0, 0, 6.0};          
+regression.newSample(y, x);
+          </source>
+          </dd>
+          <dd>Get regression parameters and diagnostics:
+         <source>
+double[] beta = regression.estimateRegressionParameters();       
+
+double[] residuals = regression.estimateResiduals();
+
+double[][] parametersVariance = regression.estimateRegressionParametersVariance();
+
+double regressandVariance = regression.estimateRegressandVariance();
+
+double rSquared = regression.caclulateRSquared();
+
+double sigma = regression.estimateRegressionStandardError();
+         </source>
+         </dd>
+         <dt>GLS regression</dt>
+          <br></br>
+          <dd>Instantiate a GLS regression object and load a dataset:
+          <source>
+GLSMultipleLinearRegression regression = new GLSMultipleLinearRegression();
+double[] y = new double[]{11.0, 12.0, 13.0, 14.0, 15.0, 16.0};
+double[] x = new double[6][];
+x[0] = new double[]{0, 0, 0, 0, 0};
+x[1] = new double[]{2.0, 0, 0, 0, 0};
+x[2] = new double[]{0, 3.0, 0, 0, 0};
+x[3] = new double[]{0, 0, 4.0, 0, 0};
+x[4] = new double[]{0, 0, 0, 5.0, 0};
+x[5] = new double[]{0, 0, 0, 0, 6.0};          
+double[][] omega = new double[6][];
+omega[0] = new double[]{1.1, 0, 0, 0, 0, 0};
+omega[1] = new double[]{0, 2.2, 0, 0, 0, 0};
+omega[2] = new double[]{0, 0, 3.3, 0, 0, 0};
+omega[3] = new double[]{0, 0, 0, 4.4, 0, 0};
+omega[4] = new double[]{0, 0, 0, 0, 5.5, 0};
+omega[5] = new double[]{0, 0, 0, 0, 0, 6.6};
+regression.newSampleData(y, x, omega); 
+          </source>
+          </dd>
+         </dl>
+        </p>
+      </subsection>    
+      <subsection name="1.6 Rank transformations">
+      <p>
+         Some statistical algorithms require that input data be replaced by ranks.
+         The <a href="../apidocs/org/apache/commons/math/stat/ranking/package-summary.html">
+         org.apache.commons.math.stat.ranking</a> package provides rank transformation.
+         <a href="../apidocs/org/apache/commons/math/stat/ranking/RankingAlgorithm.html">
+         RankingAlgorithm</a> defines the interface for ranking.  
+         <a href="../apidocs/org/apache/commons/math/stat/ranking/NaturalRanking.html">
+         NaturalRanking</a> provides an implementation that has two configuration options.
+         <ul>
+         <li><a href="../apidocs/org/apache/commons/math/stat/ranking/TiesStrategy.html">
+         Ties strategy</a> deterimines how ties in the source data are handled by the ranking</li>
+         <li><a href="../apidocs/org/apache/commons/math/stat/ranking/NaNStrategy.html">
+         NaN strategy</a> determines how NaN values in the source data are handled.</li>
+         </ul>
+      </p>
+      <p>
+         Examples:
+         <source>
+NaturalRanking ranking = new NaturalRanking(NaNStrategy.MINIMAL,
+TiesStrategy.MAXIMUM);
+double[] data = { 20, 17, 30, 42.3, 17, 50,
+                  Double.NaN, Double.NEGATIVE_INFINITY, 17 };
+double[] ranks = ranking.rank(exampleData);
+         </source>
+         results in <code>ranks</code> containing <code>{6, 5, 7, 8, 5, 9, 2, 2, 5}.</code>
+         <source>
+new NaturalRanking(NaNStrategy.REMOVED,TiesStrategy.SEQUENTIAL).rank(exampleData);   
+         </source>
+         returns <code>{5, 2, 6, 7, 3, 8, 1, 4}.</code>
+      </p>
+      <p>
+        The default <code>NaNStrategy</code> is NaNStrategy.MAXIMAL.  This makes <code>NaN</code>
+        values larger than any other value (including <code>Double.POSITIVE_INFINITY</code>). The
+        default <code>TiesStrategy</code> is <code>TiesStrategy.AVERAGE,</code> which assigns tied
+        values the average of the ranks applicable to the sequence of ties.  See the 
+        <a href="../apidocs/org/apache/commons/math/stat/ranking/NaturalRanking.html">
+        NaturalRanking</a> for more examples and <a href="../apidocs/org/apache/commons/math/stat/ranking/TiesStrategy.html">
+        TiesStrategy</a> and <a href="../apidocs/org/apache/commons/math/stat/ranking/NaNStrategy.html">NaNStrategy</a>
+        for details on these configuration options.
+       </p>
+      </subsection>  
+      <subsection name="1.7 Covariance and correlation">
+        <p>
+          The <a href="../apidocs/org/apache/commons/math/stat/correlation/package-summary.html">
+          org.apache.commons.math.stat.correlation</a> package computes covariances
+          and correlations for pairs of arrays or columns of a matrix.
+          <a href="../apidocs/org/apache/commons/math/stat/correlation/Covariance.html">
+          Covariance</a> computes covariances, 
+          <a href="../apidocs/org/apache/commons/math/stat/correlation/PearsonsCorrelation.html">
+          PearsonsCorrelation</a> provides Pearson's Product-Moment correlation coefficients and
+          <a href="../apidocs/org/apache/commons/math/stat/correlation/SpearmansCorrelation.html">
+          SpearmansCorrelation</a> computes Spearman's rank correlation.
+        </p>
+        <p>
+          <strong>Implementation Notes</strong>
+          <ul>
+          <li>
+            Unbiased covariances are given by the formula <br></br>
+            <code>cov(X, Y) = sum [(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. Non-bias-corrected estimates use 
+           <code>n</code> in place of <code>n - 1.</code>  Whether or not covariances are
+           bias-corrected is determined by the optional parameter, "biasCorrected," which
+           defaults to <code>true.</code>      
+          </li>
+          <li>
+          <a href="../apidocs/org/apache/commons/math/stat/correlation/PearsonsCorrelation.html">
+          PearsonsCorrelation</a> computes correlations defined by the formula <br></br>
+          <code>cor(X, Y) = sum[(x<sub>i</sub> - E(X))(y<sub>i</sub> - E(Y))] / [(n - 1)s(X)s(Y)]</code><br/>
+          where <code>E(X)</code> and <code>E(Y)</code> are means of <code>X</code> and <code>Y</code>
+          and <code>s(X)</code>, <code>s(Y)</code> are standard deviations.
+          </li>
+          <li>
+          <a href="../apidocs/org/apache/commons/math/stat/correlation/SpearmansCorrelation.html">
+          SpearmansCorrelation</a> applies a rank transformation to the input data and computes Pearson's
+          correlation on the ranked data.  The ranking algorithm is configurable. By default, 
+          <a href="../apidocs/org/apache/commons/math/stat/ranking/NaturalRanking.html">
+          NaturalRanking</a> with default strategies for handling ties and NaN values is used.
+          </li> 
+          </ul>
+        </p>
+        <p>
+        <strong>Examples:</strong>
+        <dl>
+          <dt><strong>Covariance of 2 arrays</strong></dt>
+          <br></br>
+          <dd>To compute the unbiased covariance between 2 double arrays,
+          <code>x</code> and <code>y</code>, use:
+          <source>
+new Covariance().covariance(x, y)
+          </source>
+          For non-bias-corrected covariances, use
+          <source>
+covariance(x, y, false)
+          </source>
+          </dd>
+          <br></br>
+          <dt><strong>Covariance matrix</strong></dt>
+          <br></br>
+          <dd> A covariance matrix over the columns of a source matrix <code>data</code>
+          can be computed using
+          <source>
+new Covariance().computeCovarianceMatrix(data)
+          </source>
+          The i-jth entry of the returned matrix is the unbiased covariance of the ith and jth
+          columns of <code>data.</code> As above, to get non-bias-corrected covariances,
+          use 
+         <source>
+computeCovarianceMatrix(data, false)
+         </source>
+          </dd>
+           <br></br>
+          <dt><strong>Pearson's correlation of 2 arrays</strong></dt>
+          <br></br>
+          <dd>To compute the Pearson's product-moment correlation between two double arrays
+          <code>x</code> and <code>y</code>, use:
+          <source>
+new PearsonsCorrelation().correlation(x, y)
+          </source>
+          </dd>
+          <br></br>
+          <dt><strong>Pearson's correlation matrix</strong></dt>
+          <br></br>
+          <dd> A (Pearson's) correlation matrix over the columns of a source matrix <code>data</code>
+          can be computed using
+          <source>
+new PearsonsCorrelation().computeCorrelationMatrix(data)
+          </source>
+          The i-jth entry of the returned matrix is the Pearson's product-moment correlation between the
+          ith and jth columns of <code>data.</code> 
+          </dd>
+           <br></br>
+          <dt><strong>Pearson's correlation significance and standard errors</strong></dt>
+          <br></br>
+          <dd> To compute standard errors and/or significances of correlation coefficients
+          associated with Pearson's correlation coefficients, start by creating a
+          <code>PearsonsCorrelation</code> instance
+          <source>
+PearsonsCorrelation correlation = new PearsonsCorrelation(data);
+          </source>
+          where <code>data</code> is either a rectangular array or a <code>RealMatrix.</code>
+          Then the matrix of standard errors is
+          <source>
+correlation.getCorrelationStandardErrors();
+          </source>
+          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><br/>
+           where <code>r</code> is the estimated correlation coefficient and 
+          <code>n</code> is the number of observations in the source dataset.<br/><br/>
+          <strong>p-values</strong> for the (2-sided) null hypotheses that elements of
+          a correlation matrix are zero populate the RealMatrix returned by
+          <source>
+correlation.getCorrelationPValues()
+          </source>
+          <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></br>
+           <code>|r<sub>ij</sub>|((n - 2) / (1 - r<sub>ij</sub><sup>2</sup>))<sup>1/2</sup></code>,
+           where <code>r<sub>ij</sub></code> is the estimated correlation between the ith and jth
+           columns of the source array or RealMatrix. This is sometimes referred to as the 
+           <i>significance</i> of the coefficient.<br/><br/>
+           For example, if <code>data</code> is a RealMatrix with 2 columns and 10 rows, then 
+           <source>
+new PearsonsCorrelation(data).getCorrelationPValues().getEntry(0,1)
+           </source>
+           is the significance of the Pearson's correlation coefficient between the two columns
+           of <code>data</code>.  If this value is less than .01, we can say that the correlation
+           between the two columns of data is significant at the 99% level.
+          </dd>
+           <br></br>
+          <dt><strong>Spearman's rank correlation coefficient</strong></dt>
+          <br></br>
+          <dd>To compute the Spearman's rank-moment correlation between two double arrays
+          <code>x</code> and <code>y</code>:
+          <source>
+new SpearmansCorrelation().correlation(x, y)
+          </source>
+          This is equivalent to 
+          <source>
+RankingAlgorithm ranking = new NaturalRanking();
+new PearsonsCorrelation().correlation(ranking.rank(x), ranking.rank(y))
+          </source>
+          </dd>
+           <br></br>
+        </dl>
+        </p>
+      </subsection>
+            <subsection name="1.8 Statistical tests">
+        <p>
+          The interfaces and implementations in the
+          <a href="../apidocs/org/apache/commons/math/stat/inference/">
+          org.apache.commons.math.stat.inference</a> package provide
+          <a href="http://www.itl.nist.gov/div898/handbook/prc/section2/prc22.htm">
+          Student's t</a>,
+          <a href="http://www.itl.nist.gov/div898/handbook/eda/section3/eda35f.htm">
+          Chi-Square</a> and 
+          <a href="http://www.itl.nist.gov/div898/handbook/prc/section4/prc43.htm">
+          One-Way ANOVA</a> test statistics as well as
+          <a href="http://www.cas.lancs.ac.uk/glossary_v1.1/hyptest.html#pvalue">
+          p-values</a> associated with <code>t-</code>,
+          <code>Chi-Square</code> and <code>One-Way ANOVA</code> tests.  The
+          interfaces are
+          <a href="../apidocs/org/apache/commons/math/stat/inference/TTest.html">
+          TTest</a>,
+          <a href="../apidocs/org/apache/commons/math/stat/inference/ChiSquareTest.html">
+          ChiSquareTest</a>, and
+          <a href="../apidocs/org/apache/commons/math/stat/inference/OneWayAnova.html">
+          OneWayAnova</a> with provided implementations
+          <a href="../apidocs/org/apache/commons/math/stat/inference/TTestImpl.html">
+          TTestImpl</a>,
+          <a href="../apidocs/org/apache/commons/math/stat/inference/ChiSquareTestImpl.html">
+          ChiSquareTestImpl</a> and
+          <a href="../apidocs/org/apache/commons/math/stat/inference/OneWayAnovaImpl.html">
+          OneWayAnovaImpl</a>, respectively.
+          The <a href="../apidocs/org/apache/commons/math/stat/inference/TestUtils.html">
+          TestUtils</a> class provides static methods to get test instances or
+          to compute test statistics directly.  The examples below all use the
+          static methods in <code>TestUtils</code> to execute tests.  To get
+          test object instances, either use e.g., <code>TestUtils.getTTest()</code>
+          or use the implementation constructors directly, e.g. <code>new TTestImpl()</code>.
+        </p>
+        <p>
+          <strong>Implementation Notes</strong>
+          <ul>
+          <li>Both one- and two-sample t-tests are supported.  Two sample tests
+          can be either paired or unpaired and the unpaired two-sample tests can
+          be conducted under the assumption of equal subpopulation variances or
+          without this assumption.  When equal variances is assumed, a pooled
+          variance estimate is used to compute the t-statistic and the degrees
+          of freedom used in the t-test equals the sum of the sample sizes minus 2.
+          When equal variances is not assumed, the t-statistic uses both sample
+          variances and the
+          <a href="http://www.itl.nist.gov/div898/handbook/prc/section3/gifs/nu3.gif">
+          Welch-Satterwaite approximation</a> is used to compute the degrees
+          of freedom.  Methods to return t-statistics and p-values are provided in each
+          case, as well as boolean-valued methods to perform fixed significance
+          level tests.  The names of methods or methods that assume equal
+          subpopulation variances always start with "homoscedastic."  Test or
+          test-statistic methods that just start with "t" do not assume equal
+          variances. See the examples below and the API documentation for
+          more details.</li>
+          <li>The validity of the p-values returned by the t-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></li>
+          <li>p-values returned by t-, chi-square and Anova tests are exact, based
+           on numerical approximations to the t-, chi-square and F distributions in the
+           <code>distributions</code> package. </li>
+           <li>p-values returned by t-tests are for two-sided tests and the boolean-valued
+           methods supporting fixed significance level tests assume that the hypotheses
+           are two-sided.  One sided tests can be performed by dividing returned p-values
+           (resp. critical values) by 2.</li>
+           <li>Degrees of freedom for chi-square tests are integral values, based on the
+           number of observed or expected counts (number of observed counts - 1)
+           for the goodness-of-fit tests and (number of columns -1) * (number of rows - 1)
+           for independence tests.</li>
+          </ul>
+          </p>
+          <p>
+        <strong>Examples:</strong>
+        <dl>
+          <dt><strong>One-sample <code>t</code> tests</strong></dt>
+          <br></br>
+          <dd>To compare the mean of a double[] array to a fixed value:
+          <source>
+double[] observed = {1d, 2d, 3d};
+double mu = 2.5d;
+System.out.println(TestUtils.t(mu, observed));
+          </source>
+          The code above will display the t-statisitic associated with a one-sample
+           t-test comparing the mean of the <code>observed</code> values against
+           <code>mu.</code>
+          </dd>
+          <dd>To compare the mean of a dataset described by a
+          <a href="../apidocs/org/apache/commons/math/stat/descriptive/StatisticalSummary.html">
+          StatisticalSummary</a>  to a fixed value:
+          <source>
+double[] observed ={1d, 2d, 3d};
+double mu = 2.5d;
+SummaryStatistics sampleStats = new SummaryStatistics();
+for (int i = 0; i < observed.length; i++) {
+    sampleStats.addValue(observed[i]);
+}
+System.out.println(TestUtils.t(mu, observed));
+</source>
+           </dd>
+           <dd>To compute the p-value associated with the null hypothesis that the mean
+            of a set of values equals a point estimate, against the two-sided alternative that
+            the mean is different from the target value:
+            <source>
+double[] observed = {1d, 2d, 3d};
+double mu = 2.5d;
+System.out.println(TestUtils.tTest(mu, observed));
+           </source>
+          The snippet above will display the p-value associated with the null
+          hypothesis that the mean of the population from which the
+          <code>observed</code> values are drawn equals <code>mu.</code>
+          </dd>
+          <dd>To perform the test using a fixed significance level, use:
+          <source>
+TestUtils.tTest(mu, observed, alpha);
+          </source>
+          where <code>0 < alpha < 0.5</code> is the significance level of
+          the test.  The boolean value returned will be <code>true</code> iff the
+          null hypothesis can be rejected with confidence <code>1 - alpha</code>.
+          To test, for example at the 95% level of confidence, use
+          <code>alpha = 0.05</code>
+          </dd>
+          <br></br>
+          <dt><strong>Two-Sample t-tests</strong></dt>
+          <br></br>
+          <dd><strong>Example 1:</strong> Paired test evaluating
+          the null hypothesis that the mean difference between corresponding
+          (paired) elements of the <code>double[]</code> arrays
+          <code>sample1</code> and <code>sample2</code> is zero.
+          <p>
+          To compute the t-statistic:
+          <source>
+TestUtils.pairedT(sample1, sample2);
+          </source>
+           </p>
+           <p>
+           To compute the p-value:
+           <source>
+TestUtils.pairedTTest(sample1, sample2);
+           </source>
+           </p>
+           <p>
+           To perform a fixed significance level test with alpha = .05:
+           <source>
+TestUtils.pairedTTest(sample1, sample2, .05);
+           </source>
+           </p>
+           The last example will return <code>true</code> iff the p-value
+           returned by <code>TestUtils.pairedTTest(sample1, sample2)</code>
+           is less than <code>.05</code>
+           </dd>
+           <dd><strong>Example 2: </strong> unpaired, two-sided, two-sample t-test using
+           <code>StatisticalSummary</code> instances, without assuming that
+           subpopulation variances are equal.
+           <p>
+           First create the <code>StatisticalSummary</code> instances.  Both
+           <code>DescriptiveStatistics</code> and <code>SummaryStatistics</code>
+           implement this interface.  Assume that <code>summary1</code> and
+           <code>summary2</code> are <code>SummaryStatistics</code> instances,
+           each of which has had at least 2 values added to the (virtual) dataset that
+           it describes.  The sample sizes do not have to be the same -- all that is required
+           is that both samples have at least 2 elements.
+           </p>
+           <p><strong>Note:</strong> The <code>SummaryStatistics</code> class does
+           not store the dataset that it describes in memory, but it does compute all
+           statistics necessary to perform t-tests, so this method can be used to
+           conduct t-tests with very large samples.  One-sample tests can also be
+           performed this way.
+           (See <a href="#1.2 Descriptive statistics">Descriptive statistics</a> for details
+           on the <code>SummaryStatistics</code> class.)
+           </p>
+           <p>
+          To compute the t-statistic:
+          <source>
+TestUtils.t(summary1, summary2);
+          </source>
+           </p>
+           <p>
+           To compute the p-value:
+           <source>
+TestUtils.tTest(sample1, sample2);
+           </source>
+           </p>
+           <p>
+           To perform a fixed significance level test with alpha = .05:
+           <source>
+TestUtils.tTest(sample1, sample2, .05);
+           </source>
+           </p>
+           <p>
+           In each case above, the test does not assume that the subpopulation
+           variances are equal.  To perform the tests under this assumption,
+           replace "t" at the beginning of the method name with "homoscedasticT"
+           </p>
+           </dd>
+           <br></br>
+          <dt><strong>Chi-square tests</strong></dt>
+          <br></br>
+          <dd>To compute a chi-square statistic measuring the agreement between a
+          <code>long[]</code> array of observed counts and a <code>double[]</code>
+          array of expected counts, use:
+          <source>
+long[] observed = {10, 9, 11};
+double[] expected = {10.1, 9.8, 10.3};
+System.out.println(TestUtils.chiSquare(expected, observed));
+          </source>
+          the value displayed will be
+          <code>sum((expected[i] - observed[i])^2 / expected[i])</code>
+          </dd>
+          <dd> To get the p-value associated with the null hypothesis that
+          <code>observed</code> conforms to <code>expected</code> use:
+          <source>
+TestUtils.chiSquareTest(expected, observed);
+          </source>
+          </dd>
+          <dd> To test the null hypothesis that <code>observed</code> conforms to
+          <code>expected</code> with <code>alpha</code> siginficance level
+          (equiv. <code>100 * (1-alpha)%</code> confidence) where <code>
+          0 < alpha < 1 </code> use:
+          <source>
+TestUtils.chiSquareTest(expected, observed, alpha);
+          </source>
+          The boolean value returned will be <code>true</code> iff the null hypothesis
+          can be rejected with confidence <code>1 - alpha</code>.
+          </dd>
+          <dd>To compute a chi-square statistic 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 a two-dimensional (long[][])
+          <code>counts</code> array viewed as a two-way table, use:
+          <source>
+TestUtils.chiSquareTest(counts);
+          </source>
+          The rows of the 2-way table are
+          <code>count[0], ... , count[count.length - 1]. </code><br></br>
+          The chi-square statistic returned is
+          <code>sum((counts[i][j] - expected[i][j])^2/expected[i][j])</code>
+          where the sum is taken over all table entries and
+          <code>expected[i][j]</code> is the product of the row and column sums at
+          row <code>i</code>, column <code>j</code> divided by the total count.
+          </dd>
+          <dd>To compute the p-value associated with the null hypothesis that
+          the classifications represented by the counts in the columns of the input 2-way
+          table are independent of the rows, use:
+          <source>
+ TestUtils.chiSquareTest(counts);
+          </source>
+          </dd>
+          <dd>To perform a chi-square test of independence with <code>alpha</code>
+          siginficance level (equiv. <code>100 * (1-alpha)%</code> confidence)
+          where <code>0 < alpha < 1 </code> use:
+          <source>
+TestUtils.chiSquareTest(counts, alpha);
+          </source>
+          The boolean value returned will be <code>true</code> iff the null
+          hypothesis can be rejected with confidence <code>1 - alpha</code>.
+          </dd>
+          <br></br>
+          <dt><strong>One-Way Anova tests</strong></dt>
+          <br></br>
+          <dd>To conduct a One-Way Analysis of Variance (ANOVA) to evaluate the
+          null hypothesis that the means of a collection of univariate datasets
+          are the same, start by loading the datasets into a collection, e.g.
+          <source>
+double[] classA =
+   {93.0, 103.0, 95.0, 101.0, 91.0, 105.0, 96.0, 94.0, 101.0 };
+double[] classB =
+   {99.0, 92.0, 102.0, 100.0, 102.0, 89.0 };
+double[] classC =
+   {110.0, 115.0, 111.0, 117.0, 128.0, 117.0 };
+List classes = new ArrayList();
+classes.add(classA);
+classes.add(classB);
+classes.add(classC);
+          </source>
+          Then you can compute ANOVA F- or p-values associated with the
+          null hypothesis that the class means are all the same
+          using a <code>OneWayAnova</code> instance or <code>TestUtils</code>
+          methods:
+          <source>
+double fStatistic = TestUtils.oneWayAnovaFValue(classes); // F-value
+double pValue = TestUtils.oneWayAnovaPValue(classes);     // P-value
+          </source>
+          To test perform a One-Way Anova test with signficance level set at 0.01
+          (so the test will, assuming assumptions are met, reject the null
+          hypothesis incorrectly only about one in 100 times), use
+          <source>
+TestUtils.oneWayAnovaTest(classes, 0.01); // returns a boolean
+                                          // true means reject null hypothesis
+          </source>
+          </dd>
+        </dl>
+        </p>
+      </subsection>
+    </section>
+  </body>
+</document>
diff --git a/src/site/xdoc/userguide/transform.xml b/src/site/xdoc/userguide/transform.xml
new file mode 100644
index 0000000..3d64ef2
--- /dev/null
+++ b/src/site/xdoc/userguide/transform.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0"?>
+
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+  
+<?xml-stylesheet type="text/xsl" href="./xdoc.xsl"?>
+<!-- $Revision: 937893 $ $Date: 2010-04-25 23:42:47 +0200 (dim. 25 avril 2010) $ -->
+<document url="transform.html">
+
+  <properties>
+    <title>The Commons Math User Guide - Transform methods</title>
+  </properties>
+
+  <body>
+    <section name="10 Transform methods">
+      <p>
+         This package provides a few transformers for signal analysis. All transformers
+         provide both direct and inverse transforms.
+         <ul>
+           <li><a href="../apidocs/org/apache/commons/math/transform/FastFourierTransformer.html">
+            FastFourierTransformer</a> (produces <code>Complex</code> results)</li>
+           <li><a href="../apidocs/org/apache/commons/math/transform/FastCosineTransformer.html">
+           FastCosineTransformer</a> (produces real results)</li>
+           <li><a href="../apidocs/org/apache/commons/math/transform/FastSineTransformer.html">
+           FastSineTransformer</a> (produces real results)</li>
+           <li><a href="../apidocs/org/apache/commons/math/transform/FastHadamardTransformer.html">
+           FastHadamardTransformer</a> (produces real results)</li>
+         </ul>
+      </p>
+     </section>
+  </body>
+</document>
diff --git a/src/site/xdoc/userguide/utilities.xml b/src/site/xdoc/userguide/utilities.xml
new file mode 100644
index 0000000..02b4234
--- /dev/null
+++ b/src/site/xdoc/userguide/utilities.xml
@@ -0,0 +1,195 @@
+<?xml version="1.0"?>
+
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+  
+<?xml-stylesheet type="text/xsl" href="./xdoc.xsl"?>
+<!-- $Revision: 958552 $ $Date: 2010-06-28 14:09:02 +0200 (lun. 28 juin 2010) $ -->
+<document url="utilities.html">
+
+<properties>
+    <title>The Commons Math User Guide - Utilites</title>
+</properties>
+
+<body>
+
+<section name="6 Utilities">
+
+<subsection name="6.1 Overview" href="overview">
+    <p>
+    The <a href="../apidocs/org/apache/commons/math/util/package-summary.html">
+    org.apache.commons.math.util</a> package collects a group of array utilities,
+    value transformers,  and numerical routines used by implementation classes in
+    commons-math.
+    </p>
+</subsection>
+
+<subsection name="6.2 Double array utilities" href="arrays">
+    <p>
+    To maintain statistics based on a "rolling" window of values, a resizable 
+    array implementation was developed and is provided for reuse in the 
+    <code>util</code> package.  The core functionality provided is described in
+    the documentation for the interface, 
+    <a href="../apidocs/org/apache/commons/math/util/DoubleArray.html">
+    DoubleArray</a>.  This interface adds one method,
+    <code>addElementRolling(double)</code> to basic list accessors. 
+    The <code>addElementRolling</code> method adds an element 
+    (the actual parameter) to the end of the list and removes the first element
+     in the list.
+    </p>
+    <p>
+    The <a href="../apidocs/org/apache/commons/math/util/ResizableDoubleArray.html">
+    ResizableDoubleArray</a> class provides a configurable, array-backed
+    implementation of the <code>DoubleArray</code> interface.
+    When <code>addElementRolling</code> is invoked, the underlying
+    array is expanded if necessary, the new element is added to the end of the
+    array and the "usable window" of the array is moved forward, so that
+    the first element is effectively discarded, what was the second becomes the
+    first, and so on.  To efficiently manage storage, two maintenance
+    operations need to be periodically performed -- orphaned elements at the
+    beginning of the array need to be reclaimed and space for new elements at
+    the end needs to be created.  Both of these operations are handled
+    automatically, with frequency / effect driven by the configuration
+    properties <code>expansionMode</code>, <code>expansionFactor</code> and
+    <code>contractionCriteria.</code>  See 
+    <a href="../apidocs/org/apache/commons/math/util/ResizableDoubleArray.html">
+    ResizableDoubleArray</a>
+    for details. 
+    </p>
+</subsection>
+
+<subsection name="6.3 int/double hash map" href="int_double_hash_map">
+    <p>
+    The <a href="../apidocs/org/apache/commons/math/util/OpenIntToDoubleHashMap.html">
+    OpenIntToDoubleHashMap</a> class provides a specialized hash map
+    implementation for int/double. This implementation has a much smaller memory
+    overhead than standard <code>java.util.HashMap</code> class. It uses open addressing
+    and primitive arrays, which greatly reduces the number of intermediate objects and
+    improve data locality.
+    </p>
+</subsection>
+
+<subsection name="6.4 Continued Fractions" href="continued_fractions">
+  <p>
+    The <a href="../apidocs/org/apache/commons/math/util/ContinuedFraction.html">
+    ContinuedFraction</a> class provides a generic way to create and evaluate
+    continued fractions.  The easiest way to create a continued fraction is
+    to subclass <code>ContinuedFraction</code> and override the
+    <code>getA</code> and <code>getB</code> methods which return
+    the continued fraction terms.  The precise definition of these terms is
+    explained in <a href="http://mathworld.wolfram.com/ContinuedFraction.html">
+    Continued Fraction, equation (1)</a> from MathWorld.
+  </p>
+  <p>
+    As an example, the constant Pi can be computed using a <a href="http://functions.wolfram.com/Constants/Pi/10/0002/">continued fraction</a>.  The following anonymous class
+    provides the implementation:
+    <source>ContinuedFraction c = new ContinuedFraction() {
+    public double getA(int n, double x) {
+        switch(n) {
+            case 0: return 3.0;
+            default: return 6.0;
+        }
+    }
+    
+    public double getB(int n, double x) {
+        double y = (2.0 * n) - 1.0;
+        return y * y;
+    }
+}</source>
+  </p>
+  <p>
+    Then, to evalute Pi, simply call any of the <code>evalute</code> methods
+    (Note, the point of evalution in this example is meaningless since Pi is a
+    constant).
+  </p>
+  <p>
+    For a more practical use of continued fractions, consider the <a href="http://functions.wolfram.com/ElementaryFunctions/Exp/10/">exponential function</a>.
+    The following anonymous class provides its implementation:
+    <source>ContinuedFraction c = new ContinuedFraction() {
+    public double getA(int n, double x) {
+        if (n % 2 == 0) {
+            switch(n) {
+                case 0: return 1.0;
+                default: return 2.0;
+            }
+        } else {
+            return n;
+        }
+    }
+    
+    public double getB(int n, double x) {
+        if (n % 2 == 0) {
+            return -x;
+        } else {
+            return x;
+        }
+    }
+}</source>
+  </p>
+  <p>
+    Then, to evalute <i>e</i><sup>x</sup> for any value x, simply call any of the
+    <code>evalute</code> methods.
+  </p>
+</subsection>
+
+<subsection name="6.5 binomial coefficients, factorials and other common math functions" href="math_utils">
+    <p>
+    A collection of reusable math functions is provided in the
+    <a href="../apidocs/org/apache/commons/math/util/MathUtils.html">MathUtils</a>
+    utility class.  MathUtils currently includes methods to compute the following: <ul>
+    <li>
+    Binomial coefficients -- "n choose k" available as an (exact) long value,  
+    <code>binomialCoefficient(int, int)</code> for small n, k; as a double,
+    <code>binomialCoefficientDouble(int, int)</code> for larger values; and in
+    a "super-sized" version, <code>binomialCoefficientLog(int, int)</code> 
+    that returns the natural logarithm of the value.</li>
+    <li>
+    Factorials -- like binomial coefficients, these are available as exact long
+    values, <code>factorial(int)</code>;  doubles, 
+    <code>factorialDouble(int)</code>; or logs, <code>factorialLog(int)</code>. </li>
+    <li>
+    Hyperbolic sine and cosine functions -- 
+    <code>cosh(double), sinh(double)</code></li>
+    <li>
+    sign (+1 if argument > 0, 0 if x = 0, and -1 if x < 0) and 
+    indicator (+1.0 if argument  >= 0 and -1.0 if argument < 0) functions
+    for variables of all primitive numeric types.</li>
+    <li>
+    a hash function, <code>hash(double),</code> returning a long-valued
+    hash code for a double value.
+    </li>
+    <li>
+    Convience methods to round floating-point number to arbitrary precision.
+    </li>
+    <li>
+    Least common multiple and greatest common denominator functions.
+    </li>
+    </ul>
+    </p>
+</subsection>
+
+<subsection name="6.7 Miscellaneous" href="math_utils">
+  The <a href="../apidocs/org/apache/commons/math/util/MultidimensionalCounter.html">
+    MultidimensionalCounter</a> is a utility class that converts a set of indices
+  (identifying points in a multidimensional space) to a single index (e.g. identifying
+  a location in a one-dimensional array.
+</subsection>
+
+</section>
+
+</body>
+</document>
diff --git a/src/site/xdoc/userguide/xdoc.xsl b/src/site/xdoc/userguide/xdoc.xsl
new file mode 100644
index 0000000..cfbd5f5
--- /dev/null
+++ b/src/site/xdoc/userguide/xdoc.xsl
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+  -->
+
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+    <xsl:template match="/">
+        <html xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+            <head>
+                <xsl:apply-templates select="/document/properties/title"/>
+                <xsl:apply-templates select="/document/meta"/>
+                <style type="text/css">
+                    @import url("../style/tigris.css");
+                    @import url("../style/maven.css");
+                    @import url("../style/project.css");
+                </style>
+                <link rel="stylesheet" href="../style/print.css" type="text/css" media="print"></link>
+            </head>
+            <xsl:apply-templates select="/document/body"/>
+        </html>
+    </xsl:template>
+    <xsl:template match="body">
+        <body>
+            <div class="app">
+                <xsl:apply-templates/>
+            </div>
+        </body> 
+    </xsl:template>
+    <xsl:template match="section">
+        <div>
+            <h3><xsl:value-of select="@name"/></h3>
+            <xsl:apply-templates/>
+        </div>
+    </xsl:template>
+    <xsl:template match="subsection">
+        <div>
+            <h4><xsl:value-of select="@name"/></h4>
+            <xsl:apply-templates/>
+        </div>
+    </xsl:template>
+    <xsl:template match="source">
+        <div id="source">
+            <pre>
+                <xsl:apply-templates/>
+            </pre>
+        </div>
+    </xsl:template> 
+    <xsl:template match="node()|@*">
+        <xsl:copy>
+            <xsl:apply-templates select="@*"/>
+            <xsl:apply-templates/>
+        </xsl:copy>
+    </xsl:template>
+</xsl:stylesheet>
diff --git a/src/test/R/ChiSquareDistributionTestCases.R b/src/test/R/ChiSquareDistributionTestCases.R
new file mode 100644
index 0000000..78929ef
--- /dev/null
+++ b/src/test/R/ChiSquareDistributionTestCases.R
@@ -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.
+#
+#------------------------------------------------------------------------------
+# R source file to validate ChiSquare distribution tests in
+# org.apache.commons.math.distribution.ChiSquareDistributionTest
+#
+# To run the test, install R, put this file and testFunctions
+# into the same directory, launch R from this directory and then enter
+# source("<name-of-this-file>")
+#
+#-----------------------------------------------------------------------------
+tol <- 1E-9
+
+# Function definitions
+source("testFunctions")           # utility test functions
+
+# function to verify distribution computations
+verifyDistribution <- function(points, expected, df, tol) {
+ rDistValues <- rep(0, length(points))
+    i <- 0
+    for (point in points) {
+        i <- i + 1
+        rDistValues[i] <- pchisq(point, df, log = FALSE)
+    }
+    output <- c("Distribution test df = ", df)
+    if (assertEquals(expected, rDistValues, tol, "Distribution Values")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }       
+}
+
+# function to verify density computations
+verifyDensity <- function(points, expected, df, tol) {
+ rDensityValues <- rep(0, length(points))
+    i <- 0
+    for (point in points) {
+        i <- i + 1
+        rDensityValues[i] <- dchisq(point, df, log = FALSE)
+    }
+    output <- c("Density test df = ", df)
+    if (assertEquals(expected, rDensityValues, tol, "Density Values")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }       
+}
+
+# function to verify quantiles
+verifyQuantiles <- function(points, expected, df, tol) {
+	rQuantileValues <- rep(0, length(points))
+    i <- 0
+    for (point in points) {
+        i <- i + 1
+        rQuantileValues[i] <- qchisq(point, df, log = FALSE)
+    }
+    output <- c("Quantile test df = ", df)
+    if (assertEquals(expected, rQuantileValues, tol, "Quantile Values")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }    
+}
+
+#--------------------------------------------------------------------------
+cat("ChiSquare Distribution test cases\n")
+
+df <- 5
+distributionValues <- c(0.001, 0.01, 0.025, 0.05, 0.1, 0.999, 0.990, 0.975, 0.950, 0.900)
+densityValues <- c(0.0115379817652, 0.0415948507811, 0.0665060119842, 0.0919455953114, 0.121472591024,
+                 0.000433630076361, 0.00412780610309, 0.00999340341045, 0.0193246438937, 0.0368460089216)
+distributionPoints <- c(0.210212602629, 0.554298076728, 0.831211613487, 1.14547622606, 1.61030798696,
+                20.5150056524, 15.0862724694, 12.8325019940, 11.0704976935, 9.23635689978)              
+verifyQuantiles(distributionValues, distributionPoints, df, tol)
+verifyDistribution(distributionPoints, distributionValues, df, tol)
+verifyDensity(distributionPoints, densityValues, df, tol)
+
+df <- .1
+distributionPoints <- c(1.16892641146e-60, 1.16892641146e-40, 1.06313237798e-32, 1.11477509638e-26, 1.16892641146e-20,
+                5.47291719746, 2.17525480018, 1.13434752351, 0.531864604852, 0.152634227818)
+verifyQuantiles(distributionValues, distributionPoints, df, tol)
+verifyDistribution(distributionPoints, distributionValues, df, tol)
+
+displayDashes(WIDTH)
diff --git a/src/test/R/FDistributionTestCases.R b/src/test/R/FDistributionTestCases.R
new file mode 100644
index 0000000..e5f3656
--- /dev/null
+++ b/src/test/R/FDistributionTestCases.R
@@ -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.
+#
+#------------------------------------------------------------------------------
+# R source file to validate F distribution tests in
+# org.apache.commons.math.distribution.TDistributionTest
+#
+# To run the test, install R, put this file and testFunctions
+# into the same directory, launch R from this directory and then enter
+# source("<name-of-this-file>")
+#
+#-----------------------------------------------------------------------------
+tol <- 1E-9
+
+# Function definitions
+source("testFunctions")           # utility test functions
+
+# function to verify distribution computations
+verifyDistribution <- function(points, expected, numeratorDf, denominatorDf, tol) {
+ rDistValues <- rep(0, length(points))
+    i <- 0
+    for (point in points) {
+        i <- i + 1
+        rDistValues[i] <- pf(point, numeratorDf, denominatorDf, log = FALSE)
+    }
+    output <- c("Distribution test numerator df = ", numeratorDf, " denominator df = ", denominatorDf)
+    if (assertEquals(expected, rDistValues, tol, "Distribution Values")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }       
+}
+
+# function to verify density computations
+verifyDensity <- function(points, expected, numeratorDf, denominatorDf, tol) {
+ rDensityValues <- rep(0, length(points))
+    i <- 0
+    for (point in points) {
+        i <- i + 1
+        rDensityValues[i] <- df(point, numeratorDf, denominatorDf, log = FALSE)
+    }
+    output <- c("Density test numerator df = ", numeratorDf, " denominator df = ", denominatorDf)
+    if (assertEquals(expected, rDensityValues, tol, "Density Values")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }       
+}
+
+# function to verify quantiles
+verifyQuantiles <- function(points, expected, numeratorDf, denominatorDf, tol) {
+	rQuantileValues <- rep(0, length(points))
+    i <- 0
+    for (point in points) {
+        i <- i + 1
+        rQuantileValues[i] <- qf(point, numeratorDf, denominatorDf, log = FALSE)
+    }
+    output <- c("Quantile test numerator df = ", numeratorDf, " denominator df = ", denominatorDf)
+    if (assertEquals(expected, rQuantileValues, tol, "Quantile Values")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }    
+}
+
+#--------------------------------------------------------------------------
+cat("F Distribution test cases\n")
+
+numeratorDf <- 5
+denominatorDf <- 6
+distributionValues <- c(0.001, 0.01, 0.025, 0.05, 0.1, 0.999, 0.990, 0.975, 0.950, 0.900)
+densityValues <- c(0.0689156576706, 0.236735653193, 0.364074131941, 0.481570789649, 0.595880479994,
+                 0.000133443915657, 0.00286681303403, 0.00969192007502, 0.0242883861471, 0.0605491314658)
+distributionPoints <- c(0.0346808448626, 0.0937009113303, 0.143313661184, 0.202008445998, 0.293728320107,
+                20.8026639595, 8.74589525602, 5.98756512605, 4.38737418741, 3.10751166664)              
+verifyQuantiles(distributionValues, distributionPoints, numeratorDf, denominatorDf, tol)
+verifyDistribution(distributionPoints, distributionValues, numeratorDf, denominatorDf, tol)
+verifyDensity(distributionPoints, densityValues, numeratorDf, denominatorDf, tol)
+
+displayDashes(WIDTH)
diff --git a/src/test/R/GammaDistributionTestCases.R b/src/test/R/GammaDistributionTestCases.R
new file mode 100644
index 0000000..7cbefda
--- /dev/null
+++ b/src/test/R/GammaDistributionTestCases.R
@@ -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.
+#
+#------------------------------------------------------------------------------
+# R source file to validate Gamma distribution tests in
+# org.apache.commons.math.distribution.GammaDistributionTest
+#
+# To run the test, install R, put this file and testFunctions
+# into the same directory, launch R from this directory and then enter
+# source("<name-of-this-file>")
+#
+#-----------------------------------------------------------------------------
+tol <- 1E-9
+
+# Function definitions
+source("testFunctions")           # utility test functions
+
+# function to verify distribution computations
+verifyDistribution <- function(points, expected, alpha, beta, tol) {
+ rDistValues <- rep(0, length(points))
+    i <- 0
+    for (point in points) {
+        i <- i + 1
+        rDistValues[i] <- pgamma(point, shape=alpha, scale=beta, log = FALSE)
+    }
+    output <- c("Distribution test shape = ", shape, " scale = ", scale)
+    if (assertEquals(expected, rDistValues, tol, "Distribution Values")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }       
+}
+
+# function to verify density computations
+verifyDensity <- function(points, expected, alpha, beta, tol) {
+ rDensityValues <- rep(0, length(points))
+    i <- 0
+    for (point in points) {
+        i <- i + 1
+        rDensityValues[i] <- dgamma(point, shape=alpha, scale=beta, log = FALSE)
+    }
+    output <- c("Density test shape = ", shape, " scale = ", scale)
+    if (assertEquals(expected, rDensityValues, tol, "Density Values")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }       
+}
+
+# function to verify quantiles
+verifyQuantiles <- function(points, expected, alpha, beta, tol) {
+	rQuantileValues <- rep(0, length(points))
+    i <- 0
+    for (point in points) {
+        i <- i + 1
+        rQuantileValues[i] <- qgamma(point, shape=alpha, scale=beta, log = FALSE)
+    }
+    output <- c("Quantile test shape = ", shape, " scale = ", scale)
+    if (assertEquals(expected, rQuantileValues, tol, "Quantile Values")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }    
+}
+
+#--------------------------------------------------------------------------
+cat("Gamma Distribution test cases\n")
+
+shape <- 4
+scale <- 2
+distributionValues <- c(0.001, 0.01, 0.025, 0.05, 0.1, 0.999, 0.990, 0.975, 0.950, 0.900)
+densityValues <- c(0.00427280075546, 0.0204117166709, 0.0362756163658, 0.0542113174239, 0.0773195272491,
+                   0.000394468852816, 0.00366559696761, 0.00874649473311, 0.0166712508128, 0.0311798227954)
+distributionPoints <- c(0.857104827257, 1.64649737269, 2.17973074725, 2.7326367935, 3.48953912565,
+                   26.1244815584, 20.0902350297, 17.5345461395, 15.5073130559, 13.3615661365)              
+verifyQuantiles(distributionValues, distributionPoints, shape, scale, tol)
+verifyDistribution(distributionPoints, distributionValues, shape, scale, tol)
+verifyDensity(distributionPoints, densityValues, shape, scale, tol)
+
+displayDashes(WIDTH)
diff --git a/src/test/R/README.txt b/src/test/R/README.txt
new file mode 100644
index 0000000..c6549bc
--- /dev/null
+++ b/src/test/R/README.txt
@@ -0,0 +1,168 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+#------------------------------------------------------------------------------
+
+INTRODUCTION
+
+The purpose of the R programs included in this directory is to validate 
+the target values used in Apache commons math unit tests. Success running the 
+R and commons-math tests on a platform (OS and R version) means that R and 
+commons-math give results for the test cases that are close in value.  The 
+tests include configurable tolerance levels; but care must be taken in changing 
+these, since in most cases the pre-set tolerance is close to the number of 
+decimal digits used in expressing the expected values (both here and in the 
+corresponding commons-math unit tests).
+
+Of course it is always possible that both R and commons-math give incorrect 
+values for test cases, so these tests should not be interpreted as definitive 
+in any absolute sense. The value of developing and running the tests is really  
+to generate questions (and answers!) when the two systems give different 
+results.
+
+Contributions of additional test cases (both R and Junit code) or just 
+R programs to validate commons-math tests that are not covered here would be 
+greatly appreciated.
+
+SETUP
+
+0) Download and install R.  You can get R here
+http://www.r-project.org/
+Follow the install instructions and make sure that you can launch R from this  
+(i.e., either explitly add R to your OS path or let the install package do it 
+for you).  
+
+1) Launch R from this directory and type 
+> source("testAll")
+to an R prompt.  This should produce output to the console similar to this:
+
+Binomial test cases
+Density test n = 10, p = 0.7...........................................SUCCEEDED
+Distribution test n = 10, p = 0.7......................................SUCCEEDED
+Inverse Distribution test n = 10, p = 0.7..............................SUCCEEDED
+Density test n = 5, p = 0..............................................SUCCEEDED
+Distribution test n = 5, p = 0.........................................SUCCEEDED
+Density test n = 5, p = 1..............................................SUCCEEDED
+Distribution test n = 5, p = 1.........................................SUCCEEDED
+--------------------------------------------------------------------------------
+Normal test cases
+Distribution test mu = 2.1, sigma = 1.4................................SUCCEEDED
+Distribution test mu = 2.1, sigma = 1.4................................SUCCEEDED
+Distribution test mu = 0, sigma = 1....................................SUCCEEDED
+Distribution test mu = 0, sigma = 0.1..................................SUCCEEDED
+--------------------------------------------------------------------------------
+...
+<more test reports>
+
+
+WORKING WITH THE TESTS
+
+The R distribution comes with online manuals that you can view by launching 
+a browser instance and then entering
+
+> help.start()
+
+at an R prompt. Poking about in the test case files and the online docs should 
+bring you up to speed fairly quickly.  Here are some basic things to get 
+you started. I should note at this point that I am by no means an expert R 
+programmer, so some things may not be implemented in the the nicest way. 
+Comments / suggestions for improvement are welcome!
+
+All of the test cases use some basic functions and global constants (screen
+width and success / failure strings) defined in "testFunctions." The  
+R "source" function is used to "import" these functions into each of the test 
+programs.  The "testAll" program pulls together and executes all of the 
+individual test programs.  You can execute any one of them by just entering
+
+> source(<program-name>).
+
+The "assertEquals" function in the testFunctions file mimics the similarly 
+named function used by Junit:
+
+assertEquals <- function(expected, observed, tol, message) {
+    if(any(abs(expected - observed) > tol)) {
+        cat("FAILURE: ",message,"\n")
+        cat("EXPECTED: ",expected,"\n")
+        cat("OBSERVED: ",observed,"\n")
+        return(0)
+    } else {
+        return(1)
+    }
+}
+
+The <expected> and <observed> arguments can be scalar values, vectors or 
+matrices. If the arguments are vectors or matrices, corresponding entries
+are compared.
+
+The standard pattern used throughout the tests looks like this (from 
+binomialTestCases):
+
+Start by defining a "verification function" -- in this example a function to
+verify computation of binomial probabilities. The <points> argument is a vector
+of integer values to feed into the density function, <expected> is a vector of
+the computed probabilies from the commons-math Junit tests, <n> and <p> are
+parameters of the distribution and <tol> is the error tolerance of the test.
+The function computes the probabilities using R and compares the values that
+R produces with those in the <expected> vector.
+
+verifyDensity <- function(points, expected, n, p, tol) {
+    rDensityValues <- rep(0, length(points))
+    i <- 0
+    for (point in points) {
+        i <- i + 1
+        rDensityValues[i] <- dbinom(point, n, p, log = FALSE)
+    }
+    output <- c("Density test n = ", n, ", p = ", p)
+    if (assertEquals(expected,rDensityValues,tol,"Density Values")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }       
+}
+
+The displayPadded function just displays its first and second arguments with
+enough dots in between to make the whole string WIDTH characters long. It is 
+defined in testFunctions.
+
+Then call this function with different parameters corresponding to the different
+Junit test cases:
+
+size <- 10.0
+probability <- 0.70
+
+densityPoints <- c(-1,0,1,2,3,4,5,6,7,8,9,10,11)
+densityValues <- c(0, 0.0000, 0.0001, 0.0014, 0.0090, 0.0368, 0.1029, 
+                0.2001, 0.2668, 0.2335, 0.1211, 0.0282, 0)
+...
+verifyDensity(densityPoints, densityValues, size, probability, tol)
+
+If the values computed by R match the target values in densityValues, this will
+produce one line of output to the console:
+
+Density test n = 10, p = 0.7...........................................SUCCEEDED
+
+If you modify the value of tol set at the top of binomialTestCases to make the 
+test more sensitive than the number of digits specified in the densityValues 
+vector, it will fail, producing the following output, showing the failure and
+the expected and observed values:
+
+FAILURE:  Density Values 
+EXPECTED:  0 0 1e-04 0.0014 0.009 0.0368 0.1029 0.2001 0.2668 0.2335 0.1211 /
+ 0.0282 0 
+OBSERVED:  0 5.9049e-06 0.000137781 0.0014467005 0.009001692 0.036756909 /
+0.1029193452 0.200120949 0.266827932 0.2334744405 0.121060821 0.0282475249 0 
+Density test n = 10, p = 0.7..............................................FAILED
+
+
diff --git a/src/test/R/TDistributionTestCases.R b/src/test/R/TDistributionTestCases.R
new file mode 100644
index 0000000..723e958
--- /dev/null
+++ b/src/test/R/TDistributionTestCases.R
@@ -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.
+#
+#------------------------------------------------------------------------------
+# R source file to validate T distribution tests in
+# org.apache.commons.math.distribution.TDistributionTest
+#
+# To run the test, install R, put this file and testFunctions
+# into the same directory, launch R from this directory and then enter
+# source("<name-of-this-file>")
+#
+#-----------------------------------------------------------------------------
+tol <- 1E-9
+
+# Function definitions
+source("testFunctions")           # utility test functions
+
+# function to verify distribution computations
+verifyDistribution <- function(points, expected, df, tol) {
+ rDistValues <- rep(0, length(points))
+    i <- 0
+    for (point in points) {
+        i <- i + 1
+        rDistValues[i] <- pt(point, df, log = FALSE)
+    }
+    output <- c("Distribution test df = ", df)
+    if (assertEquals(expected, rDistValues, tol, "Distribution Values")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }       
+}
+
+# function to verify density computations
+verifyDensity <- function(points, expected, df, tol) {
+ rDensityValues <- rep(0, length(points))
+    i <- 0
+    for (point in points) {
+        i <- i + 1
+        rDensityValues[i] <- dt(point, df, log = FALSE)
+    }
+    output <- c("Density test df = ", df)
+    if (assertEquals(expected, rDensityValues, tol, "Density Values")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }       
+}
+
+# function to verify quantiles
+verifyQuantiles <- function(points, expected, df, tol) {
+	rQuantileValues <- rep(0, length(points))
+    i <- 0
+    for (point in points) {
+        i <- i + 1
+        rQuantileValues[i] <- qt(point, df, log = FALSE)
+    }
+    output <- c("Quantile test df = ", df)
+    if (assertEquals(expected, rQuantileValues, tol, "Quantile Values")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }    
+}
+
+#--------------------------------------------------------------------------
+cat("T Distribution test cases\n")
+
+df <- 5
+distributionValues <- c(0.001, 0.01, 0.025, 0.05, 0.1, 0.999, 0.990, 0.975, 0.950, 0.900)
+densityValues <- c(0.000756494565517, 0.0109109752919, 0.0303377878006, 0.0637967988952, 0.128289492005,
+                0.000756494565517, 0.0109109752919, 0.0303377878006, 0.0637967988952, 0.128289492005)
+distributionPoints <- c(-5.89342953136, -3.36492999891, -2.57058183564, -2.01504837333, -1.47588404882,
+                5.89342953136, 3.36492999891, 2.57058183564, 2.01504837333, 1.47588404882)              
+verifyQuantiles(distributionValues, distributionPoints, df, tol)
+verifyDistribution(distributionPoints, distributionValues, df, tol)
+verifyDensity(distributionPoints, densityValues, df, tol)
+
+df <- 1
+densityValues <- c(3.14158231817e-06, 0.000314055924703, 0.00195946145194, 0.00778959736375, 0.0303958893917,
+                3.14158231817e-06, 0.000314055924703, 0.00195946145194, 0.00778959736375, 0.0303958893917)
+distributionPoints <- c(-318.308838986, -31.8205159538, -12.7062047362, -6.31375151468, -3.07768353718,
+                318.308838986, 31.8205159538, 12.7062047362, 6.31375151468, 3.07768353718)
+verifyQuantiles(distributionValues, distributionPoints, df, tol)
+verifyDistribution(distributionPoints, distributionValues, df, tol)
+verifyDensity(distributionPoints, densityValues, df, tol)
+
+displayDashes(WIDTH)
diff --git a/src/test/R/TTestCases b/src/test/R/TTestCases
new file mode 100644
index 0000000..6aa5e2d
--- /dev/null
+++ b/src/test/R/TTestCases
@@ -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.
+#
+#------------------------------------------------------------------------------
+# R source file to validate TTest tests in
+# org.apache.commons.math.inference.TTestImpl
+#
+# To run the test, install R, put this file and testFunctions
+# into the same directory, launch R from this directory and then enter
+# source("<name-of-this-file>")
+#
+# R functions used
+# t.test(x, y = NULL, alternative = c("two.sided", "less", "greater"),
+# mu = 0, paired = FALSE, var.equal = FALSE, ... )
+# Arguments
+#   x  a numeric vector of data values.
+#   y  an optional numeric vector data values.
+#   alternative 	a character string specifying the alternative hypothesis, 
+#     must be one of "two.sided" (default), "greater" or "less". You can specify
+#     just the initial letter.
+#   mu  a number indicating the true value of the mean (or difference in means
+#      if you are performing a two sample test).
+#   paired 	a logical indicating whether you want a paired t-test.
+#   var.equal 	a logical variable indicating whether to treat the two
+#     variances as being equal. 
+#     If TRUE then the pooled variance is used to estimate the variance,
+#     otherwise the Welch (or Satterthwaite) approximation to the degrees
+#     of freedom is used.
+#------------------------------------------------------------------------------
+tol <- 1E-10                       # error tolerance for tests
+#------------------------------------------------------------------------------
+# Function definitions
+#------------------------------------------------------------------------------
+source("testFunctions")           # utility test functions
+#------------------------------------------------------------------------------
+# Verification function
+#
+verifyTest <- function(out,expectedP, expectedT,
+  tol) {
+  if (assertEquals(expectedP, out$p.value, tol,
+     "Ttest p value")) {
+     displayPadded(output, SUCCEEDED, 80)
+  } else {
+     displayPadded(output, FAILED, 80)
+  }  
+  output <- c("t test test statistic") 
+  if (assertEquals(expectedT, out$statistic, tol,
+      "Ttest t statistic")) {
+      displayPadded(output, SUCCEEDED, 80)
+  } else {
+      displayPadded(output, FAILED, 80)
+  }          
+  displayDashes(WIDTH)
+}
+
+cat("One-sample, two-sided TTest test cases \n")
+sample1 <- c(93.0, 103.0, 95.0, 101.0, 91.0, 105.0, 96.0, 94.0, 101.0,  88.0,
+                      98.0, 94.0, 101.0, 92.0, 95.0)
+out <- t.test(sample1, mu=100.0)
+expectedP <-  0.0136390585873
+expectedT<- -2.81976445346
+verifyTest(out,expectedP, expectedT, tol) 
+
+cat("One-sample, one-sided TTest test cases \n")
+sample1 <- c(2, 0, 6, 6, 3, 3, 2, 3, -6, 6, 6, 6, 3, 0, 1, 1, 0, 2, 3, 3)
+out <- t.test(sample1, mu=0.0, alternative="g")
+expectedP <-  0.000521637019637
+expectedT<- 3.86485535541
+verifyTest(out,expectedP, expectedT, tol) 
+
+cat("Homoscedastic TTest test cases \n")
+sample1 <- c(2, 4, 6, 8, 10, 97)
+sample2 <- c(4, 6, 8, 10, 16)
+out <- t.test(sample1,sample2,var.equal = TRUE)
+expectedP <-  0.4833963785
+expectedT<- 0.73096310086
+verifyTest(out,expectedP, expectedT, tol)
+ 
+cat("Heteroscedastic TTest test cases \n")
+sample1 <- c(7, -4, 18, 17, -3, -5, 1, 10, 11, -2)
+sample2 <- c(-1, 12, -1, -3, 3, -5, 5, 2, -11, -1, -3) 
+out <- t.test(sample1,sample2,var.equal = FALSE)
+expectedP <-  0.128839369622
+expectedT<- 1.60371728768
+verifyTest(out,expectedP, expectedT, tol)
+
+cat("Small sample, heteroscedastic test cases \n")
+sample1 <- c(1,3)
+sample2 <- c(4,5)
+out <- t.test(sample1,sample2,var.equal = FALSE)
+expectedP <-  0.198727388935
+expectedT<- -2.2360679775
+verifyTest(out,expectedP, expectedT, tol)
+ 
diff --git a/src/test/R/WeibullDistributionTestCases.R b/src/test/R/WeibullDistributionTestCases.R
new file mode 100644
index 0000000..fadc625
--- /dev/null
+++ b/src/test/R/WeibullDistributionTestCases.R
@@ -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.
+#
+#------------------------------------------------------------------------------
+# R source file to validate Weibull distribution tests in
+# org.apache.commons.math.distribution.GammaDistributionTest
+#
+# To run the test, install R, put this file and testFunctions
+# into the same directory, launch R from this directory and then enter
+# source("<name-of-this-file>")
+#
+#-----------------------------------------------------------------------------
+tol <- 1E-9
+
+# Function definitions
+source("testFunctions")           # utility test functions
+
+# function to verify distribution computations
+verifyDistribution <- function(points, expected, alpha, beta, tol) {
+ rDistValues <- rep(0, length(points))
+    i <- 0
+    for (point in points) {
+        i <- i + 1
+        rDistValues[i] <- pweibull(point, shape=alpha, scale=beta, log = FALSE)
+    }
+    output <- c("Distribution test shape = ", shape, " scale = ", scale)
+    if (assertEquals(expected, rDistValues, tol, "Distribution Values")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }       
+}
+
+# function to verify density computations
+verifyDensity <- function(points, expected, alpha, beta, tol) {
+ rDensityValues <- rep(0, length(points))
+    i <- 0
+    for (point in points) {
+        i <- i + 1
+        rDensityValues[i] <- dweibull(point, shape=alpha, scale=beta, log = FALSE)
+    }
+    output <- c("Density test shape = ", shape, " scale = ", scale)
+    if (assertEquals(expected, rDensityValues, tol, "Density Values")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }       
+}
+
+# function to verify quantiles
+verifyQuantiles <- function(points, expected, alpha, beta, tol) {
+	rQuantileValues <- rep(0, length(points))
+    i <- 0
+    for (point in points) {
+        i <- i + 1
+        rQuantileValues[i] <- qweibull(point, shape=alpha, scale=beta, log = FALSE)
+    }
+    output <- c("Quantile test shape = ", shape, " scale = ", scale)
+    if (assertEquals(expected, rQuantileValues, tol, "Quantile Values")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }    
+}
+
+#--------------------------------------------------------------------------
+cat("Weibull Distribution test cases\n")
+
+shape <- 1.2
+scale <- 2.1
+distributionValues <- c(0.001, 0.01, 0.025, 0.05, 0.1, 0.999, 0.990, 0.975, 0.950, 0.900)
+densityValues <- c(0.180535929306, 0.262801138133, 0.301905425199, 0.330899152971, 0.353441418887, 0.000788590320203,
+                 0.00737060094841, 0.0177576041516, 0.0343043442574, 0.065664589369)
+distributionPoints <- c(0.00664355180993, 0.0454328283309, 0.0981162737374, 0.176713524579, 0.321946865392,
+                 10.5115496887, 7.4976304671, 6.23205600701, 5.23968436955, 4.20790282578)              
+verifyQuantiles(distributionValues, distributionPoints, shape, scale, tol)
+verifyDistribution(distributionPoints, distributionValues, shape, scale, tol)
+verifyDensity(distributionPoints, densityValues, shape, scale, tol)
+
+displayDashes(WIDTH)
diff --git a/src/test/R/anovaTestCases b/src/test/R/anovaTestCases
new file mode 100644
index 0000000..077ba09
--- /dev/null
+++ b/src/test/R/anovaTestCases
@@ -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.
+#
+#------------------------------------------------------------------------------
+# R source file to validate Binomial distribution tests in
+# org.apache.commons.math.distribution.BinomialDistributionTest
+#
+# To run the test, install R, put this file and testFunctions
+# into the same directory, launch R from this directory and then enter
+# source("<name-of-this-file>")
+#
+# R functions used
+# anova(model) <- anova
+# lm(frame) <- linear model
+#------------------------------------------------------------------------------
+tol <- 1E-12                       # error tolerance for tests
+#------------------------------------------------------------------------------
+# Function definitions
+
+source("testFunctions")           # utility test functions
+options(digits=16)				  # override number of digits displayed
+
+# function to verify anova computations
+
+verifyAnova <- function(frame, expectedP, expectedF, frameName) {
+    a <- anova(lm(frame))
+    p <- a$"Pr(>F)"[1]
+    f <- a$"F value"[1]
+    output <- c("P-value test frame = ", frameName)
+    if (assertEquals(expectedP,p,tol,"P value")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }  
+    output <- c("F-value test frame = ", frameName)
+    if (assertEquals(expectedF,f,tol,"F value")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }           
+}
+
+#--------------------------------------------------------------------------
+cat("Anova test cases\n")
+classA <- c(93.0, 103.0, 95.0, 101.0, 91.0, 105.0, 96.0, 94.0, 101.0)
+classB <- c(99.0, 92.0, 102.0, 100.0, 102.0, 89.0)
+classC <- c(110.0, 115.0, 111.0, 117.0, 128.0, 117.0)
+
+threeClasses = data.frame(val = c(classA, classB, classC),
+class=c(rep("classA", length(classA)),
+        rep("classB", length(classB)),
+        rep("classC", length(classC))))
+
+verifyAnova(threeClasses,6.959446e-06,  24.67361709460624, "Three classes") 
+
+twoClasses = data.frame(val = c(classA, classB),
+class=c(rep("classA", length(classA)), rep("classB", length(classB))))
+verifyAnova(twoClasses, 0.904212960464, 0.0150579150579, "Two classes")
+
+displayDashes(WIDTH)
\ No newline at end of file
diff --git a/src/test/R/binomialTestCases b/src/test/R/binomialTestCases
new file mode 100644
index 0000000..6b1871c
--- /dev/null
+++ b/src/test/R/binomialTestCases
@@ -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.
+#
+#------------------------------------------------------------------------------
+# R source file to validate Binomial distribution tests in
+# org.apache.commons.math.distribution.BinomialDistributionTest
+#
+# To run the test, install R, put this file and testFunctions
+# into the same directory, launch R from this directory and then enter
+# source("<name-of-this-file>")
+#
+# R functions used
+# dbinom(x, size, prob, log = FALSE) <- density
+# pbinom(q, size, prob, lower.tail = TRUE, log.p = FALSE) <- distribution
+# qbinom(p, size, prob, lower.tail = TRUE, log.p = FALSE) <- quantiles
+#------------------------------------------------------------------------------
+tol <- 1E-4                       # error tolerance for tests
+#------------------------------------------------------------------------------
+# Function definitions
+
+source("testFunctions")           # utility test functions
+
+# function to verify density computations
+
+verifyDensity <- function(points, expected, n, p, tol) {
+    rDensityValues <- rep(0, length(points))
+    i <- 0
+    for (point in points) {
+        i <- i + 1
+        rDensityValues[i] <- dbinom(point, n, p, log = FALSE)
+    }
+    output <- c("Density test n = ", n, ", p = ", p)
+    if (assertEquals(expected,rDensityValues,tol,"Density Values")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }       
+}
+
+# function to verify distribution computations
+
+verifyDistribution <- function(points, expected, n, p, tol) {
+    rDistValues <- rep(0, length(points))
+    i <- 0
+    for (point in points) {
+        i <- i + 1
+        rDistValues[i] <- pbinom(point, n, p, log = FALSE)
+    }
+    output <- c("Distribution test n = ", n, ", p = ", p)
+    if (assertEquals(expected,rDistValues,tol,"Distribution Values")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }       
+}
+
+#--------------------------------------------------------------------------
+cat("Binomial test cases\n")
+
+size <- 10.0
+probability <- 0.70
+
+densityPoints <- c(-1,0,1,2,3,4,5,6,7,8,9,10,11)
+densityValues <- c(0, 0.0000, 0.0001, 0.0014, 0.0090, 0.0368, 0.1029, 
+                0.2001, 0.2668, 0.2335, 0.1211, 0.0282, 0)
+distributionValues <- c(0, 0.0000, 0.0001, 0.0016, 0.0106, 0.0473,
+                0.1503, 0.3504, 0.6172, 0.8507, 0.9718, 1, 1)
+inverseCumPoints <- c( 0.001, 0.010, 0.025, 0.050, 0.100, 0.999,
+                0.990, 0.975, 0.950, 0.900)
+inverseCumValues <- c(1, 2, 3, 4, 4, 9, 9, 9, 8, 8)
+
+verifyDensity(densityPoints,densityValues,size,probability,tol)
+verifyDistribution(densityPoints, distributionValues, size, probability, tol)
+
+i <- 0
+rInverseCumValues <- rep(0,length(inverseCumPoints))
+for (point in inverseCumPoints) {
+  i <- i + 1
+  rInverseCumValues[i] <- qbinom(point, size, probability, log = FALSE)
+}
+
+output <- c("Inverse Distribution test n = ", size, ", p = ", probability)
+# R defines quantiles from the right, need to subtract one
+if (assertEquals(inverseCumValues, rInverseCumValues-1, tol,
+    "Inverse Dist Values")) {
+    displayPadded(output, SUCCEEDED, 80)
+} else {
+    displayPadded(output, FAILED, 80)
+}       
+
+# Degenerate cases
+
+size <- 5
+probability <- 0.0
+
+densityPoints <- c(-1, 0, 1, 10, 11)
+densityValues <- c(0, 1, 0, 0, 0)
+distributionPoints <- c(-1, 0, 1, 5, 10)
+distributionValues <- c(0, 1, 1, 1, 1)
+
+verifyDensity(densityPoints,densityValues,size,probability,tol)
+verifyDistribution(distributionPoints,distributionValues,size,probability,tol)
+
+size <- 5
+probability <- 1.0
+
+densityPoints <- c(-1, 0, 1, 2, 5, 10)
+densityValues <- c(0, 0, 0, 0, 1, 0)
+distributionPoints <- c(-1, 0, 1, 2, 5, 10)
+distributionValues <- c(0, 0, 0, 0, 1, 1)
+
+verifyDensity(densityPoints,densityValues,size,probability,tol)
+verifyDistribution(distributionPoints,distributionValues,size,probability,tol)
+
+displayDashes(WIDTH)
diff --git a/src/test/R/cauchyTestCases.R b/src/test/R/cauchyTestCases.R
new file mode 100644
index 0000000..b046ca5
--- /dev/null
+++ b/src/test/R/cauchyTestCases.R
@@ -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.
+#
+#------------------------------------------------------------------------------
+# R source file to validate Cauchy distribution tests in
+# org.apache.commons.math.distribution.CauchyDistributionTest
+#
+# To run the test, install R, put this file and testFunctions
+# into the same directory, launch R from this directory and then enter
+# source("<name-of-this-file>")
+#
+#-----------------------------------------------------------------------------
+tol <- 1E-9
+
+# Function definitions
+
+source("testFunctions")           # utility test functions
+
+# function to verify distribution computations
+
+verifyDistribution <- function(points, expected, median, scale, tol) {
+ rDistValues <- rep(0, length(points))
+    i <- 0
+    for (point in points) {
+        i <- i + 1
+        rDistValues[i] <- pcauchy(point, median, scale, log = FALSE)
+    }
+    output <- c("Distribution test median = ",median,", scale = ", scale)
+    if (assertEquals(expected, rDistValues, tol, "Distribution Values")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }       
+}
+
+# function to verify density computations
+
+verifyDensity <- function(points, expected, median, scale, tol) {
+ rDensityValues <- rep(0, length(points))
+    i <- 0
+    for (point in points) {
+        i <- i + 1
+        rDensityValues[i] <- dcauchy(point, median, scale, log = FALSE)
+    }
+    output <- c("Density test median = ",median,", scale = ", scale)
+    if (assertEquals(expected, rDensityValues, tol, "Density Values")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }       
+}
+
+# function to verify quantiles
+
+verifyQuantiles <- function(points, expected, median, scale, tol) {
+	rQuantileValues <- rep(0, length(points))
+    i <- 0
+    for (point in points) {
+        i <- i + 1
+        rQuantileValues[i] <- qcauchy(point, median, scale, log = FALSE)
+    }
+    output <- c("Quantile test median = ",median,", scale = ", scale)
+    if (assertEquals(expected, rQuantileValues, tol, "Quantile Values")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }    
+}
+
+#--------------------------------------------------------------------------
+cat("Cauchy test cases\n")
+
+median <- 1.2
+scale <- 2.1
+distributionValues <- c(0.001, 0.01, 0.025, 0.05, 0.1, 0.999,
+                0.990, 0.975, 0.950, 0.900)
+densityValues <- c(1.49599158008e-06, 0.000149550440335, 0.000933076881878, 0.00370933207799, 0.0144742330437,
+                1.49599158008e-06, 0.000149550440335, 0.000933076881878, 0.00370933207799, 0.0144742330437)
+distributionPoints <- c(-667.24856187, -65.6230835029, -25.4830299460, -12.0588781808, -5.26313542807,
+                669.64856187, 68.0230835029, 27.8830299460, 14.4588781808, 7.66313542807)
+verifyDistribution(distributionPoints, distributionValues, median, scale, tol)
+verifyDensity(distributionPoints, densityValues, median, scale, tol)
+verifyQuantiles(distributionValues, distributionPoints, median, scale, tol)
+
+displayDashes(WIDTH)
diff --git a/src/test/R/chiSquareTestCases b/src/test/R/chiSquareTestCases
new file mode 100644
index 0000000..747b5e8
--- /dev/null
+++ b/src/test/R/chiSquareTestCases
@@ -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.
+#
+#------------------------------------------------------------------------------
+# R source file to validate ChiSquare tests in
+# org.apache.commons.math.stat.inference.ChiSquareTestTest
+#
+# To run the test, install R, put this file and testFunctions
+# into the same directory, launch R from this directory and then enter
+# source("<name-of-this-file>")
+#
+# R functions used
+#chisq.test(x, y = NULL, correct = TRUE,
+#           p = rep(1/length(x), length(x)),
+#           simulate.p.value = FALSE, B = 2000)
+#------------------------------------------------------------------------------
+tol <- 1E-9                     # error tolerance for tests
+#------------------------------------------------------------------------------
+# Function definitions
+
+source("testFunctions")           # utility test functions
+
+verifyTable <- function(counts, expectedP, expectedStat, tol, desc) {
+    results <- chisq.test(counts)
+    if (assertEquals(expectedP, results$p.value, tol, "p-value")) {
+        displayPadded(c(desc," p-value test"), SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(c(desc, " p-value test"), FAILED, WIDTH)
+    } 
+    if (assertEquals(expectedStat, results$statistic, tol,
+       "ChiSquare Statistic")) {
+        displayPadded(c(desc, " chi-square statistic test"), SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(c(desc, " chi-square statistic test"), FAILED, WIDTH)
+    } 
+}
+
+verifyHomogeneity <- function(obs, exp, expectedP, expectedStat, 
+  tol, desc) {
+    results <- chisq.test(obs,p=exp,rescale.p=TRUE)
+    chi <- results$statistic
+    p <- results$p.value
+    if (assertEquals(expectedP, p, tol, "p-value")) {
+        displayPadded(c(desc, " p-value test"), SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(c(desc, " p-value test"), FAILED, WIDTH)
+    } 
+    if (assertEquals(expectedStat, chi, tol,
+       "ChiSquare Statistic")) {
+        displayPadded(c(desc, " chi-square statistic test"), SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(c(desc, " chi-square statistic test"), FAILED, WIDTH)
+    }    
+}
+
+cat("ChiSquareTest test cases\n")
+
+observed <- c(10, 9, 11)
+expected <- c(10, 10, 10)
+verifyHomogeneity(observed, expected, 0.904837418036, 0.2, tol,
+   "testChiSquare1")
+
+observed <- c(500, 623, 72, 70, 31)
+expected <- c(485, 541, 82, 61, 37)
+verifyHomogeneity(observed, expected, 0.06051952647453607, 9.023307936427388,
+   tol, "testChiSquare2")
+
+observed <- c(2372383, 584222, 257170, 17750155, 7903832, 489265,
+              209628, 393899)
+expected <- c(3389119.5, 649136.6, 285745.4, 25357364.76, 11291189.78,
+              543628.0, 232921.0, 437665.75)
+verifyHomogeneity(observed, expected, 0, 114875.90421929007, tol,
+   "testChiSquareLargeTestStatistic")
+
+counts <- matrix(c(40, 22, 43, 91, 21, 28, 60, 10, 22), nc = 3);
+verifyTable(counts, 0.000144751460134, 22.709027688, tol, 
+   "testChiSquareIndependence1")
+
+counts <- matrix(c(10, 15, 30, 40, 60, 90), nc = 3);
+verifyTable(counts, 0.918987499852, 0.168965517241, tol,  
+   "testChiSquareIndependence2")
+ 
+counts <- matrix(c(40, 0, 4, 91, 1, 2, 60, 2, 0), nc = 3);
+verifyTable(counts, 0.0462835770603, 9.67444662263, tol,  
+  "testChiSquareZeroCount")
+
+displayDashes(WIDTH)
+            
+
diff --git a/src/test/R/correlationTestCases b/src/test/R/correlationTestCases
new file mode 100644
index 0000000..2e3b767
--- /dev/null
+++ b/src/test/R/correlationTestCases
@@ -0,0 +1,225 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+#------------------------------------------------------------------------------
+# R source file to validate Pearson's correlation tests in
+# org.apache.commons.math.stat.correlation.PearsonsCorrelationTest
+#
+# To run the test, install R, put this file and testFunctions
+# into the same directory, launch R from this directory and then enter
+# source("<name-of-this-file>")
+#
+#------------------------------------------------------------------------------
+tol <- 1E-15                      # error tolerance for tests
+#------------------------------------------------------------------------------
+# Function definitions
+
+source("testFunctions")           # utility test functions
+options(digits=16)	              # override number of digits displayed
+
+# Verify Pearson's correlation
+verifyPearsonsCorrelation <- function(matrix, expectedCorrelation, name) {
+    correlation <- cor(matrix)
+    output <- c("Pearson's Correlation matrix test dataset = ", name)
+    if (assertEquals(expectedCorrelation, correlation,tol,"Pearson's Correlations")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }  
+}
+
+# Verify Spearman's correlation
+verifySpearmansCorrelation <- function(matrix, expectedCorrelation, name) {
+    correlation <- cor(matrix, method="spearman")
+    output <- c("Spearman's Correlation matrix test dataset = ", name)
+    if (assertEquals(expectedCorrelation, correlation,tol,"Spearman's Correlations")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }  
+}
+
+# function to verify p-values
+verifyPValues <- function(matrix, pValues, name) {
+	dimension <- dim(matrix)[2]
+	corValues <- matrix(nrow=dimension,ncol=dimension)
+	expectedValues <- matrix(nrow=dimension,ncol=dimension)
+	for (i in 2:dimension) {
+		for (j in 1:(i-1)) {
+			corValues[i,j]<-cor.test(matrix[,i], matrix[,j])$p.value
+			corValues[j,i]<-corValues[i,j]
+		}
+	}
+	for (i in 1:dimension) {
+		corValues[i,i] <- 1
+		expectedValues[i,i] <- 1
+	}
+	ptr <- 1
+	for (i in 2:dimension) {
+		for (j in 1:(i-1)) {
+			expectedValues[i,j] <- pValues[ptr]
+			expectedValues[j,i] <- expectedValues[i,j]
+			ptr <- ptr + 1
+		}
+    }
+    output <- c("Correlation p-values test dataset = ", name)
+    if (assertEquals(expectedValues, corValues,tol,"p-values")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }
+ }  	
+
+#--------------------------------------------------------------------------
+cat("Correlation test cases\n")
+
+# Longley -----------------------------------------------------------------
+longley <- matrix(c(60323,83.0,234289,2356,1590,107608,1947,
+                    61122,88.5,259426,2325,1456,108632,1948,
+                    60171,88.2,258054,3682,1616,109773,1949,
+                    61187,89.5,284599,3351,1650,110929,1950,
+                    63221,96.2,328975,2099,3099,112075,1951,
+                    63639,98.1,346999,1932,3594,113270,1952,
+                    64989,99.0,365385,1870,3547,115094,1953,
+                    63761,100.0,363112,3578,3350,116219,1954,
+                    66019,101.2,397469,2904,3048,117388,1955,
+                    67857,104.6,419180,2822,2857,118734,1956,
+                    68169,108.4,442769,2936,2798,120445,1957,
+                    66513,110.8,444546,4681,2637,121950,1958,
+                    68655,112.6,482704,3813,2552,123366,1959,
+                    69564,114.2,502601,3931,2514,125368,1960,
+                    69331,115.7,518173,4806,2572,127852,1961,
+                    70551,116.9,554894,4007,2827,130081,1962),
+                    nrow = 16, ncol = 7, byrow = TRUE)
+
+# Pearson's
+expectedCorrelation <- matrix(c(
+         1.000000000000000, 0.9708985250610560, 0.9835516111796693, 0.5024980838759942,
+         0.4573073999764817, 0.960390571594376, 0.9713294591921188,
+         0.970898525061056, 1.0000000000000000, 0.9915891780247822, 0.6206333925590966,
+         0.4647441876006747, 0.979163432977498, 0.9911491900672053,
+         0.983551611179669, 0.9915891780247822, 1.0000000000000000, 0.6042609398895580,
+         0.4464367918926265, 0.991090069458478, 0.9952734837647849,
+         0.502498083875994, 0.6206333925590966, 0.6042609398895580, 1.0000000000000000,
+         -0.1774206295018783, 0.686551516365312, 0.6682566045621746,
+          0.457307399976482, 0.4647441876006747, 0.4464367918926265, -0.1774206295018783,
+          1.0000000000000000, 0.364416267189032, 0.4172451498349454,
+          0.960390571594376, 0.9791634329774981, 0.9910900694584777, 0.6865515163653120,
+          0.3644162671890320, 1.000000000000000, 0.9939528462329257,
+          0.971329459192119, 0.9911491900672053, 0.9952734837647849, 0.6682566045621746,
+          0.4172451498349454, 0.993952846232926, 1.0000000000000000),
+          nrow = 7, ncol = 7, byrow = TRUE)
+ verifyPearsonsCorrelation(longley, expectedCorrelation, "longley")
+ 
+ expectedPValues <- c(
+          4.38904690369668e-10,
+          8.36353208910623e-12, 7.8159700933611e-14,
+          0.0472894097790304, 0.01030636128354301, 0.01316878049026582, 
+          0.0749178049642416, 0.06971758330341182, 0.0830166169296545, 0.510948586323452,
+          3.693245043123738e-09, 4.327782576751815e-11, 1.167954621905665e-13, 0.00331028281967516, 0.1652293725106684, 
+          3.95834476307755e-10, 1.114663916723657e-13, 1.332267629550188e-15, 0.00466039138541463, 0.1078477071581498, 7.771561172376096e-15)
+ verifyPValues(longley, expectedPValues, "longley")
+ 
+ # Spearman's
+expectedCorrelation <- matrix(c(
+          1, 0.982352941176471, 0.985294117647059, 0.564705882352941, 0.2264705882352941, 0.976470588235294,
+          0.976470588235294, 0.982352941176471, 1, 0.997058823529412, 0.664705882352941, 0.2205882352941176,
+          0.997058823529412, 0.997058823529412, 0.985294117647059, 0.997058823529412, 1, 0.638235294117647,
+          0.2235294117647059, 0.9941176470588236, 0.9941176470588236, 0.564705882352941, 0.664705882352941,
+          0.638235294117647, 1, -0.3411764705882353, 0.685294117647059, 0.685294117647059, 0.2264705882352941,
+          0.2205882352941176, 0.2235294117647059, -0.3411764705882353, 1, 0.2264705882352941, 0.2264705882352941,
+          0.976470588235294, 0.997058823529412, 0.9941176470588236, 0.685294117647059, 0.2264705882352941, 1, 1,
+          0.976470588235294, 0.997058823529412, 0.9941176470588236, 0.685294117647059, 0.2264705882352941, 1, 1),
+          nrow = 7, ncol = 7, byrow = TRUE)
+ verifySpearmansCorrelation(longley, expectedCorrelation, "longley")
+  
+ # Swiss Fertility ---------------------------------------------------------
+ fertility <- matrix(c(80.2,17.0,15,12,9.96,
+  83.1,45.1,6,9,84.84,
+  92.5,39.7,5,5,93.40,
+  85.8,36.5,12,7,33.77,
+  76.9,43.5,17,15,5.16,
+  76.1,35.3,9,7,90.57,
+  83.8,70.2,16,7,92.85,
+  92.4,67.8,14,8,97.16,
+  82.4,53.3,12,7,97.67,
+  82.9,45.2,16,13,91.38,
+  87.1,64.5,14,6,98.61,
+  64.1,62.0,21,12,8.52,
+  66.9,67.5,14,7,2.27,
+  68.9,60.7,19,12,4.43,
+  61.7,69.3,22,5,2.82,
+  68.3,72.6,18,2,24.20,
+  71.7,34.0,17,8,3.30,
+  55.7,19.4,26,28,12.11,
+  54.3,15.2,31,20,2.15,
+  65.1,73.0,19,9,2.84,
+  65.5,59.8,22,10,5.23,
+  65.0,55.1,14,3,4.52,
+  56.6,50.9,22,12,15.14,
+  57.4,54.1,20,6,4.20,
+  72.5,71.2,12,1,2.40,
+  74.2,58.1,14,8,5.23,
+  72.0,63.5,6,3,2.56,
+  60.5,60.8,16,10,7.72,
+  58.3,26.8,25,19,18.46,
+  65.4,49.5,15,8,6.10,
+  75.5,85.9,3,2,99.71,
+  69.3,84.9,7,6,99.68,
+  77.3,89.7,5,2,100.00,
+  70.5,78.2,12,6,98.96,
+  79.4,64.9,7,3,98.22,
+  65.0,75.9,9,9,99.06,
+  92.2,84.6,3,3,99.46,
+  79.3,63.1,13,13,96.83,
+  70.4,38.4,26,12,5.62,
+  65.7,7.7,29,11,13.79,
+  72.7,16.7,22,13,11.22,
+  64.4,17.6,35,32,16.92,
+  77.6,37.6,15,7,4.97,
+  67.6,18.7,25,7,8.65,
+  35.0,1.2,37,53,42.34,
+  44.7,46.6,16,29,50.43,
+  42.8,27.7,22,29,58.33),
+  nrow = 47, ncol = 5, byrow = TRUE)
+  
+# Pearson's
+  expectedCorrelation <- matrix(c(
+          1, 0.3530791836199747, -0.6458827064572875, -0.663788857035069, 0.463684700651794,
+          0.3530791836199747, 1, -0.6865422086171366, -0.63952251894832, 0.4010950530487398,
+         -0.6458827064572875, -0.6865422086171366, 1, 0.698415296288483, -0.572741806064167,
+         -0.663788857035069, -0.63952251894832, 0.698415296288483, 1, -0.1538589170909148,
+          0.463684700651794, 0.4010950530487398, -0.572741806064167, -0.1538589170909148, 1),
+          nrow = 5, ncol = 5, byrow = TRUE)
+verifyPearsonsCorrelation(fertility, expectedCorrelation, "swiss fertility")
+
+expectedPValues <- c(
+          0.01491720061472623,
+          9.45043734069043e-07, 9.95151527133974e-08,
+          3.658616965962355e-07, 1.304590105694471e-06, 4.811397236181847e-08,
+          0.001028523190118147, 0.005204433539191644, 2.588307925380906e-05, 0.301807756132683)
+verifyPValues(fertility, expectedPValues, "swiss fertility")
+
+# Spearman's
+expectedCorrelation <- matrix(c(
+           1, 0.2426642769364176, -0.660902996352354, -0.443257690360988, 0.4136455623012432,
+           0.2426642769364176, 1, -0.598859938748963, -0.650463814145816, 0.2886878090882852,
+          -0.660902996352354, -0.598859938748963, 1, 0.674603831406147, -0.4750575257171745,
+          -0.443257690360988, -0.650463814145816, 0.674603831406147, 1, -0.1444163088302244,
+           0.4136455623012432, 0.2886878090882852, -0.4750575257171745, -0.1444163088302244, 1),
+          nrow = 5, ncol = 5, byrow = TRUE)
+ verifySpearmansCorrelation(fertility, expectedCorrelation, "swiss fertility")
+
+displayDashes(WIDTH)
diff --git a/src/test/R/covarianceTestCases b/src/test/R/covarianceTestCases
new file mode 100644
index 0000000..3d8d603
--- /dev/null
+++ b/src/test/R/covarianceTestCases
@@ -0,0 +1,146 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+#------------------------------------------------------------------------------
+# R source file to validate covariance tests in
+# org.apache.commons.math.stat.correlation.CovarianceTest
+#
+# To run the test, install R, put this file and testFunctions
+# into the same directory, launch R from this directory and then enter
+# source("<name-of-this-file>")
+#
+#------------------------------------------------------------------------------
+tol <- 1E-9                       # error tolerance for tests
+#------------------------------------------------------------------------------
+# Function definitions
+
+source("testFunctions")           # utility test functions
+options(digits=16)	              # override number of digits displayed
+
+# function to verify covariance computations
+verifyCovariance <- function(matrix, expectedCovariance, name) {
+    covariance <- cov(matrix)
+    output <- c("Covariance test dataset = ", name)
+    if (assertEquals(expectedCovariance,covariance,tol,"Covariances")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }  
+}
+
+#--------------------------------------------------------------------------
+cat("Covariance test cases\n")
+
+# Longley
+
+longley <- matrix(c(60323,83.0,234289,2356,1590,107608,1947,
+                    61122,88.5,259426,2325,1456,108632,1948,
+                    60171,88.2,258054,3682,1616,109773,1949,
+                    61187,89.5,284599,3351,1650,110929,1950,
+                    63221,96.2,328975,2099,3099,112075,1951,
+                    63639,98.1,346999,1932,3594,113270,1952,
+                    64989,99.0,365385,1870,3547,115094,1953,
+                    63761,100.0,363112,3578,3350,116219,1954,
+                    66019,101.2,397469,2904,3048,117388,1955,
+                    67857,104.6,419180,2822,2857,118734,1956,
+                    68169,108.4,442769,2936,2798,120445,1957,
+                    66513,110.8,444546,4681,2637,121950,1958,
+                    68655,112.6,482704,3813,2552,123366,1959,
+                    69564,114.2,502601,3931,2514,125368,1960,
+                    69331,115.7,518173,4806,2572,127852,1961,
+                    70551,116.9,554894,4007,2827,130081,1962),
+                    nrow = 16, ncol = 7, byrow = TRUE)
+
+expectedCovariance <- matrix(c(
+         12333921.73333333246, 3.679666000000000e+04, 343330206.333333313,
+         1649102.666666666744, 1117681.066666666651, 23461965.733333334, 16240.93333333333248,
+         36796.66000000000, 1.164576250000000e+02, 1063604.115416667,
+         6258.666250000000, 3490.253750000000, 73503.000000000, 50.92333333333334,
+         343330206.33333331347, 1.063604115416667e+06, 9879353659.329166412,
+         56124369.854166664183, 30880428.345833335072, 685240944.600000024, 470977.90000000002328,
+         1649102.66666666674, 6.258666250000000e+03, 56124369.854166664,
+         873223.429166666698, -115378.762499999997, 4462741.533333333, 2973.03333333333330,
+         1117681.06666666665, 3.490253750000000e+03, 30880428.345833335,
+         -115378.762499999997, 484304.095833333326, 1764098.133333333, 1382.43333333333339,
+         23461965.73333333433, 7.350300000000000e+04, 685240944.600000024,
+         4462741.533333333209, 1764098.133333333302, 48387348.933333330, 32917.40000000000146,
+         16240.93333333333, 5.092333333333334e+01, 470977.900000000,
+         2973.033333333333, 1382.433333333333, 32917.40000000, 22.66666666666667),
+         nrow = 7, ncol = 7, byrow = TRUE)
+         
+ verifyCovariance(longley, expectedCovariance, "longley")
+ 
+ # Swiss Fertility
+ 
+ fertility <- matrix(c(80.2,17.0,15,12,9.96,
+  83.1,45.1,6,9,84.84,
+  92.5,39.7,5,5,93.40,
+  85.8,36.5,12,7,33.77,
+  76.9,43.5,17,15,5.16,
+  76.1,35.3,9,7,90.57,
+  83.8,70.2,16,7,92.85,
+  92.4,67.8,14,8,97.16,
+  82.4,53.3,12,7,97.67,
+  82.9,45.2,16,13,91.38,
+  87.1,64.5,14,6,98.61,
+  64.1,62.0,21,12,8.52,
+  66.9,67.5,14,7,2.27,
+  68.9,60.7,19,12,4.43,
+  61.7,69.3,22,5,2.82,
+  68.3,72.6,18,2,24.20,
+  71.7,34.0,17,8,3.30,
+  55.7,19.4,26,28,12.11,
+  54.3,15.2,31,20,2.15,
+  65.1,73.0,19,9,2.84,
+  65.5,59.8,22,10,5.23,
+  65.0,55.1,14,3,4.52,
+  56.6,50.9,22,12,15.14,
+  57.4,54.1,20,6,4.20,
+  72.5,71.2,12,1,2.40,
+  74.2,58.1,14,8,5.23,
+  72.0,63.5,6,3,2.56,
+  60.5,60.8,16,10,7.72,
+  58.3,26.8,25,19,18.46,
+  65.4,49.5,15,8,6.10,
+  75.5,85.9,3,2,99.71,
+  69.3,84.9,7,6,99.68,
+  77.3,89.7,5,2,100.00,
+  70.5,78.2,12,6,98.96,
+  79.4,64.9,7,3,98.22,
+  65.0,75.9,9,9,99.06,
+  92.2,84.6,3,3,99.46,
+  79.3,63.1,13,13,96.83,
+  70.4,38.4,26,12,5.62,
+  65.7,7.7,29,11,13.79,
+  72.7,16.7,22,13,11.22,
+  64.4,17.6,35,32,16.92,
+  77.6,37.6,15,7,4.97,
+  67.6,18.7,25,7,8.65,
+  35.0,1.2,37,53,42.34,
+  44.7,46.6,16,29,50.43,
+  42.8,27.7,22,29,58.33),
+  nrow = 47, ncol = 5, byrow = TRUE)
+   
+ expectedCovariance <- matrix(c(
+         156.0424976873265, 100.1691489361702, -64.36692876965772, -79.7295097132285, 241.5632030527289,
+         100.169148936170251, 515.7994172062905, -124.39283071230344, -139.6574005550416, 379.9043755781684,
+         -64.3669287696577, -124.3928307123034, 63.64662349676226, 53.5758556891767, -190.5606105457909,
+         -79.7295097132285, -139.6574005550416, 53.57585568917669, 92.4560592044403, -61.6988297872340,
+          241.5632030527289, 379.9043755781684, -190.56061054579092, -61.6988297872340, 1739.2945371877890),
+          nrow = 5, ncol = 5, byrow = TRUE)
+      
+ verifyCovariance(fertility, expectedCovariance, "swiss fertility")
+ 
+ displayDashes(WIDTH)
\ No newline at end of file
diff --git a/src/test/R/descriptiveTestCases b/src/test/R/descriptiveTestCases
new file mode 100644
index 0000000..c724567
--- /dev/null
+++ b/src/test/R/descriptiveTestCases
@@ -0,0 +1,83 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+#------------------------------------------------------------------------------
+# R source file to validate summary statistics tests in
+# org.apache.commons.math.stat.CertifiedDataTest
+#
+# To run the test, install R, put this file and testFunctions
+# into the same directory, launch R from this directory and then enter
+# source("<name-of-this-file>")
+#
+# R functions used
+#  mean(x)
+#  sd(x)
+#------------------------------------------------------------------------------
+tol <- 1E-14                     # error tolerance for tests
+#------------------------------------------------------------------------------
+# Function definitions
+
+source("testFunctions")           # utility test functions
+options(digits=15)                # bump displayed digits to 15
+
+verifyMean <- function(values, expectedMean, tol, desc) {
+    results <- mean(values)
+    if (assertEquals(expectedMean, results, tol, "mean")) {
+        displayPadded(c(desc," mean test"), SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(c(desc, " mean test"), FAILED, WIDTH)
+    } 
+}
+
+verifySigma <- function(values, expectedSigma, tol, desc) {
+    results <- sd(values)
+    if (assertEquals(expectedSigma, results, tol, "std")) {
+        displayPadded(c(desc," std test"), SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(c(desc, " std test"), FAILED, WIDTH)
+    } 
+}
+
+cat("Descriptive test cases\n")
+
+# Michelson data
+values <- c(299.85,299.74,299.90,300.07,299.93,299.85,299.95,299.98,299.98,
+299.88,300.00,299.98,299.93,299.65,299.76,299.81,300.00,300.00,299.96,299.96,
+299.96,299.94,299.96,299.94,299.88,299.80,299.85,299.88,299.90,299.84,299.83,
+299.79,299.81,299.88,299.88,299.83,299.80,299.79,299.76,299.80,299.88,299.88,
+299.88,299.86,299.72,299.72,299.62,299.86,299.97,299.95,299.88,299.91,299.85,
+299.87,299.84,299.84,299.85,299.84,299.84,299.84,299.89,299.81,299.81,299.82,
+299.80,299.77,299.76,299.74,299.75,299.76,299.91,299.92,299.89,299.86,299.88,
+299.72,299.84,299.85,299.85,299.78,299.89,299.84,299.78,299.81,299.76,299.81,
+299.79,299.81,299.82,299.85,299.87,299.87,299.81,299.74,299.81,299.94,299.95,
+299.80,299.81,299.87)
+expectedMean <- 299.852400000000
+expectedSigma <- 0.0790105478190518
+verifyMean(values, expectedMean, tol, "Michelson")
+verifySigma(values, expectedSigma, tol, "Michelson")
+
+# Mavro data
+values <- c(2.00180,2.00170,2.00180,2.00190,2.00180,2.00170,2.00150,2.00140,
+2.00150,2.00150,2.00170,2.00180,2.00180,2.00190,2.00190,2.00210,2.00200,
+2.00160,2.00140,2.00130,2.00130,2.00150,2.00150,2.00160,2.00150,2.00140,
+2.00130,2.00140,2.00150,2.00140,2.00150,2.00160,2.00150,2.00160,2.00190,
+2.00200,2.00200,2.00210,2.00220,2.00230,2.00240,2.00250,2.00270,2.00260,
+2.00260,2.00260,2.00270,2.00260,2.00250,2.00240)
+expectedMean <- 2.00185600000000
+expectedSigma <- 0.000429123454003053
+verifyMean(values, expectedMean, tol, "Mavro")
+verifySigma(values, expectedSigma, tol, "Mavro")
+
+displayDashes(WIDTH)        
diff --git a/src/test/R/exponentialTestCases b/src/test/R/exponentialTestCases
new file mode 100644
index 0000000..25f801a
--- /dev/null
+++ b/src/test/R/exponentialTestCases
@@ -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.
+#
+#------------------------------------------------------------------------------
+# R source file to validate exponential distribution tests in
+# org.apache.commons.math.distribution.ExponentialDistributionTest
+#
+# To run the test, install R, put this file and testFunctions
+# into the same directory, launch R from this directory and then enter
+# source("<name-of-this-file>")
+#
+#------------------------------------------------------------------------------
+tol <- 1E-9
+
+# Function definitions
+
+source("testFunctions")           # utility test functions
+
+# function to verify distribution computations
+
+verifyDistribution <- function(points, expected, mean, tol) {
+ rDistValues <- rep(0, length(points))
+    i <- 0
+    for (point in points) {
+        i <- i + 1
+        rDistValues[i] <- pexp(point, 1/mean)
+    }
+    output <- c("Distribution test mean = ", mean)
+    if (assertEquals(expected, rDistValues, tol, "Distribution Values")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }       
+}
+
+# function to verify density computations
+verifyDensity <- function(points, expected, mean, tol) {
+ rDensityValues <- rep(0, length(points))
+    i <- 0
+    for (point in points) {
+        i <- i + 1
+        rDensityValues[i] <- dexp(point, 1/mean)
+    }
+    output <- c("Density test mean = ", mean)
+    if (assertEquals(expected, rDensityValues, tol, "Density Values")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }       
+}
+
+# function to verify quantiles
+verifyQuantiles <- function(points, expected, mean, tol) {
+	rQuantileValues <- rep(0, length(points))
+    i <- 0
+    for (point in points) {
+        i <- i + 1
+        rQuantileValues[i] <- qexp(point, 1/mean, log = FALSE)
+    }
+    output <- c("Quantile test mean = ", mean)
+    if (assertEquals(expected, rQuantileValues, tol, "Quantile Values")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }    
+}
+
+
+#--------------------------------------------------------------------------
+cat("Exponential test cases\n")
+
+mean <- 5
+
+distributionValues <- c(0, 0, 0.001, 0.01, 0.025, 0.05, 0.1, 0.999,
+                0.990, 0.975, 0.950, 0.900)
+densityValues <- c(0.2, 0.2, 0.1998, 0.198, 0.195, 0.19, 0.18, 0.000200000000000,
+                0.00200000000002, 0.00499999999997, 0.00999999999994, 0.0199999999999)
+distributionPoints <- c(0, 0, 0.00500250166792, 0.0502516792675, 0.126589039921, 0.256466471938,
+                0.526802578289, 34.5387763949, 23.0258509299, 18.4443972706, 14.9786613678, 11.5129254650)
+verifyDistribution(distributionPoints, distributionValues, mean, tol)
+verifyQuantiles(distributionValues, distributionPoints, mean, tol)
+verifyDensity(distributionPoints, densityValues, mean, tol)
+
+output <- "Probability test P(.25 < X < .75)"
+if (assertEquals(0.0905214480757, pexp(.75, 1/mean) - pexp(.25, 1/mean), tol, "Probability value")) {
+    displayPadded(output, SUCCEEDED, WIDTH)
+} else {
+    displayPadded(output, FAILED, WIDTH)
+}      
+
+displayDashes(WIDTH)
diff --git a/src/test/R/hypergeometricTestCases b/src/test/R/hypergeometricTestCases
new file mode 100644
index 0000000..a912fa5
--- /dev/null
+++ b/src/test/R/hypergeometricTestCases
@@ -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.
+#
+#------------------------------------------------------------------------------
+# R source file to validate Hypergeometric distribution tests in
+# org.apache.commons.math.distribution.HypergeometricDistributionTest
+#
+# To run the test, install R, put this file and testFunctions
+# into the same directory, launch R from this directory and then enter
+# source("<name-of-this-file>")
+#
+# R functions used
+# dhyper(x, m, n, k, log = FALSE) <- density 
+# phyper(q, m, n, k, lower.tail = TRUE, log.p = FALSE) <- distribution
+# qhyper(p, m, n, k, lower.tail = TRUE, log.p = FALSE) <- quantiles
+#------------------------------------------------------------------------------
+tol <- 1E-6                       # error tolerance for tests
+#------------------------------------------------------------------------------
+# Function definitions
+
+source("testFunctions")           # utility test functions
+
+# function to verify density computations
+
+verifyDensity <- function(points, expected, good, bad, selected, tol) {
+    rDensityValues <- rep(0, length(points))
+    i <- 0
+    for (point in points) {
+        i <- i + 1
+        rDensityValues[i] <- dhyper(point, good, bad, selected)
+    }
+    output <- c("Density test good = ", good, ", bad = ", bad, 
+                ", selected = ",selected)
+    if (assertEquals(expected,rDensityValues,tol,"Density Values")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }       
+}
+
+# function to verify distribution computations
+
+verifyDistribution <- function(points, expected, good, bad, selected, tol) {
+    rDistValues <- rep(0, length(points))
+    i <- 0
+    for (point in points) {
+        i <- i + 1
+        rDistValues[i] <- phyper(point, good, bad, selected)
+    }
+    output <- c("Distribution test good = ", good, ", bad = ",
+                 bad, ", selected = ",selected)
+    if (assertEquals(expected,rDistValues,tol,"Distribution Values")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }       
+}
+
+#--------------------------------------------------------------------------
+cat("Hypergeometric test cases\n")
+
+good <- 5
+bad <- 5
+selected <- 5
+
+densityPoints <- c(-1, 0, 1, 2, 3, 4, 5, 10)
+densityValues <- c(0, 0.003968, 0.099206, 0.396825, 0.396825, 0.099206, 
+                   0.003968, 0)
+distributionValues <- c(0, .003968, .103175, .50000, .896825, .996032,
+                        1.00000, 1)
+#Eliminate p=1 case because it will mess up adjustement below
+inverseCumPoints <- c(0, 0.001, 0.010, 0.025, 0.050, 0.100, 0.999,
+                      0.990, 0.975, 0.950, 0.900)
+inverseCumValues <- c(-1, -1, 0, 0, 0, 0, 4, 3, 3, 3, 3)
+
+verifyDensity(densityPoints, densityValues, good, bad, selected, tol)
+verifyDistribution(densityPoints, distributionValues, good, bad, selected, tol)
+
+i <- 0
+rInverseCumValues <- rep(0,length(inverseCumPoints))
+for (point in inverseCumPoints) {
+  i <- i + 1
+  rInverseCumValues[i] <- qhyper(point, good, bad, selected)
+}
+
+output <- c("Inverse Distribution test good = ", good, ", bad = ", bad, 
+            ", selected = ", selected)
+# R defines quantiles from the right, need to subtract one
+if (assertEquals(inverseCumValues, rInverseCumValues-1, tol,
+    "Inverse Dist Values")) {
+    displayPadded(output, SUCCEEDED, 80)
+} else {
+    displayPadded(output, FAILED, 80)
+}       
+
+# Degenerate cases
+good <- 5
+bad <- 0
+selected <- 3
+densityPoints <- c(-1, 0, 1, 3, 10)
+densityValues <- c(0, 0, 0, 1, 0)
+distributionValues <- c(0, 0, 0, 1, 1)
+verifyDensity(densityPoints, densityValues, good, bad, selected, tol)
+verifyDistribution(densityPoints, distributionValues, good, bad, selected, tol)
+
+good <- 0
+bad <- 5
+selected <- 3
+densityPoints <- c(-1, 0, 1, 3, 10)
+densityValues <- c(0, 1, 0, 0, 0)
+distributionValues <- c(0, 1, 1, 1, 1)
+verifyDensity(densityPoints, densityValues, good, bad, selected, tol)
+verifyDistribution(densityPoints, distributionValues, good, bad, selected, tol)
+
+good <- 3
+bad <- 2
+selected <- 5
+densityPoints <- c(-1, 0, 1, 3, 10)
+densityValues <- c(0, 0, 0, 1, 0)
+distributionValues <- c(0, 0, 0, 1, 1)
+verifyDensity(densityPoints, densityValues, good, bad, selected, tol)
+verifyDistribution(densityPoints, distributionValues, good, bad, selected, tol)
+
+displayDashes(WIDTH)
diff --git a/src/test/R/multipleOLSRegressionTestCases b/src/test/R/multipleOLSRegressionTestCases
new file mode 100644
index 0000000..1fe983d
--- /dev/null
+++ b/src/test/R/multipleOLSRegressionTestCases
@@ -0,0 +1,309 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+#------------------------------------------------------------------------------
+# R source file to validate OLS multiple regression tests in
+# org.apache.commons.math.stat.regression.OLSMultipleLinearRegressionTest
+#
+# To run the test, install R, put this file and testFunctions
+# into the same directory, launch R from this directory and then enter
+# source("<name-of-this-file>")
+#
+#------------------------------------------------------------------------------
+tol <- 1E-8                       # default error tolerance for tests
+#------------------------------------------------------------------------------
+# Function definitions
+
+source("testFunctions")           # utility test functions
+options(digits=16)	              # override number of digits displayed
+
+# function to verify OLS computations
+
+verifyRegression <- function(model, expectedBeta, expectedResiduals,
+  expectedErrors, expectedStdError, expectedRSquare, expecteAdjRSquare, modelName) {
+    betaHat <- as.vector(coefficients(model))
+    residuals <- as.vector(residuals(model))
+    errors <-  as.vector(as.matrix(coefficients(summary(model)))[,2])
+    stdError <- summary(model)$sigma
+    rSquare <- summary(model)$r.squared
+    adjRSquare <- summary(model)$adj.r.squared
+    output <- c("Parameter test dataset = ", modelName)
+    if (assertEquals(expectedBeta,betaHat,tol,"Parameters")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }  
+    output <- c("Residuals test dataset = ", modelName)
+    if (assertEquals(expectedResiduals,residuals,tol,"Residuals")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }    
+    output <- c("Errors test dataset = ", modelName)
+    if (assertEquals(expectedErrors,errors,tol,"Errors")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }
+    output <- c("Standard Error test dataset = ", modelName)
+    if (assertEquals(expectedStdError,stdError,tol,"Regression Standard Error")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }
+    output <- c("RSquared test dataset = ", modelName)
+    if (assertEquals(expectedRSquare,rSquare,tol,"RSquared")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }
+    output <- c("Adjusted RSquared test dataset = ", modelName)
+    if (assertEquals(expecteAdjRSquare,adjRSquare,tol,"Adjusted RSquared")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }
+}
+
+#--------------------------------------------------------------------------
+cat("Multiple regression OLS test cases\n")
+
+# Perfect fit
+x1 <- c(0,2,0,0,0,0)
+x2 <- c(0,0,3,0,0,0)
+x3 <- c(0,0,0,4,0,0)
+x4 <- c(0,0,0,0,5,0)
+x5 <- c(0,0,0,0,0,6)
+y <- c(11, 12, 13, 14, 15, 16)
+model <- lm(y ~ x1 + x2 + x3 + x4 + x5)
+expectedBeta <- c(11.0,0.5,0.666666666666667,0.75,0.8,0.8333333333333333)
+expectedResiduals <- c(0,0,0,0,0,0)
+expectedErrors <- c(NaN,NaN,NaN,NaN,NaN,NaN)
+expectedStdError <- NaN
+expectedRSquare <- 1
+expectedAdjRSquare <- NaN
+verifyRegression(model, expectedBeta, expectedResiduals, expectedErrors,
+ expectedStdError, expectedRSquare, expectedAdjRSquare, "perfect fit") 
+
+# Longley
+#
+# Data Source: J. Longley (1967) "An Appraisal of Least Squares Programs for the
+# Electronic Computer from the Point of View of the User", 
+# Journal of the American Statistical Association, 
+# vol. 62. September, pp. 819-841.
+#
+# Certified values (and data) are from NIST:
+# http://www.itl.nist.gov/div898/strd/lls/data/LINKS/DATA/Longley.dat
+#
+design <- matrix(c(60323,83.0,234289,2356,1590,107608,1947,
+                    61122,88.5,259426,2325,1456,108632,1948,
+                    60171,88.2,258054,3682,1616,109773,1949,
+                    61187,89.5,284599,3351,1650,110929,1950,
+                    63221,96.2,328975,2099,3099,112075,1951,
+                    63639,98.1,346999,1932,3594,113270,1952,
+                    64989,99.0,365385,1870,3547,115094,1953,
+                    63761,100.0,363112,3578,3350,116219,1954,
+                    66019,101.2,397469,2904,3048,117388,1955,
+                    67857,104.6,419180,2822,2857,118734,1956,
+                    68169,108.4,442769,2936,2798,120445,1957,
+                    66513,110.8,444546,4681,2637,121950,1958,
+                    68655,112.6,482704,3813,2552,123366,1959,
+                    69564,114.2,502601,3931,2514,125368,1960,
+                    69331,115.7,518173,4806,2572,127852,1961,
+                    70551,116.9,554894,4007,2827,130081,1962),
+                    nrow = 16, ncol = 7, byrow = TRUE)
+y <- design[,1]
+x1 <- design[,2]
+x2 <- design[,3]
+x3 <- design[,4]
+x4 <- design[,5]
+x5 <- design[,6]
+x6 <- design[,7]
+model <- lm(y ~ x1 + x2 + x3 + x4 + x5 + x6)
+
+estimates <- matrix(c(-3482258.63459582,890420.383607373,
+                       15.0618722713733,84.9149257747669,
+                      -0.358191792925910E-01,0.334910077722432E-01,
+                      -2.02022980381683,0.488399681651699,
+                      -1.03322686717359,0.214274163161675,
+                      -0.511041056535807E-01,0.226073200069370,
+                       1829.15146461355,455.478499142212),
+                       nrow = 7, ncol = 2, byrow = TRUE)
+
+expectedBeta <- estimates[,1]
+expectedErrors <- estimates[,2]
+expectedResiduals <- c(267.340029759711,-94.0139423988359,46.28716775752924,
+ -410.114621930906,309.7145907602313,-249.3112153297231,-164.0489563956039,
+ -13.18035686637081,14.30477260005235,455.394094551857,-17.26892711483297,
+ -39.0550425226967,-155.5499735953195,-85.6713080421283,341.9315139607727,
+ -206.7578251937366)
+expectedStdError <- 304.8540735619638
+expectedRSquare <- 0.995479004577296
+expectedAdjRSquare <- 0.992465007628826
+
+verifyRegression(model, expectedBeta, expectedResiduals, expectedErrors,
+expectedStdError, expectedRSquare, expectedAdjRSquare, "Longley") 
+
+# Model with no intercept
+model <- lm(y ~ 0 + x1 + x2 + x3 + x4 + x5 + x6)
+
+estimates <- matrix(c(-52.99357013868291, 129.54486693117232, 
+         0.07107319907358, 0.03016640003786,
+        -0.42346585566399, 0.41773654056612, 
+        -0.57256866841929, 0.27899087467676,
+        -0.41420358884978, 0.32128496193363, 
+         48.41786562001326, 17.68948737819961),
+         nrow = 6, ncol = 2, byrow = TRUE)
+
+expectedBeta <- estimates[,1]
+expectedErrors <- estimates[,2]
+expectedResiduals <- c(279.90274927293092, -130.32465380836874, 90.73228661967445,
+  -401.31252201634948, -440.46768772620027, -543.54512853774793, 201.32111639536299,
+   215.90889365977932, 73.09368242049943, 913.21694494481869, 424.82484953610174, 
+   -8.56475876776709, -361.32974610842876, 27.34560497213464, 151.28955976355002,
+   -492.49937355336846)
+expectedStdError <- 475.1655079819517
+expectedRSquare <- 0.9999670130706
+expectedAdjRSquare <- 0.999947220913
+
+verifyRegression(model, expectedBeta, expectedResiduals, expectedErrors,
+expectedStdError, expectedRSquare, expectedAdjRSquare, "Longley No Intercept") 
+
+# Swiss Fertility (R dataset named "swiss")
+
+design <- matrix(c(80.2,17.0,15,12,9.96,
+  83.1,45.1,6,9,84.84,
+  92.5,39.7,5,5,93.40,
+  85.8,36.5,12,7,33.77,
+  76.9,43.5,17,15,5.16,
+  76.1,35.3,9,7,90.57,
+  83.8,70.2,16,7,92.85,
+  92.4,67.8,14,8,97.16,
+  82.4,53.3,12,7,97.67,
+  82.9,45.2,16,13,91.38,
+  87.1,64.5,14,6,98.61,
+  64.1,62.0,21,12,8.52,
+  66.9,67.5,14,7,2.27,
+  68.9,60.7,19,12,4.43,
+  61.7,69.3,22,5,2.82,
+  68.3,72.6,18,2,24.20,
+  71.7,34.0,17,8,3.30,
+  55.7,19.4,26,28,12.11,
+  54.3,15.2,31,20,2.15,
+  65.1,73.0,19,9,2.84,
+  65.5,59.8,22,10,5.23,
+  65.0,55.1,14,3,4.52,
+  56.6,50.9,22,12,15.14,
+  57.4,54.1,20,6,4.20,
+  72.5,71.2,12,1,2.40,
+  74.2,58.1,14,8,5.23,
+  72.0,63.5,6,3,2.56,
+  60.5,60.8,16,10,7.72,
+  58.3,26.8,25,19,18.46,
+  65.4,49.5,15,8,6.10,
+  75.5,85.9,3,2,99.71,
+  69.3,84.9,7,6,99.68,
+  77.3,89.7,5,2,100.00,
+  70.5,78.2,12,6,98.96,
+  79.4,64.9,7,3,98.22,
+  65.0,75.9,9,9,99.06,
+  92.2,84.6,3,3,99.46,
+  79.3,63.1,13,13,96.83,
+  70.4,38.4,26,12,5.62,
+  65.7,7.7,29,11,13.79,
+  72.7,16.7,22,13,11.22,
+  64.4,17.6,35,32,16.92,
+  77.6,37.6,15,7,4.97,
+  67.6,18.7,25,7,8.65,
+  35.0,1.2,37,53,42.34,
+  44.7,46.6,16,29,50.43,
+  42.8,27.7,22,29,58.33),
+  nrow = 47, ncol = 5, byrow = TRUE)
+
+y  <- design[,1]
+x1 <- design[,2]
+x2 <- design[,3]
+x3 <- design[,4]
+x4 <- design[,5]
+
+model <- lm(y ~ x1 + x2 + x3 + x4)
+
+estimates <- matrix(c(91.05542390271397,6.94881329475087,
+                      -0.22064551045715,0.07360008972340,
+                      -0.26058239824328,0.27410957467466,
+                      -0.96161238456030,0.19454551679325,
+                       0.12441843147162,0.03726654773803),
+                       nrow = 5, ncol = 2, byrow = TRUE)
+
+expectedBeta <- estimates[,1]
+expectedErrors <- estimates[,2]
+
+expectedResiduals <- c(7.1044267859730512,1.6580347433531366,
+  4.6944952770029644,8.4548022690166160,13.6547432343186212,
+ -9.3586864458500774,7.5822446330520386,15.5568995563859289,
+  0.8113090736598980,7.1186762732484308,7.4251378771228724,
+  2.6761316873234109,0.8351584810309354,7.1769991119615177,
+ -3.8746753206299553,-3.1337779476387251,-0.1412575244091504,
+  1.1186809170469780,-6.3588097346816594,3.4039270429434074,
+  2.3374058329820175,-7.9272368576900503,-7.8361010968497959,
+ -11.2597369269357070,0.9445333697827101,6.6544245101380328,
+ -0.9146136301118665,-4.3152449403848570,-4.3536932047009183,
+ -3.8907885169304661,-6.3027643926302188,-7.8308982189289091,
+ -3.1792280015332750,-6.7167298771158226,-4.8469946718041754,
+ -10.6335664353633685,11.1031134362036958,6.0084032641811733,
+  5.4326230830188482,-7.2375578629692230,2.1671550814448222,
+  15.0147574652763112,4.8625103516321015,-7.1597256413907706,
+  -0.4515205619767598,-10.2916870903837587,-15.7812984571900063)
+
+expectedStdError <- 7.73642194433223
+expectedRSquare <- 0.649789742860228
+expectedAdjRSquare <- 0.6164363850373927
+
+verifyRegression(model, expectedBeta, expectedResiduals, expectedErrors,
+expectedStdError, expectedRSquare, expectedAdjRSquare, "Swiss Fertility") 
+
+# model with no intercept
+model <- lm(y ~ 0 + x1 + x2 + x3 + x4)
+
+estimates <- matrix(c(0.52191832900513, 0.10470063765677,
+      2.36588087917963, 0.41684100584290,
+     -0.94770353802795, 0.43370143099691, 
+      0.30851985863609, 0.07694953606522),
+     nrow = 4, ncol = 2, byrow = TRUE)
+
+expectedBeta <- estimates[,1]
+expectedErrors <- estimates[,2]
+
+expectedResiduals <- c(44.138759883538249, 27.720705122356215, 35.873200836126799, 
+  34.574619581211977, 26.600168342080213, 15.074636243026923, -12.704904871199814,
+  1.497443824078134, 2.691972687079431, 5.582798774291231, -4.422986561283165, 
+  -9.198581600334345, 4.481765170730647, 2.273520207553216, -22.649827853221336,
+  -17.747900013943308, 20.298314638496436, 6.861405135329779, -8.684712790954924,
+  -10.298639278062371, -9.896618896845819, 4.568568616351242, -15.313570491727944,
+  -13.762961360873966, 7.156100301980509, 16.722282219843990, 26.716200609071898,
+  -1.991466398777079, -2.523342564719335, 9.776486693095093, -5.297535127628603,
+  -16.639070567471094, -10.302057295211819, -23.549487860816846, 1.506624392156384,
+  -17.939174438345930, 13.105792202765040, -1.943329906928462, -1.516005841666695,
+  -0.759066561832886, 20.793137744128977, -2.485236153005426, 27.588238710486976,
+  2.658333257106881, -15.998337823623046, -5.550742066720694, -14.219077806826615)
+
+expectedStdError <- 17.24710630547
+expectedRSquare <- 0.946350722085
+expectedAdjRSquare <- 0.9413600915813
+
+verifyRegression(model, expectedBeta, expectedResiduals, expectedErrors,
+expectedStdError, expectedRSquare, expectedAdjRSquare, "Swiss Fertility No Intercept") 
+
+displayDashes(WIDTH)
diff --git a/src/test/R/normalTestCases b/src/test/R/normalTestCases
new file mode 100644
index 0000000..ae54a96
--- /dev/null
+++ b/src/test/R/normalTestCases
@@ -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.
+#
+#------------------------------------------------------------------------------
+# R source file to validate Normal distribution tests in
+# org.apache.commons.math.distribution.NormalDistributionTest
+#
+# To run the test, install R, put this file and testFunctions
+# into the same directory, launch R from this directory and then enter
+# source("<name-of-this-file>")
+#
+# R functions used
+# pnorm(q, mean=0, sd=1, lower.tail = TRUE, log.p = FALSE) <-- distribution
+#-----------------------------------------------------------------------------
+tol <- 1E-9
+
+# Function definitions
+
+source("testFunctions")           # utility test functions
+
+# function to verify distribution computations
+
+verifyDistribution <- function(points, expected, mu, sigma, tol) {
+ rDistValues <- rep(0, length(points))
+    i <- 0
+    for (point in points) {
+        i <- i + 1
+        rDistValues[i] <- pnorm(point, mu, sigma, log = FALSE)
+    }
+    output <- c("Distribution test mu = ",mu,", sigma = ", sigma)
+    if (assertEquals(expected, rDistValues, tol, "Distribution Values")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }       
+}
+
+# function to verify density computations
+
+verifyDensity <- function(points, expected, mu, sigma, tol) {
+ rDensityValues <- rep(0, length(points))
+    i <- 0
+    for (point in points) {
+        i <- i + 1
+        rDensityValues[i] <- dnorm(point, mu, sigma, log = FALSE)
+    }
+    output <- c("Density test mu = ",mu,", sigma = ", sigma)
+    if (assertEquals(expected, rDensityValues, tol, "Density Values")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }       
+}
+
+#--------------------------------------------------------------------------
+cat("Normal test cases\n")
+
+mu <- 2.1
+sigma <- 1.4
+distributionValues <- c(0.001, 0.01, 0.025, 0.05, 0.1, 0.999,
+                0.990, 0.975, 0.950, 0.900)
+densityValues <- c(0.00240506434076, 0.0190372444310, 0.0417464784322, 0.0736683145538, 0.125355951380,
+                0.00240506434076, 0.0190372444310, 0.0417464784322, 0.0736683145538, 0.125355951380)
+distributionPoints <- c(-2.226325228634938, -1.156887023657177, -0.643949578356075, -0.2027950777320613, 0.305827808237559,
+                6.42632522863494, 5.35688702365718, 4.843949578356074, 4.40279507773206, 3.89417219176244)
+verifyDistribution(distributionPoints, distributionValues, mu, sigma, tol)
+verifyDensity(distributionPoints, densityValues, mu, sigma, tol)
+
+distributionValues <- c( 0.0227501319482, 0.158655253931, 0.5, 0.841344746069, 0.977249868052,
+                     0.998650101968, 0.999968328758, 0.999999713348)
+densityValues <- c(0.0385649760808, 0.172836231799, 0.284958771715, 0.172836231799, 0.0385649760808,
+                0.00316560600853, 9.55930184035e-05, 1.06194251052e-06)
+distributionPoints <- c(mu - 2 *sigma, mu - sigma, mu, mu + sigma,
+		mu + 2 * sigma,  mu + 3 * sigma, mu + 4 * sigma,
+                    mu + 5 * sigma)
+verifyDistribution(distributionPoints, distributionValues, mu, sigma, tol)
+verifyDensity(distributionPoints, densityValues, mu, sigma, tol)
+
+mu <- 0
+sigma <- 1
+distributionPoints <- c(mu - 2 *sigma, mu - sigma, mu, mu + sigma,
+		mu + 2 * sigma,  mu + 3 * sigma, mu + 4 * sigma,
+                    mu + 5 * sigma)
+densityValues <- c(0.0539909665132, 0.241970724519, 0.398942280401, 0.241970724519, 0.0539909665132,
+                0.00443184841194, 0.000133830225765, 1.48671951473e-06)            
+verifyDistribution(distributionPoints, distributionValues, mu, sigma, tol)
+verifyDensity(distributionPoints, densityValues, mu, sigma, tol)
+
+mu <- 0
+sigma <- 0.1
+distributionPoints <- c(mu - 2 *sigma, mu - sigma, mu, mu + sigma,
+		mu + 2 * sigma,  mu + 3 * sigma, mu + 4 * sigma,
+                    mu + 5 * sigma)
+densityValues <- c(0.539909665132, 2.41970724519, 3.98942280401, 2.41970724519,
+                0.539909665132, 0.0443184841194, 0.00133830225765, 1.48671951473e-05)                    
+verifyDistribution(distributionPoints, distributionValues, mu, sigma, tol)
+verifyDensity(distributionPoints, densityValues, mu, sigma, tol)
+
+displayDashes(WIDTH)
diff --git a/src/test/R/pascalTestCases b/src/test/R/pascalTestCases
new file mode 100644
index 0000000..a8c6c31
--- /dev/null
+++ b/src/test/R/pascalTestCases
@@ -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.
+#
+#------------------------------------------------------------------------------
+# R source file to validate Pascal distribution tests in
+# org.apache.commons.math.distribution.PascalDistributionTest
+#
+# To run the test, install R, put this file and testFunctions
+# into the same directory, launch R from this directory and then enter
+# source("<name-of-this-file>")
+#
+# R functions used
+# dnbinom(x, size, prob, mu, log = FALSE) <- density
+# pnbinom(q, size, prob, mu, lower.tail = TRUE, log.p = FALSE) <- distribution
+# qnbinom(p, size, prob, mu, lower.tail = TRUE, log.p = FALSE) <- quantiles
+#------------------------------------------------------------------------------
+tol <- 1E-9                       # error tolerance for tests
+#------------------------------------------------------------------------------
+# Function definitions
+
+source("testFunctions")           # utility test functions
+
+# function to verify density computations
+
+verifyDensity <- function(points, expected, size, p, tol) {
+    rDensityValues <- rep(0, length(points))
+    i <- 0
+    for (point in points) {
+        i <- i + 1
+        rDensityValues[i] <- dnbinom(point, size, p)
+    }
+    output <- c("Density test size = ", size, ", p = ", p)
+    if (assertEquals(expected,rDensityValues,tol,"Density Values")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }       
+}
+
+# function to verify distribution computations
+
+verifyDistribution <- function(points, expected, size, p, tol) {
+    rDistValues <- rep(0, length(points))
+    i <- 0
+    for (point in points) {
+        i <- i + 1
+        rDistValues[i] <- pnbinom(point, size, p)
+    }
+    output <- c("Distribution test size = ", size, ", p = ", p)
+    if (assertEquals(expected,rDistValues,tol,"Distribution Values")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }       
+}
+
+#--------------------------------------------------------------------------
+cat("Negative Binomial test cases\n")
+
+size <- 10.0
+probability <- 0.70
+
+densityPoints <- c(-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)
+densityValues <- c(0, 0.0282475249, 0.0847425747, 0.139825248255, 0.167790297906, 0.163595540458,
+          0.137420253985, 0.103065190489, 0.070673273478, 0.0450542118422, 0.0270325271053,
+          0.0154085404500, 0.0084046584273)
+distributionValues <- c(0, 0.0282475249, 0.1129900996, 0.252815347855, 0.420605645761, 0.584201186219,
+          0.721621440204, 0.824686630693, 0.895359904171, 0.940414116013, 0.967446643119,
+          0.982855183569, 0.991259841996)
+inverseCumPoints <- c( 0, 0.001, 0.010, 0.025, 0.050, 0.100, 0.999,
+          0.990, 0.975, 0.950, 0.900)
+inverseCumValues <- c(-1, -1, -1, -1, 0, 0, 13, 10, 9, 8, 7)
+
+verifyDensity(densityPoints,densityValues,size,probability,tol)
+verifyDistribution(densityPoints, distributionValues, size, probability, tol)
+
+i <- 0
+rInverseCumValues <- rep(0,length(inverseCumPoints))
+for (point in inverseCumPoints) {
+  i <- i + 1
+  rInverseCumValues[i] <- qnbinom(point, size, probability)
+}
+
+output <- c("Inverse Distribution test n = ", size, ", p = ", probability)
+# R defines quantiles from the right, need to subtract one
+if (assertEquals(inverseCumValues, rInverseCumValues-1, tol,
+    "Inverse Dist Values")) {
+    displayPadded(output, SUCCEEDED, 80)
+} else {
+    displayPadded(output, FAILED, 80)
+}       
+
+# Degenerate cases
+
+size <- 5
+probability <- 0.0
+
+densityPoints <- c(-1, 0, 1, 10, 11)
+# Note: commons math returns 0's below
+densityValues <- c(NaN, NaN, NaN, NaN, NaN)
+distributionPoints <- c(-1, 0, 1, 5, 10)
+# Note: commons math returns 0's below
+distributionValues <- c(NaN, NaN, NaN, NaN, NaN)
+
+output <- c("Density test n = ", size, ", p = ", probability)
+verifyDensity(densityPoints,densityValues,size,probability,tol)
+output <- c("Distribution test n = ", size, ", p = ", probability)
+verifyDistribution(distributionPoints,distributionValues,size,probability,tol)
+
+size <- 5
+probability <- 1.0
+
+densityPoints <- c(-1, 0, 1, 2, 5, 10)
+densityValues <- c(0, 1, 0, 0, 0, 0)
+distributionPoints <- c(-1, 0, 1, 2, 5, 10)
+distributionValues <- c(0, 1, 1, 1, 1, 1)
+
+output <- c("Density test n = ", size, ", p = ", probability)
+verifyDensity(densityPoints,densityValues,size,probability,tol)
+output <- c("Distribution test n = ", size, ", p = ", probability)
+verifyDistribution(distributionPoints,distributionValues,size,probability,tol)
+
+displayDashes(WIDTH)
diff --git a/src/test/R/poissonTestCases b/src/test/R/poissonTestCases
new file mode 100644
index 0000000..200aa75
--- /dev/null
+++ b/src/test/R/poissonTestCases
@@ -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.
+#
+#------------------------------------------------------------------------------
+# R source file to validate Poisson distribution tests in
+# org.apache.commons.math.distribution.PoissonDistributionTest
+#
+# To run the test, install R, put this file and testFunctions
+# into the same directory, launch R from this directory and then enter
+# source("<name-of-this-file>")
+#
+# R functions used
+# dpois(x, lambda, log = FALSE) <-- density
+# ppois(q, lambda, lower.tail = TRUE, log.p = FALSE) <-- distribution
+# pnorm(q, mean=0, sd=1, lower.tail = TRUE, log.p = FALSE) <-- normal dist.
+#------------------------------------------------------------------------------
+tol <- 1E-10
+#------------------------------------------------------------------------------
+# Function definitions
+
+source("testFunctions")           # utility test functions
+
+# function to verify density computations
+
+verifyDensity <- function(points, expected, lambda, tol) {
+    rDensityValues <- rep(0, length(points))
+    i <- 0
+    for (point in points) {
+        i <- i + 1
+        rDensityValues[i] <- dpois(point, lambda, log = FALSE)
+    }
+    output <- c("Density test lambda = ", lambda)
+    if (assertEquals(expected, rDensityValues, tol, "Density Values")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }       
+}
+
+# function to verify distribution computations
+
+verifyDistribution <- function(points, expected, lambda, tol) {
+    rDistValues <- rep(0, length(points))
+    i <- 0
+    for (point in points) {
+        i <- i + 1
+        rDistValues[i] <- ppois(point, lambda, log = FALSE)
+    }
+    output <- c("Distribution test lambda = ", lambda)
+    if (assertEquals(expected, rDistValues, tol, "Distribution Values")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }       
+}
+
+# function to verify normal approximation
+
+verifyNormalApproximation <- function(expected, lambda, lower, upper, tol) {
+    rValue <- pnorm(upper, mean=lambda, sd=sqrt(lambda), lower.tail = TRUE,
+               log.p = FALSE) - 
+               pnorm(lower, mean=lambda, sd=sqrt(lambda), lower.tail = TRUE,
+               log.p = FALSE)
+    output <- c("Normal approx. test lambda = ", lambda, " upper = ",
+               upper, " lower = ", lower)
+    if (assertEquals(expected, rValue, tol, "Distribution Values")) {
+        displayPadded(output, SUCCEEDED, WIDTH)
+    } else {
+        displayPadded(output, FAILED, WIDTH)
+    }       
+}
+
+cat("Poisson distribution test cases\n")
+
+# stock tests
+
+lambda <- 4.0
+densityPoints <- c(-1,0,1,2,3,4,5,10,20)
+densityValues <- c(0, 0.0183156388887,  0.073262555555, 0.14652511111,
+                   0.195366814813, 0.195366814813, 0.156293451851,
+                   0.00529247667642, 8.27746364655e-09)
+verifyDensity(densityPoints, densityValues, lambda, tol)
+
+
+distributionPoints <- c(-1, 0, 1, 2, 3, 4, 5, 10, 20)
+distributionValues <- c(0,  0.0183156388887, 0.0915781944437, 0.238103305554,
+                        0.433470120367, 0.62883693518, 0.78513038703,
+                        0.99716023388, 0.999999998077)
+verifyDistribution(distributionPoints, distributionValues, lambda, tol)
+
+# normal approximation tests
+
+lambda <- 100
+verifyNormalApproximation(0.706281887248, lambda, 89.5, 110.5, tol)
+
+lambda <- 10000
+verifyNormalApproximation(0.820070051552, lambda, 9899.5, 10200.5, tol)
+
+displayDashes(WIDTH)
+
+
+
+ 
+ 
diff --git a/src/test/R/regressionTestCases b/src/test/R/regressionTestCases
new file mode 100644
index 0000000..c4465ca
--- /dev/null
+++ b/src/test/R/regressionTestCases
@@ -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.
+#
+#-----------------------------------------------------------------------
+# R source file to validate Binomial distribution tests in
+# org.apache.commons.math.stat.regression.SimpleRegressionTest
+#
+# To run the test, install R, put this file and testFunctions
+# into the same directory, launch R from this directory and then enter
+# source("<name-of-this-file>")
+#
+# Output will be written to a file named "regTestResults"
+# in the directory from which R was launched
+#
+#------------------------------------------------------------------------------
+tol <- 1E-8
+#------------------------------------------------------------------------------
+# Function definitions
+
+source("testFunctions")           # utility test functions
+#------------------------------------------------------------------------------
+# infData example
+
+cat("Regresssion test cases\n")
+
+x <- c(15.6, 26.8,37.8,36.4,35.5,18.6,15.3,7.9,0.0)
+y <- c(5.2, 6.1, 8.7, 8.5, 8.8, 4.9, 4.5, 2.5, 1.1)
+model<-lm(y~x)
+coef <- coefficients(summary(model))
+intercept <- coef[1, 1]
+interceptStd <- coef[1, 2]
+slope <- coef[2, 1]
+slopeStd <- coef[2, 2]
+significance <- coef[2, 4]
+
+output <- "InfData std error test"
+if (assertEquals(0.011448491, slopeStd, tol, "Slope Standard Error") &&
+    assertEquals(0.286036932, interceptStd, tol, "Intercept Standard Error")) {
+    displayPadded(output, SUCCEEDED, WIDTH)
+} else {
+    displayPadded(output, FAILED, WIDTH)
+}
+
+output <- "InfData significance test"
+if (assertEquals(4.596e-07, significance, tol, "Significance")) {
+    displayPadded(output, SUCCEEDED, WIDTH)
+} else {
+    displayPadded(output, FAILED, WIDTH)
+}     
+ 
+output <- "InfData conf interval test"
+ci<-confint(model)
+# ci[1,1] = lower 2.5% bound for intercept, ci[1,2] = upper 97.5% for intercept
+# ci[2,1] = lower 2.5% bound for slope,     ci[2,2] = upper 97.5% for slope
+halfWidth <- ci[2,2] - slope
+if (assertEquals(0.0270713794287, halfWidth, tol, 
+   "Slope conf. interval half-width")) {
+    displayPadded(output, SUCCEEDED, WIDTH)
+} else {
+    displayPadded(output, FAILED, WIDTH)
+}       
+#------------------------------------------------------------------------------
+# Norris dataset from NIST examples
+
+y <- c(0.1, 338.8, 118.1, 888.0, 9.2, 228.1, 668.5, 998.5, 449.1, 778.9, 559.2,
+0.3, 0.1, 778.1, 668.8, 339.3, 448.9, 10.8, 557.7, 228.3, 998.0, 888.8, 119.6,
+0.3, 0.6, 557.6, 339.3, 888.0, 998.5, 778.9, 10.2, 117.6, 228.9, 668.4, 449.2,
+0.2)
+x <- c(0.2, 337.4, 118.2, 884.6, 10.1, 226.5, 666.3, 996.3, 448.6, 777.0, 558.2,
+0.4, 0.6, 775.5, 666.9, 338.0, 447.5, 11.6, 556.0, 228.1, 995.8, 887.6, 120.2,
+0.3, 0.3, 556.8, 339.1, 887.2, 999.0, 779.0, 11.1, 118.3, 229.2, 669.1, 448.9,
+0.5)
+model<-lm(y~x)
+coef <- coefficients(summary(model))
+intercept <- coef[1, 1]
+interceptStd <- coef[1, 2]
+slope <- coef[2, 1]
+slopeStd <- coef[2, 2]
+
+output <- "Norris std error test"
+if (assertEquals(0.429796848199937E-03, slopeStd, tol, "Slope Standard Error")
+    && assertEquals(0.232818234301152, interceptStd, tol,
+   "Intercept Standard Error")) {
+    displayPadded(output, SUCCEEDED, WIDTH)
+} else {
+    displayPadded(output, FAILED, WIDTH)
+}
+#------------------------------------------------------------------------------
+# infData2 -- bad fit example
+#
+x <- c(1,2,3,4,5,6)
+y <- c(1,0,5,2,-1,12)
+model<-lm(y~x)
+coef <- coefficients(summary(model))
+intercept <- coef[1, 1]
+interceptStd <- coef[1, 2]
+slope <- coef[2, 1]
+slopeStd <- coef[2, 2]
+significance <- coef[2, 4]
+
+output <- "InfData2 std error test"
+if (assertEquals(1.07260253, slopeStd, tol, "Slope Standard Error") &&
+    assertEquals(4.17718672, interceptStd, tol, "Intercept Standard Error")) {
+    displayPadded(output, SUCCEEDED, WIDTH)
+} else {
+    displayPadded(output, FAILED, WIDTH)
+}
+
+output <- "InfData2 significance test"
+if (assertEquals(0.261829133982, significance, tol, "Significance")) {
+    displayPadded(output, SUCCEEDED, WIDTH)
+} else {
+    displayPadded(output, FAILED, WIDTH)
+}  
+
+output <- "InfData2 conf interval test"
+ci<-confint(model)
+# ci[1,1] = lower 2.5% bound for intercept, ci[1,2] = upper 97.5% for intercept
+# ci[2,1] = lower 2.5% bound for slope,     ci[2,2] = upper 97.5% for slope
+halfWidth <- ci[2,2] - slope
+if (assertEquals(2.97802204827, halfWidth, tol, 
+   "Slope conf. interval half-width")) {
+    displayPadded(output, SUCCEEDED, WIDTH)
+} else {
+    displayPadded(output, FAILED, WIDTH)
+} 
+#------------------------------------------------------------------------------
+# Correlation example
+
+x <- c(101.0, 100.1, 100.0, 90.6, 86.5, 89.7, 90.6, 82.8, 70.1, 65.4,
+       61.3, 62.5, 63.6, 52.6, 59.7, 59.5, 61.3)
+y <- c(99.2, 99.0, 100.0, 111.6, 122.2, 117.6, 121.1, 136.0, 154.2, 153.6,
+       158.5, 140.6, 136.2, 168.0, 154.3, 149.0, 165.5)  
+
+output <- "Correlation test"
+if (assertEquals(-0.94663767742, cor(x,y, method="pearson"), tol, 
+   "Correlation coefficient")) {
+    displayPadded(output, SUCCEEDED, WIDTH)
+} else {
+    displayPadded(output, FAILED, WIDTH)
+}
+ 
+displayDashes(WIDTH)
+
+
+ 
+ 
diff --git a/src/test/R/testAll b/src/test/R/testAll
new file mode 100644
index 0000000..ba5d223
--- /dev/null
+++ b/src/test/R/testAll
@@ -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.
+#
+#------------------------------------------------------------------------------
+# R source file to run all commons-math R verification tests
+# 
+# To run the test, install R, put this file and all other o.a.c.math R
+# verification tests and the testfunctions utilities file into the same
+# directory, launch R from this directory and then enter
+# source("<name-of-this-file>")
+#
+# To redirect output to a file, uncomment the following line, substituting
+# another file path if you like (default behavior is to write the file to the
+# current directory).
+#
+# sink("testResults")
+#------------------------------------------------------------------------------
+# distribution
+source("binomialTestCases")
+source("normalTestCases")
+source("poissonTestCases")
+source("hypergeometricTestCases")
+source("exponentialTestCases")
+source("cauchyTestCases.R")
+source("pascalTestCases")
+source("TDistributionTestCases.R")
+source("FDistributionTestCases.R")
+source("GammaDistributionTestCases.R")
+source("WeibullDistributionTestCases.R")
+source("ChiSquareDistributionTestCases.R")
+
+# regression
+source("regressionTestCases")
+
+# inference
+source("chiSquareTestCases")
+source("anovaTestCases")
+
+# descriptive
+source("descriptiveTestCases")
+
+# multiple regression
+source("multipleOLSRegressionTestCases")
+
+# covariance
+source("covarianceTestCases")
+
+# correlation
+source("correlationTestCases")
+
+#------------------------------------------------------------------------------
+# if output has been diverted, change it back
+if (sink.number()) {
+    sink()
+}
diff --git a/src/test/R/testFunctions b/src/test/R/testFunctions
new file mode 100644
index 0000000..c952774
--- /dev/null
+++ b/src/test/R/testFunctions
@@ -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.
+#
+#------------------------------------------------------------------------------
+#
+# Utility functions used in R comparison tests.
+#
+#------------------------------------------------------------------------------
+# Global constants
+#------------------------------------------------------------------------------
+WIDTH <- 80                    # screen size constant for display functions
+SUCCEEDED <- "SUCCEEDED"
+FAILED <- "FAILED"
+options(digits=12)             # display 12 digits throughout
+#------------------------------------------------------------------------------
+# Comparison functions
+#------------------------------------------------------------------------------
+# Tests to see if <expected> and <observed> are within <tol> of
+# one another in the sup norm.
+#
+# Returns 1 if no pair of corresponding non-NULL, non-NaN, non-na entries
+# differs by more than abs and NULLs, NaNs, na's correspond;
+# otherwise displays <message> and returns 0.
+# Works for both vectors and scalar values.
+#
+assertEquals <- function(expected, observed, tol, message) {
+    failed <- 0
+    if (any(is.na(observed) != is.na(expected))) {
+        failed <- 1
+    }
+    if (any(is.null(observed) != is.null(expected))) {
+        failed <- 1
+    }
+    if (any(is.nan(expected) != is.nan(observed))) {
+        failed <- 1
+    }
+    if (any(is.na(expected) != is.na(observed))) {
+        failed <- 1
+    }
+    if (!failed) {
+        if(any(abs(observed - expected) > tol, na.rm = TRUE)) {
+            failed <- 1
+        }
+    }
+    if (failed) {
+        cat("FAILURE: ",message,"\n")
+        cat("EXPECTED: ",expected,"\n")
+        cat("OBSERVED: ",observed,"\n")
+        cat("DIFF: ",observed - expected,"\n")
+        cat("TOLERANCE: ",tol,"\n")
+    }  
+    return(!failed)
+}
+#------------------------------------------------------------------------------
+# Display functions
+#------------------------------------------------------------------------------
+# Displays n-col dashed line.
+#
+displayDashes <- function(n) {
+    cat(rep("-",n),"\n",sep='')
+    return(1)
+}
+#------------------------------------------------------------------------------
+# Displays <start>......<end> with enough dots in between to make <n> cols, 
+# followed by a new line character. Blows up if <start><end> is longer than
+# <n> cols by itself.
+#
+# Expects <start> and <end> to be strings (character vectors).
+# 
+displayPadded <- function(start, end, n) {
+    len = sum(nchar(start)) + sum(nchar(end))
+    cat(start, rep(".", n - len), end, "\n",sep='')
+    return(1)
+}
diff --git a/src/test/java/org/apache/commons/math/ArgumentOutsideDomainExceptionTest.java b/src/test/java/org/apache/commons/math/ArgumentOutsideDomainExceptionTest.java
new file mode 100644
index 0000000..2702363
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ArgumentOutsideDomainExceptionTest.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;
+
+import java.util.Locale;
+
+import org.apache.commons.math.util.FastMath;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ */
+public class ArgumentOutsideDomainExceptionTest extends TestCase {
+
+    public void testConstructor(){
+        ArgumentOutsideDomainException ex = new ArgumentOutsideDomainException(FastMath.PI, 10.0, 20.0);
+        assertNull(ex.getCause());
+        assertNotNull(ex.getMessage());
+        assertTrue(ex.getMessage().indexOf("3.14") > 0);
+        assertEquals(FastMath.PI, ex.getArgument()[0], 0);
+        assertFalse(ex.getMessage().equals(ex.getMessage(Locale.FRENCH)));
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/ConvergenceExceptionTest.java b/src/test/java/org/apache/commons/math/ConvergenceExceptionTest.java
new file mode 100644
index 0000000..ea8e478
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ConvergenceExceptionTest.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;
+
+import junit.framework.TestCase;
+
+import java.util.Locale;
+
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * @version $Revision: 1035475 $ $Date: 2010-11-15 23:39:25 +0100 (lun. 15 nov. 2010) $
+ */
+public class ConvergenceExceptionTest extends TestCase {
+
+    public void testConstructor(){
+        ConvergenceException ex = new ConvergenceException();
+        assertNull(ex.getCause());
+        assertNotNull(ex.getMessage());
+        assertNotNull(ex.getMessage(Locale.FRENCH));
+        assertFalse(ex.getMessage().equals(ex.getMessage(Locale.FRENCH)));
+    }
+
+    public void testConstructorPatternArguments(){
+        LocalizedFormats pattern = LocalizedFormats.ROTATION_MATRIX_DIMENSIONS;
+        Object[] arguments = { Integer.valueOf(6), Integer.valueOf(4) };
+        ConvergenceException ex = new ConvergenceException(pattern, arguments);
+        assertNull(ex.getCause());
+        assertEquals(pattern, ex.getGeneralPattern());
+        assertEquals(arguments.length, ex.getArguments().length);
+        for (int i = 0; i < arguments.length; ++i) {
+            assertEquals(arguments[i], ex.getArguments()[i]);
+        }
+        assertFalse(pattern.equals(ex.getMessage()));
+        assertFalse(ex.getMessage().equals(ex.getMessage(Locale.FRENCH)));
+    }
+
+    public void testConstructorCause(){
+        String inMsg = "inner message";
+        Exception cause = new Exception(inMsg);
+        ConvergenceException ex = new ConvergenceException(cause);
+        assertEquals(cause, ex.getCause());
+    }
+
+    public void testConstructorPatternArgumentsCause(){
+        LocalizedFormats pattern = LocalizedFormats.ROTATION_MATRIX_DIMENSIONS;
+        Object[] arguments = { Integer.valueOf(6), Integer.valueOf(4) };
+        String inMsg = "inner message";
+        Exception cause = new Exception(inMsg);
+        ConvergenceException ex = new ConvergenceException(cause, pattern, arguments);
+        assertEquals(cause, ex.getCause());
+        assertEquals(pattern, ex.getGeneralPattern());
+        assertEquals(arguments.length, ex.getArguments().length);
+        for (int i = 0; i < arguments.length; ++i) {
+            assertEquals(arguments[i], ex.getArguments()[i]);
+        }
+        assertFalse(pattern.equals(ex.getMessage()));
+        assertFalse(ex.getMessage().equals(ex.getMessage(Locale.FRENCH)));
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/DuplicateSampleAbscissaExceptionTest.java b/src/test/java/org/apache/commons/math/DuplicateSampleAbscissaExceptionTest.java
new file mode 100644
index 0000000..3555283
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/DuplicateSampleAbscissaExceptionTest.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;
+
+import java.util.Locale;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+public class DuplicateSampleAbscissaExceptionTest extends TestCase {
+
+    public void testConstructor(){
+        DuplicateSampleAbscissaException ex = new DuplicateSampleAbscissaException(1.2, 10, 11);
+        assertNull(ex.getCause());
+        assertNotNull(ex.getMessage());
+        assertTrue(ex.getMessage().indexOf("1.2") > 0);
+        assertEquals(1.2, ex.getDuplicateAbscissa(), 0);
+        assertFalse(ex.getMessage().equals(ex.getMessage(Locale.FRENCH)));
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/FunctionEvaluationExceptionTest.java b/src/test/java/org/apache/commons/math/FunctionEvaluationExceptionTest.java
new file mode 100644
index 0000000..0d2e161
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/FunctionEvaluationExceptionTest.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;
+
+import java.util.Locale;
+
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Revision: 1042336 $ $Date: 2010-12-05 13:40:48 +0100 (dim. 05 déc. 2010) $
+ * @deprecated in 2.2 (to be removed in 3.0)
+ */
+ at Deprecated
+public class FunctionEvaluationExceptionTest extends TestCase {
+
+    public void testConstructor(){
+        FunctionEvaluationException ex = new FunctionEvaluationException(0.0);
+        assertNull(ex.getCause());
+        assertNotNull(ex.getMessage());
+        assertTrue(ex.getMessage().indexOf("0") > 0);
+        assertEquals(0.0, ex.getArgument()[0], 0);
+    }
+
+    public void testConstructorArray(){
+        FunctionEvaluationException ex =
+            new FunctionEvaluationException(new double[] { 0, 1, 2 });
+        assertNull(ex.getCause());
+        assertNotNull(ex.getMessage());
+        assertTrue(ex.getMessage().indexOf("0") > 0);
+        assertEquals(0.0, ex.getArgument()[0], 0);
+        assertEquals(1.0, ex.getArgument()[1], 0);
+        assertEquals(2.0, ex.getArgument()[2], 0);
+    }
+
+    public void testConstructorPatternArguments(){
+        LocalizedFormats pattern = LocalizedFormats.EVALUATION_FAILED;
+        Object[] arguments = { Double.valueOf(0.0) };
+        FunctionEvaluationException ex = new FunctionEvaluationException(0.0, pattern, arguments);
+        assertNull(ex.getCause());
+        assertEquals(pattern, ex.getGeneralPattern());
+        assertEquals(arguments.length, ex.getArguments().length);
+        for (int i = 0; i < arguments.length; ++i) {
+            assertEquals(arguments[i], ex.getArguments()[i]);
+        }
+        assertFalse(pattern.equals(ex.getMessage()));
+        assertFalse(ex.getMessage().equals(ex.getMessage(Locale.FRENCH)));
+    }
+
+    public void testConstructorArrayPatternArguments(){
+        LocalizedFormats pattern = LocalizedFormats.EVALUATION_FAILED;
+        Object[] arguments = { Double.valueOf(0.0) };
+        FunctionEvaluationException ex =
+            new FunctionEvaluationException(new double[] { 0, 1, 2 }, pattern, arguments);
+        assertNull(ex.getCause());
+        assertEquals(pattern, ex.getGeneralPattern());
+        assertEquals(arguments.length, ex.getArguments().length);
+        for (int i = 0; i < arguments.length; ++i) {
+            assertEquals(arguments[i], ex.getArguments()[i]);
+        }
+        assertFalse(pattern.equals(ex.getMessage()));
+        assertFalse(ex.getMessage().equals(ex.getMessage(Locale.FRENCH)));
+        assertEquals(0.0, ex.getArgument()[0], 0);
+        assertEquals(1.0, ex.getArgument()[1], 0);
+        assertEquals(2.0, ex.getArgument()[2], 0);
+    }
+
+    public void testConstructorPatternArgumentsCause(){
+        LocalizedFormats pattern = LocalizedFormats.EVALUATION_FAILED;
+        Object[] arguments = { Double.valueOf(0.0) };
+        String inMsg = "inner message";
+        Exception cause = new Exception(inMsg);
+        FunctionEvaluationException ex = new FunctionEvaluationException(cause, 0.0, pattern, arguments);
+        assertEquals(cause, ex.getCause());
+        assertEquals(pattern, ex.getGeneralPattern());
+        assertEquals(arguments.length, ex.getArguments().length);
+        for (int i = 0; i < arguments.length; ++i) {
+            assertEquals(arguments[i], ex.getArguments()[i]);
+        }
+        assertFalse(pattern.equals(ex.getMessage()));
+        assertFalse(ex.getMessage().equals(ex.getMessage(Locale.FRENCH)));
+    }
+
+    public void testConstructorArrayPatternArgumentsCause(){
+        LocalizedFormats pattern = LocalizedFormats.EVALUATION_FAILED;
+        Object[] arguments = { Double.valueOf(0.0) };
+        String inMsg = "inner message";
+        Exception cause = new Exception(inMsg);
+        FunctionEvaluationException ex =
+            new FunctionEvaluationException(cause, new double[] { 0, 1, 2 }, pattern, arguments);
+        assertEquals(cause, ex.getCause());
+        assertEquals(pattern, ex.getGeneralPattern());
+        assertEquals(arguments.length, ex.getArguments().length);
+        for (int i = 0; i < arguments.length; ++i) {
+            assertEquals(arguments[i], ex.getArguments()[i]);
+        }
+        assertFalse(pattern.equals(ex.getMessage()));
+        assertFalse(ex.getMessage().equals(ex.getMessage(Locale.FRENCH)));
+        assertEquals(0.0, ex.getArgument()[0], 0);
+        assertEquals(1.0, ex.getArgument()[1], 0);
+        assertEquals(2.0, ex.getArgument()[2], 0);
+    }
+
+    public void testConstructorArgumentCause(){
+        String inMsg = "inner message";
+        Exception cause = new Exception(inMsg);
+        FunctionEvaluationException ex = new FunctionEvaluationException(cause, 0.0);
+        assertEquals(cause, ex.getCause());
+        assertTrue(ex.getMessage().equals(ex.getMessage(Locale.FRENCH)));
+    }
+
+    public void testConstructorArrayArgumentCause(){
+        String inMsg = "inner message";
+        Exception cause = new Exception(inMsg);
+        FunctionEvaluationException ex =
+            new FunctionEvaluationException(cause, new double[] { 0, 1, 2 });
+        assertEquals(cause, ex.getCause());
+        assertTrue(ex.getMessage().equals(ex.getMessage(Locale.FRENCH)));
+        assertEquals(0.0, ex.getArgument()[0], 0);
+        assertEquals(1.0, ex.getArgument()[1], 0);
+        assertEquals(2.0, ex.getArgument()[2], 0);
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/MathConfigurationExceptionTest.java b/src/test/java/org/apache/commons/math/MathConfigurationExceptionTest.java
new file mode 100644
index 0000000..3fd3226
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/MathConfigurationExceptionTest.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;
+
+import junit.framework.TestCase;
+
+import java.util.Locale;
+
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * @version $Revision: 1035475 $ $Date: 2010-11-15 23:39:25 +0100 (lun. 15 nov. 2010) $
+ */
+public class MathConfigurationExceptionTest extends TestCase {
+
+    public void testConstructor(){
+        MathConfigurationException ex = new MathConfigurationException();
+        assertNull(ex.getCause());
+        assertEquals("", ex.getMessage());
+        assertEquals("", ex.getMessage(Locale.FRENCH));
+    }
+
+    public void testConstructorPatternArguments(){
+        LocalizedFormats pattern = LocalizedFormats.ROTATION_MATRIX_DIMENSIONS;
+        Object[] arguments = { Integer.valueOf(6), Integer.valueOf(4) };
+        MathConfigurationException ex = new MathConfigurationException(pattern, arguments);
+        assertNull(ex.getCause());
+        assertEquals(pattern, ex.getGeneralPattern());
+        assertEquals(arguments.length, ex.getArguments().length);
+        for (int i = 0; i < arguments.length; ++i) {
+            assertEquals(arguments[i], ex.getArguments()[i]);
+        }
+        assertFalse(pattern.equals(ex.getMessage()));
+        assertFalse(ex.getMessage().equals(ex.getMessage(Locale.FRENCH)));
+    }
+
+    public void testConstructorCause(){
+        String inMsg = "inner message";
+        Exception cause = new Exception(inMsg);
+        MathConfigurationException ex = new MathConfigurationException(cause);
+        assertEquals(cause, ex.getCause());
+    }
+
+    public void testConstructorPatternArgumentsCause(){
+        LocalizedFormats pattern = LocalizedFormats.ROTATION_MATRIX_DIMENSIONS;
+        Object[] arguments = { Integer.valueOf(6), Integer.valueOf(4) };
+        String inMsg = "inner message";
+        Exception cause = new Exception(inMsg);
+        MathConfigurationException ex = new MathConfigurationException(cause, pattern, arguments);
+        assertEquals(cause, ex.getCause());
+        assertEquals(pattern, ex.getGeneralPattern());
+        assertEquals(arguments.length, ex.getArguments().length);
+        for (int i = 0; i < arguments.length; ++i) {
+            assertEquals(arguments[i], ex.getArguments()[i]);
+        }
+        assertFalse(pattern.equals(ex.getMessage()));
+        assertFalse(ex.getMessage().equals(ex.getMessage(Locale.FRENCH)));
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/MathExceptionTest.java b/src/test/java/org/apache/commons/math/MathExceptionTest.java
new file mode 100644
index 0000000..7f6a092
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/MathExceptionTest.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;
+
+import junit.framework.TestCase;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.util.Locale;
+
+import org.apache.commons.math.exception.util.DummyLocalizable;
+import org.apache.commons.math.exception.util.Localizable;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * @version $Revision: 1035475 $ $Date: 2010-11-15 23:39:25 +0100 (lun. 15 nov. 2010) $
+ */
+public class MathExceptionTest extends TestCase {
+
+    public void testConstructor(){
+        MathException ex = new MathException();
+        assertNull(ex.getCause());
+        assertEquals("", ex.getMessage());
+        assertEquals("", ex.getMessage(Locale.FRENCH));
+    }
+
+    public void testConstructorPatternArguments(){
+        LocalizedFormats pattern = LocalizedFormats.ROTATION_MATRIX_DIMENSIONS;
+        Object[] arguments = { Integer.valueOf(6), Integer.valueOf(4) };
+        MathException ex = new MathException(pattern, arguments);
+        assertNull(ex.getCause());
+        assertEquals(pattern, ex.getGeneralPattern());
+        assertEquals(arguments.length, ex.getArguments().length);
+        for (int i = 0; i < arguments.length; ++i) {
+            assertEquals(arguments[i], ex.getArguments()[i]);
+        }
+        assertFalse(pattern.equals(ex.getMessage()));
+        assertFalse(ex.getMessage().equals(ex.getMessage(Locale.FRENCH)));
+    }
+
+    public void testConstructorCause(){
+        String inMsg = "inner message";
+        Exception cause = new Exception(inMsg);
+        MathException ex = new MathException(cause);
+        assertEquals(cause, ex.getCause());
+    }
+
+    public void testConstructorPatternArgumentsCause(){
+        LocalizedFormats pattern = LocalizedFormats.ROTATION_MATRIX_DIMENSIONS;
+        Object[] arguments = { Integer.valueOf(6), Integer.valueOf(4) };
+        String inMsg = "inner message";
+        Exception cause = new Exception(inMsg);
+        MathException ex = new MathException(cause, pattern, arguments);
+        assertEquals(cause, ex.getCause());
+        assertEquals(pattern, ex.getGeneralPattern());
+        assertEquals(arguments.length, ex.getArguments().length);
+        for (int i = 0; i < arguments.length; ++i) {
+            assertEquals(arguments[i], ex.getArguments()[i]);
+        }
+        assertFalse(pattern.equals(ex.getMessage()));
+        assertFalse(ex.getMessage().equals(ex.getMessage(Locale.FRENCH)));
+    }
+
+    /**
+     * Tests the printStackTrace() operation.
+     */
+    public void testPrintStackTrace() {
+        Localizable outMsg = new DummyLocalizable("outer message");
+        Localizable inMsg = new DummyLocalizable("inner message");
+        MathException cause = new MathConfigurationException(inMsg);
+        MathException ex = new MathException(cause, outMsg);
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        PrintStream ps = new PrintStream(baos);
+        ex.printStackTrace(ps);
+        String stack = baos.toString();
+        String outerMsg = "org.apache.commons.math.MathException: outer message";
+        String innerMsg = "Caused by: " +
+        "org.apache.commons.math.MathConfigurationException: inner message";
+        assertTrue(stack.startsWith(outerMsg));
+        assertTrue(stack.indexOf(innerMsg) > 0);
+
+        PrintWriter pw = new PrintWriter(ps, true);
+        ex.printStackTrace(pw);
+        stack = baos.toString();
+        assertTrue(stack.startsWith(outerMsg));
+        assertTrue(stack.indexOf(innerMsg) > 0);
+    }
+
+    /**
+     * Test serialization
+     */
+    public void testSerialization() {
+        Localizable outMsg = new DummyLocalizable("outer message");
+        Localizable inMsg = new DummyLocalizable("inner message");
+        MathException cause = new MathConfigurationException(inMsg);
+        MathException ex = new MathException(cause, outMsg);
+        MathException image = (MathException) TestUtils.serializeAndRecover(ex);
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        PrintStream ps = new PrintStream(baos);
+        ex.printStackTrace(ps);
+        String stack = baos.toString();
+
+        ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
+        PrintStream ps2 = new PrintStream(baos2);
+        image.printStackTrace(ps2);
+        String stack2 = baos2.toString();
+
+        // See if JDK supports nested exceptions.  If not, stack trace of
+        // inner exception will not be serialized
+        boolean jdkSupportsNesting = false;
+        try {
+            Throwable.class.getDeclaredMethod("getCause", new Class[0]);
+            jdkSupportsNesting = true;
+        } catch (NoSuchMethodException e) {
+            jdkSupportsNesting = false;
+        }
+
+        if (jdkSupportsNesting) {
+            assertEquals(stack, stack2);
+        } else {
+            assertTrue(stack2.indexOf(inMsg.getSourceString()) != -1);
+            assertTrue(stack2.indexOf("MathConfigurationException") != -1);
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/MaxIterationsExceededExceptionTest.java b/src/test/java/org/apache/commons/math/MaxIterationsExceededExceptionTest.java
new file mode 100644
index 0000000..74df44d
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/MaxIterationsExceededExceptionTest.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;
+
+import java.util.Locale;
+
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ */
+public class MaxIterationsExceededExceptionTest extends TestCase {
+
+    public void testSimpleConstructor(){
+        MaxIterationsExceededException ex = new MaxIterationsExceededException(1000000);
+        assertNull(ex.getCause());
+        assertNotNull(ex.getMessage());
+        assertTrue(ex.getMessage().indexOf("1,000,000") > 0);
+        assertEquals(1000000, ex.getMaxIterations());
+        assertFalse(ex.getMessage().equals(ex.getMessage(Locale.FRENCH)));
+    }
+
+    public void testComplexConstructor(){
+        MaxIterationsExceededException ex =
+            new MaxIterationsExceededException(1000000,
+                LocalizedFormats.NON_CONVERGENT_CONTINUED_FRACTION,
+                1234567);
+        assertNull(ex.getCause());
+        assertNotNull(ex.getMessage());
+        assertTrue(ex.getMessage().indexOf("1,000,000") < 0);
+        assertTrue(ex.getMessage().indexOf("1,234,567") > 0);
+        assertEquals(1000000, ex.getMaxIterations());
+        assertFalse(ex.getMessage().equals(ex.getMessage(Locale.FRENCH)));
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/RetryTestCase.java b/src/test/java/org/apache/commons/math/RetryTestCase.java
new file mode 100644
index 0000000..7092d85
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/RetryTestCase.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;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.TestCase;
+
+/**
+ * A TestCase that retries tests when assertions fail.
+ * <p>
+ * If one or more tests throw an AssertionFailedError, all tests are
+ * repeated one time.
+ * <p>
+ * Errors or exceptions other than AssertionFailedError do not lead to retries.
+ *
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+public abstract class RetryTestCase extends TestCase {
+
+    public RetryTestCase() {
+        super();
+    }
+
+    public RetryTestCase(String arg0) {
+        super(arg0);
+    }
+
+    /**
+     *  Override runTest() to catch AssertionFailedError and retry
+     */
+    @Override
+    protected void runTest() throws Throwable {
+        try {
+            super.runTest();
+        } catch (AssertionFailedError err) {
+            // System.out.println("Retrying " + this.getName());
+            super.runTest();
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/TestUtils.java b/src/test/java/org/apache/commons/math/TestUtils.java
new file mode 100644
index 0000000..64137cf
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/TestUtils.java
@@ -0,0 +1,516 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.text.DecimalFormat;
+
+import junit.framework.Assert;
+import junit.framework.AssertionFailedError;
+
+import org.apache.commons.math.complex.Complex;
+import org.apache.commons.math.complex.ComplexFormat;
+import org.apache.commons.math.distribution.ContinuousDistribution;
+import org.apache.commons.math.linear.FieldMatrix;
+import org.apache.commons.math.linear.RealMatrix;
+import org.apache.commons.math.stat.inference.ChiSquareTest;
+import org.apache.commons.math.stat.inference.ChiSquareTestImpl;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * @version $Revision: 1042376 $ $Date: 2010-12-05 16:54:55 +0100 (dim. 05 déc. 2010) $
+ */
+public class TestUtils {
+    /**
+     * Collection of static methods used in math unit tests.
+     */
+    private TestUtils() {
+        super();
+    }
+
+    /**
+     * Verifies that expected and actual are within delta, or are both NaN or
+     * infinities of the same sign.
+     */
+    public static void assertEquals(double expected, double actual, double delta) {
+        assertEquals(null, expected, actual, delta);
+    }
+
+    /**
+     * Verifies that expected and actual are within delta, or are both NaN or
+     * infinities of the same sign.
+     */
+    public static void assertEquals(String msg, double expected, double actual, double delta) {
+        // check for NaN
+        if(Double.isNaN(expected)){
+            Assert.assertTrue("" + actual + " is not NaN.",
+                Double.isNaN(actual));
+        } else {
+            Assert.assertEquals(msg, expected, actual, delta);
+        }
+    }
+
+    /**
+     * Verifies that the two arguments are exactly the same, either
+     * both NaN or infinities of same sign, or identical floating point values.
+     */
+    public static void assertSame(double expected, double actual) {
+     assertEquals(expected, actual, 0);
+    }
+
+    /**
+     * Verifies that real and imaginary parts of the two complex arguments
+     * are exactly the same.  Also ensures that NaN / infinite components match.
+     */
+    public static void assertSame(Complex expected, Complex actual) {
+        assertSame(expected.getReal(), actual.getReal());
+        assertSame(expected.getImaginary(), actual.getImaginary());
+    }
+
+    /**
+     * Verifies that real and imaginary parts of the two complex arguments
+     * differ by at most delta.  Also ensures that NaN / infinite components match.
+     */
+    public static void assertEquals(Complex expected, Complex actual, double delta) {
+        assertEquals(expected.getReal(), actual.getReal(), delta);
+        assertEquals(expected.getImaginary(), actual.getImaginary(), delta);
+    }
+
+    /**
+     * Verifies that two double arrays have equal entries, up to tolerance
+     */
+    public static void assertEquals(double expected[], double observed[], double tolerance) {
+        assertEquals("Array comparison failure", expected, observed, tolerance);
+    }
+
+    /**
+     * Serializes an object to a bytes array and then recovers the object from the bytes array.
+     * Returns the deserialized object.
+     *
+     * @param o  object to serialize and recover
+     * @return  the recovered, deserialized object
+     */
+    public static Object serializeAndRecover(Object o) {
+        try {
+            // serialize the Object
+            ByteArrayOutputStream bos = new ByteArrayOutputStream();
+            ObjectOutputStream so = new ObjectOutputStream(bos);
+            so.writeObject(o);
+
+            // deserialize the Object
+            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
+            ObjectInputStream si = new ObjectInputStream(bis);
+            return si.readObject();
+        } catch (IOException ioe) {
+            return null;
+        } catch (ClassNotFoundException cnfe) {
+            return null;
+        }
+    }
+
+    /**
+     * Verifies that serialization preserves equals and hashCode.
+     * Serializes the object, then recovers it and checks equals and hash code.
+     *
+     * @param object  the object to serialize and recover
+     */
+    public static void checkSerializedEquality(Object object) {
+        Object object2 = serializeAndRecover(object);
+        Assert.assertEquals("Equals check", object, object2);
+        Assert.assertEquals("HashCode check", object.hashCode(), object2.hashCode());
+    }
+
+    /**
+     * Verifies that the relative error in actual vs. expected is less than or
+     * equal to relativeError.  If expected is infinite or NaN, actual must be
+     * the same (NaN or infinity of the same sign).
+     *
+     * @param expected expected value
+     * @param actual  observed value
+     * @param relativeError  maximum allowable relative error
+     */
+    public static void assertRelativelyEquals(double expected, double actual,
+            double relativeError) {
+        assertRelativelyEquals(null, expected, actual, relativeError);
+    }
+
+    /**
+     * Verifies that the relative error in actual vs. expected is less than or
+     * equal to relativeError.  If expected is infinite or NaN, actual must be
+     * the same (NaN or infinity of the same sign).
+     *
+     * @param msg  message to return with failure
+     * @param expected expected value
+     * @param actual  observed value
+     * @param relativeError  maximum allowable relative error
+     */
+    public static void assertRelativelyEquals(String msg, double expected,
+            double actual, double relativeError) {
+        if (Double.isNaN(expected)) {
+            Assert.assertTrue(msg, Double.isNaN(actual));
+        } else if (Double.isNaN(actual)) {
+            Assert.assertTrue(msg, Double.isNaN(expected));
+        } else if (Double.isInfinite(actual) || Double.isInfinite(expected)) {
+            Assert.assertEquals(expected, actual, relativeError);
+        } else if (expected == 0.0) {
+            Assert.assertEquals(msg, actual, expected, relativeError);
+        } else {
+            double absError = FastMath.abs(expected) * relativeError;
+            Assert.assertEquals(msg, expected, actual, absError);
+        }
+    }
+
+    /**
+     * Fails iff values does not contain a number within epsilon of z.
+     *
+     * @param msg  message to return with failure
+     * @param values complex array to search
+     * @param z  value sought
+     * @param epsilon  tolerance
+     */
+    public static void assertContains(String msg, Complex[] values,
+            Complex z, double epsilon) {
+        int i = 0;
+        boolean found = false;
+        while (!found && i < values.length) {
+            try {
+                assertEquals(values[i], z, epsilon);
+                found = true;
+            } catch (AssertionFailedError er) {
+                // no match
+            }
+            i++;
+        }
+        if (!found) {
+            Assert.fail(msg +
+                " Unable to find " + ComplexFormat.formatComplex(z));
+        }
+    }
+
+    /**
+     * Fails iff values does not contain a number within epsilon of z.
+     *
+     * @param values complex array to search
+     * @param z  value sought
+     * @param epsilon  tolerance
+     */
+    public static void assertContains(Complex[] values,
+            Complex z, double epsilon) {
+        assertContains(null, values, z, epsilon);
+    }
+
+    /**
+     * Fails iff values does not contain a number within epsilon of x.
+     *
+     * @param msg  message to return with failure
+     * @param values double array to search
+     * @param x value sought
+     * @param epsilon  tolerance
+     */
+    public static void assertContains(String msg, double[] values,
+            double x, double epsilon) {
+        int i = 0;
+        boolean found = false;
+        while (!found && i < values.length) {
+            try {
+                assertEquals(values[i], x, epsilon);
+                found = true;
+            } catch (AssertionFailedError er) {
+                // no match
+            }
+            i++;
+        }
+        if (!found) {
+            Assert.fail(msg + " Unable to find" + x);
+        }
+    }
+
+    /**
+     * Fails iff values does not contain a number within epsilon of x.
+     *
+     * @param values double array to search
+     * @param x value sought
+     * @param epsilon  tolerance
+     */
+    public static void assertContains(double[] values, double x,
+            double epsilon) {
+       assertContains(null, values, x, epsilon);
+    }
+
+    /** verifies that two matrices are close (1-norm) */
+    public static void assertEquals(String msg, RealMatrix expected, RealMatrix observed,
+        double tolerance) {
+
+        Assert.assertNotNull(msg + "\nObserved should not be null",observed);
+
+        if (expected.getColumnDimension() != observed.getColumnDimension() ||
+                expected.getRowDimension() != observed.getRowDimension()) {
+            StringBuilder messageBuffer = new StringBuilder(msg);
+            messageBuffer.append("\nObserved has incorrect dimensions.");
+            messageBuffer.append("\nobserved is " + observed.getRowDimension() +
+                    " x " + observed.getColumnDimension());
+            messageBuffer.append("\nexpected " + expected.getRowDimension() +
+                    " x " + expected.getColumnDimension());
+            Assert.fail(messageBuffer.toString());
+        }
+
+        RealMatrix delta = expected.subtract(observed);
+        if (delta.getNorm() >= tolerance) {
+            StringBuilder messageBuffer = new StringBuilder(msg);
+            messageBuffer.append("\nExpected: " + expected);
+            messageBuffer.append("\nObserved: " + observed);
+            messageBuffer.append("\nexpected - observed: " + delta);
+            Assert.fail(messageBuffer.toString());
+        }
+    }
+
+    /** verifies that two matrices are equal */
+    public static void assertEquals(FieldMatrix<? extends FieldElement<?>> expected,
+                                    FieldMatrix<? extends FieldElement<?>> observed) {
+
+        Assert.assertNotNull("Observed should not be null",observed);
+
+        if (expected.getColumnDimension() != observed.getColumnDimension() ||
+                expected.getRowDimension() != observed.getRowDimension()) {
+            StringBuilder messageBuffer = new StringBuilder();
+            messageBuffer.append("Observed has incorrect dimensions.");
+            messageBuffer.append("\nobserved is " + observed.getRowDimension() +
+                    " x " + observed.getColumnDimension());
+            messageBuffer.append("\nexpected " + expected.getRowDimension() +
+                    " x " + expected.getColumnDimension());
+            Assert.fail(messageBuffer.toString());
+        }
+
+        for (int i = 0; i < expected.getRowDimension(); ++i) {
+            for (int j = 0; j < expected.getColumnDimension(); ++j) {
+                FieldElement<?> eij = expected.getEntry(i, j);
+                FieldElement<?> oij = observed.getEntry(i, j);
+                Assert.assertEquals(eij, oij);
+            }
+        }
+    }
+
+    /** verifies that two arrays are close (sup norm) */
+    public static void assertEquals(String msg, double[] expected, double[] observed,
+        double tolerance) {
+        StringBuilder out = new StringBuilder(msg);
+        if (expected.length != observed.length) {
+            out.append("\n Arrays not same length. \n");
+            out.append("expected has length ");
+            out.append(expected.length);
+            out.append(" observed length = ");
+            out.append(observed.length);
+            Assert.fail(out.toString());
+        }
+        boolean failure = false;
+        for (int i=0; i < expected.length; i++) {
+            try {
+                assertEquals(expected[i], observed[i], tolerance);
+            } catch (AssertionFailedError ex) {
+                failure = true;
+                out.append("\n Elements at index ");
+                out.append(i);
+                out.append(" differ. ");
+                out.append(" expected = ");
+                out.append(expected[i]);
+                out.append(" observed = ");
+                out.append(observed[i]);
+            }
+        }
+        if (failure) {
+            Assert.fail(out.toString());
+        }
+    }
+
+    /** verifies that two arrays are equal */
+    public static <T extends FieldElement<T>> void assertEquals(T[] m, T[] n) {
+        if (m.length != n.length) {
+            Assert.fail("vectors not same length");
+        }
+        for (int i = 0; i < m.length; i++) {
+            Assert.assertEquals(m[i],n[i]);
+        }
+    }
+
+    /**
+     * Computes the sum of squared deviations of <values> from <target>
+     * @param values array of deviates
+     * @param target value to compute deviations from
+     *
+     * @return sum of squared deviations
+     */
+    public static double sumSquareDev(double[] values, double target) {
+        double sumsq = 0d;
+        for (int i = 0; i < values.length; i++) {
+            final double dev = values[i] - target;
+            sumsq += (dev * dev);
+        }
+        return sumsq;
+    }
+    
+    /**
+     * Asserts the null hypothesis for a ChiSquare test.  Fails and dumps arguments and test
+     * statistics if the null hypothesis can be rejected with confidence 100 * (1 - alpha)%
+     * 
+     * @param valueLabels
+     * @param expected expected counts
+     * @param observed observed counts
+     * @param alpha significance level of the test
+     */
+    public static void assertChiSquareAccept(String[] valueLabels, double[] expected, long[] observed, double alpha) throws Exception {
+        ChiSquareTest chiSquareTest = new ChiSquareTestImpl();
+        try {
+            // Fail if we can reject null hypothesis that distributions are the same
+            Assert.assertFalse(chiSquareTest.chiSquareTest(expected, observed, alpha));
+        } catch (AssertionFailedError ex) {
+            StringBuilder msgBuffer = new StringBuilder();
+            DecimalFormat df = new DecimalFormat("#.##");
+            msgBuffer.append("Chisquare test failed");
+            msgBuffer.append(" p-value = ");
+            msgBuffer.append(chiSquareTest.chiSquareTest(expected, observed));
+            msgBuffer.append(" chisquare statistic = ");
+            msgBuffer.append(chiSquareTest.chiSquare(expected, observed));
+            msgBuffer.append(". \n");
+            msgBuffer.append("value\texpected\tobserved\n");
+            for (int i = 0; i < expected.length; i++) {
+                msgBuffer.append(valueLabels[i]);
+                msgBuffer.append("\t");
+                msgBuffer.append(df.format(expected[i]));
+                msgBuffer.append("\t\t");
+                msgBuffer.append(observed[i]);
+                msgBuffer.append("\n");
+            }
+            msgBuffer.append("This test can fail randomly due to sampling error with probability ");
+            msgBuffer.append(alpha);
+            msgBuffer.append(".");
+            Assert.fail(msgBuffer.toString());
+        }   
+    }
+    
+    /**
+     * Asserts the null hypothesis for a ChiSquare test.  Fails and dumps arguments and test
+     * statistics if the null hypothesis can be rejected with confidence 100 * (1 - alpha)%
+     * 
+     * @param values
+     * @param expected expected counts
+     * @param observed observed counts
+     * @param alpha significance level of the test
+     */
+    public static void assertChiSquareAccept(int[] values, double[] expected, long[] observed, double alpha) throws Exception {
+        String[] labels = new String[values.length];
+        for (int i = 0; i < values.length; i++) {
+            labels[i] = Integer.toString(values[i]);
+        }
+        assertChiSquareAccept(labels, expected, observed, alpha);
+    }
+    
+    /**
+     * Asserts the null hypothesis for a ChiSquare test.  Fails and dumps arguments and test
+     * statistics if the null hypothesis can be rejected with confidence 100 * (1 - alpha)%
+     * 
+     * @param values
+     * @param expected expected counts
+     * @param observed observed counts
+     * @param alpha significance level of the test
+     */
+    public static void assertChiSquareAccept(double[] values, double[] expected, long[] observed, double alpha) throws Exception {
+        String[] labels = new String[values.length];
+        for (int i = 0; i < values.length; i++) {
+            labels[i] = Double.toString(values[i]);
+        }
+        assertChiSquareAccept(labels, expected, observed, alpha);
+    }
+    
+    /**
+     * Asserts the null hypothesis for a ChiSquare test.  Fails and dumps arguments and test
+     * statistics if the null hypothesis can be rejected with confidence 100 * (1 - alpha)%
+     * 
+     * @param expected expected counts
+     * @param observed observed counts
+     * @param alpha significance level of the test
+     */
+    public static void assertChiSquareAccept(double[] expected, long[] observed, double alpha) throws Exception {
+        String[] labels = new String[expected.length];
+        for (int i = 0; i < labels.length; i++) {
+            labels[i] = Integer.toString(i + 1);
+        }
+        assertChiSquareAccept(labels, expected, observed, alpha);
+    }
+    
+    /**
+     * Computes the 25th, 50th and 75th percentiles of the given distribution and returns
+     * these values in an array.
+     */
+    public static double[] getDistributionQuartiles(ContinuousDistribution distribution) throws Exception {
+        double[] quantiles = new double[3];
+        quantiles[0] = distribution.inverseCumulativeProbability(0.25d);
+        quantiles[1] = distribution.inverseCumulativeProbability(0.5d);
+        quantiles[2] = distribution.inverseCumulativeProbability(0.75d);
+        return quantiles;
+    }
+    
+    /**
+     * Updates observed counts of values in quartiles.
+     * counts[0] <-> 1st quartile ... counts[3] <-> top quartile
+     */
+    public static void updateCounts(double value, long[] counts, double[] quartiles) {
+        if (value < quartiles[0]) {
+            counts[0]++;
+        } else if (value > quartiles[2]) {
+            counts[3]++;
+        } else if (value > quartiles[1]) {
+            counts[2]++;
+        } else {
+            counts[1]++;
+        }  
+    }
+    
+    /**
+     * Eliminates points with zero mass from densityPoints and densityValues parallel
+     * arrays.  Returns the number of positive mass points and collapses the arrays so
+     * that the first <returned value> elements of the input arrays represent the positive
+     * mass points.
+     */
+    public static int eliminateZeroMassPoints(int[] densityPoints, double[] densityValues) {
+        int positiveMassCount = 0;
+        for (int i = 0; i < densityValues.length; i++) {
+            if (densityValues[i] > 0) {
+                positiveMassCount++;
+            }
+        }
+        if (positiveMassCount < densityValues.length) {
+            int[] newPoints = new int[positiveMassCount];
+            double[] newValues = new double[positiveMassCount];
+            int j = 0;
+            for (int i = 0; i < densityValues.length; i++) {
+                if (densityValues[i] > 0) {
+                    newPoints[j] = densityPoints[i];
+                    newValues[j] = densityValues[i];
+                    j++;
+                }
+            }
+            System.arraycopy(newPoints,0,densityPoints,0,positiveMassCount);
+            System.arraycopy(newValues,0,densityValues,0,positiveMassCount);
+        }
+        return positiveMassCount;
+    } 
+}
diff --git a/src/test/java/org/apache/commons/math/analysis/BinaryFunctionTest.java b/src/test/java/org/apache/commons/math/analysis/BinaryFunctionTest.java
new file mode 100644
index 0000000..2311a2e
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/BinaryFunctionTest.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.analysis;
+
+import org.apache.commons.math.util.FastMath;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class BinaryFunctionTest {
+
+    @Test
+    public void testAdd() throws Exception {
+        Assert.assertEquals(5.0, BinaryFunction.ADD.value(2, 3), 1.0e-15);
+        Assert.assertEquals(0.0, BinaryFunction.ADD.value(-1, 1), 1.0e-15);
+    }
+
+    @Test
+    public void testSubtract() throws Exception {
+        Assert.assertEquals(-1.0, BinaryFunction.SUBTRACT.value(2, 3), 1.0e-15);
+        Assert.assertEquals(-2.0, BinaryFunction.SUBTRACT.value(-1, 1), 1.0e-15);
+    }
+
+    @Test
+    public void testMultiply() throws Exception {
+        Assert.assertEquals(6.0, BinaryFunction.MULTIPLY.value(2, 3), 1.0e-15);
+        Assert.assertEquals(-1.0, BinaryFunction.MULTIPLY.value(-1, 1), 1.0e-15);
+    }
+
+    @Test
+    public void testDivide() throws Exception {
+        Assert.assertEquals(1.5, BinaryFunction.DIVIDE.value(3, 2), 1.0e-15);
+        Assert.assertEquals(-1.0, BinaryFunction.DIVIDE.value(-1, 1), 1.0e-15);
+    }
+
+    @Test
+    public void testPow() throws Exception {
+        Assert.assertEquals(9.0, BinaryFunction.POW.value(3, 2), 1.0e-15);
+        Assert.assertEquals(-1.0, BinaryFunction.POW.value(-1, 1), 1.0e-15);
+    }
+
+    @Test
+    public void testAtan2() throws Exception {
+        Assert.assertEquals(FastMath.PI / 4, BinaryFunction.ATAN2.value(1, 1), 1.0e-15);
+        Assert.assertEquals(-FastMath.PI / 4, BinaryFunction.ATAN2.value(-1, 1), 1.0e-15);
+    }
+
+    @Test
+    public void testFix1st() throws Exception {
+        ComposableFunction f = BinaryFunction.POW.fix1stArgument(2);
+        for (double x = 0.0; x < 1.0; x += 0.01) {
+            Assert.assertEquals(FastMath.pow(2.0, x), f.value(x), 1.0e-15);
+        }
+    }
+
+    @Test
+    public void testFix2nd() throws Exception {
+        ComposableFunction f = BinaryFunction.POW.fix2ndArgument(2);
+        for (double y = 0.0; y < 1.0; y += 0.01) {
+            Assert.assertEquals(y * y, f.value(y), 1.0e-15);
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/analysis/ComposableFunctionTest.java b/src/test/java/org/apache/commons/math/analysis/ComposableFunctionTest.java
new file mode 100644
index 0000000..2099dce
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/ComposableFunctionTest.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.analysis;
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.util.FastMath;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ComposableFunctionTest {
+
+    @Test
+    public void testZero() throws Exception {
+        Assert.assertEquals(0.0, ComposableFunction.ZERO.value(1), 1.0e-15);
+        Assert.assertEquals(0.0, ComposableFunction.ZERO.value(2), 1.0e-15);
+    }
+
+    @Test
+    public void testOne() throws Exception {
+        Assert.assertEquals(1.0, ComposableFunction.ONE.value(1), 1.0e-15);
+        Assert.assertEquals(1.0, ComposableFunction.ONE.value(2), 1.0e-15);
+    }
+
+    @Test
+    public void testIdentity() throws Exception {
+        Assert.assertEquals(1.0, ComposableFunction.IDENTITY.value(1), 1.0e-15);
+        Assert.assertEquals(2.0, ComposableFunction.IDENTITY.value(2), 1.0e-15);
+    }
+
+    @Test
+    public void testRint() throws Exception {
+        Assert.assertEquals(1.0, ComposableFunction.RINT.value(0.9), 1.0e-15);
+        Assert.assertEquals(2.0, ComposableFunction.RINT.value(2.2), 1.0e-15);
+    }
+
+    @Test
+    public void testSignum() throws Exception {
+        Assert.assertEquals(1.0, ComposableFunction.SIGNUM.value(12.3), 1.0e-15);
+        Assert.assertEquals(-1.0, ComposableFunction.SIGNUM.value(-6), 1.0e-15);
+    }
+
+    @Test
+    public void testComposition() throws Exception {
+        ComposableFunction abs    = ComposableFunction.ABS;
+        ComposableFunction acos   = ComposableFunction.ACOS;
+        ComposableFunction asin   = ComposableFunction.ASIN;
+        ComposableFunction atan   = ComposableFunction.ATAN;
+        ComposableFunction cbrt   = ComposableFunction.CBRT;
+        ComposableFunction ceil   = ComposableFunction.CEIL;
+        ComposableFunction cos    = ComposableFunction.COS;
+        ComposableFunction cosh   = ComposableFunction.COSH;
+        ComposableFunction exp    = ComposableFunction.EXP;
+        ComposableFunction expm1  = ComposableFunction.EXPM1;
+        ComposableFunction floor  = ComposableFunction.FLOOR;
+        ComposableFunction id     = ComposableFunction.IDENTITY;
+        ComposableFunction log    = ComposableFunction.LOG;
+        ComposableFunction log10  = ComposableFunction.LOG10;
+        ComposableFunction negate = ComposableFunction.NEGATE;
+        ComposableFunction sin    = ComposableFunction.SIN;
+        ComposableFunction sinh   = ComposableFunction.SINH;
+        ComposableFunction sqrt   = ComposableFunction.SQRT;
+        ComposableFunction tan    = ComposableFunction.TAN;
+        ComposableFunction tanh   = ComposableFunction.TANH;
+        ComposableFunction ulp    = ComposableFunction.ULP;
+
+        ComposableFunction f1 = sqrt.of(abs.of(expm1.of(cbrt.of(tanh).of(id))));
+        for (double x = 0.1; x < 0.9; x += 0.01) {
+            Assert.assertEquals(FastMath.sqrt(FastMath.abs(FastMath.expm1(FastMath.cbrt(FastMath.tanh(x))))),
+                                f1.value(x), 1.0e-15);
+        }
+
+        ComposableFunction f2 = cosh.of(sinh.of(tanh.of(ceil.postCompose(log.postCompose(cosh)))));
+        for (double x = 0.1; x < 12.9; x += 1.0) {
+            Assert.assertEquals(FastMath.cosh(FastMath.sinh(FastMath.tanh(FastMath.cosh(FastMath.log(FastMath.ceil(x)))))),
+                                f2.value(x), 1.0e-15);
+        }
+
+        ComposableFunction f3 = cos.of(sin.of(tan.of(acos.of(asin.of(log10.of(log.of(ulp)))))));
+        for (double x = 1.0e16; x < 1.0e17; x += 1.0e16) {
+            Assert.assertEquals(FastMath.cos(FastMath.sin(FastMath.tan(FastMath.acos(FastMath.asin(FastMath.log10(FastMath.log(FastMath.ulp(x)))))))),
+                                f3.value(x), 1.0e-15);
+        }
+
+        ComposableFunction f4 = atan.of(exp.of(negate.of(floor)));
+        for (double x = 1.1; x < 10.2; x += 1.0) {
+            Assert.assertEquals(FastMath.atan(FastMath.exp(-FastMath.floor(x))),
+                                f4.value(x), 1.0e-15);
+        }
+
+    }
+
+    @Test
+    public void testCombine() throws Exception {
+
+        ComposableFunction f =
+            ComposableFunction.COS.combine(ComposableFunction.ASIN, BinaryFunction.POW);
+        for (double x = 0.1; x < 0.9; x += 0.01) {
+            Assert.assertEquals(FastMath.pow(FastMath.cos(x), FastMath.asin(x)), f.value(x), 1.0e-15);
+        }
+
+    }
+
+    @Test
+    public void testSimpleCombination() throws Exception {
+
+        ComposableFunction f1 = ComposableFunction.COS.add(3);
+        ComposableFunction f2 = ComposableFunction.COS.add(ComposableFunction.SIN);
+        ComposableFunction f3 = ComposableFunction.COS.subtract(ComposableFunction.SIN);
+        ComposableFunction f4 = ComposableFunction.COS.multiply(ComposableFunction.SIN);
+        ComposableFunction f5 = ComposableFunction.COS.multiply(5);
+        ComposableFunction f6 = ComposableFunction.COS.divide(ComposableFunction.SIN);
+        for (double x = 0.1; x < 0.9; x += 0.01) {
+            Assert.assertEquals(FastMath.cos(x) + 3, f1.value(x), 1.0e-15);
+            Assert.assertEquals(FastMath.cos(x) + FastMath.sin(x), f2.value(x), 1.0e-15);
+            Assert.assertEquals(FastMath.cos(x) - FastMath.sin(x), f3.value(x), 1.0e-15);
+            Assert.assertEquals(FastMath.cos(x) * FastMath.sin(x), f4.value(x), 1.0e-15);
+            Assert.assertEquals(FastMath.cos(x) * 5, f5.value(x), 1.0e-15);
+            Assert.assertEquals(FastMath.cos(x) / FastMath.sin(x), f6.value(x), 1.0e-15);
+        }
+
+    }
+
+    @Test
+    public void testCollector() throws Exception {
+
+        ComposableFunction f = BinaryFunction.POW.fix2ndArgument(2);
+        Assert.assertEquals(30, f.asCollector().value(new double[] { 1, 2, 3, 4 }), 1.0e-15);
+        Assert.assertEquals(33, f.asCollector(3).value(new double[] { 1, 2, 3, 4 }), 1.0e-15);
+        Assert.assertEquals(-30, f.asCollector(BinaryFunction.SUBTRACT).value(new double[] { 1, 2, 3, 4 }), 1.0e-15);
+        Assert.assertEquals(1152, f.asCollector(BinaryFunction.MULTIPLY, 2).value(new double[] { 1, 2, 3, 4 }), 1.0e-15);
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/analysis/Expm1Function.java b/src/test/java/org/apache/commons/math/analysis/Expm1Function.java
new file mode 100644
index 0000000..b421f4d
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/Expm1Function.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.util.FastMath;
+
+/**
+ * Auxiliary class for testing purposes.
+ *
+ * @version $Revision: 1037327 $ $Date: 2010-11-20 21:57:37 +0100 (sam. 20 nov. 2010) $
+ */
+public class Expm1Function implements DifferentiableUnivariateRealFunction {
+
+    public double value(double x) {
+        return FastMath.expm1(x);
+    }
+
+    public UnivariateRealFunction derivative() {
+        return new UnivariateRealFunction() {
+            public double value(double x) {
+                return FastMath.exp(x);
+            }
+        };
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/analysis/MonitoredFunction.java b/src/test/java/org/apache/commons/math/analysis/MonitoredFunction.java
new file mode 100644
index 0000000..44842b3
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/MonitoredFunction.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.analysis;
+
+import org.apache.commons.math.FunctionEvaluationException;
+/**
+ * Wrapper class for counting functions calls.
+ *
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ */
+public class MonitoredFunction implements UnivariateRealFunction {
+
+    public MonitoredFunction(UnivariateRealFunction f) {
+        callsCount = 0;
+        this.f = f;
+    }
+
+    public void setCallsCount(int callsCount) {
+        this.callsCount = callsCount;
+    }
+
+    public int getCallsCount() {
+        return callsCount;
+    }
+
+    public double value(double x) throws FunctionEvaluationException {
+        ++callsCount;
+        return f.value(x);
+    }
+
+    private int callsCount;
+    private UnivariateRealFunction f;
+
+}
diff --git a/src/test/java/org/apache/commons/math/analysis/QuinticFunction.java b/src/test/java/org/apache/commons/math/analysis/QuinticFunction.java
new file mode 100644
index 0000000..0f7b177
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/QuinticFunction.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;
+
+/**
+ * Auxiliary class for testing solvers.
+ *
+ * @version $Revision: 1037327 $ $Date: 2010-11-20 21:57:37 +0100 (sam. 20 nov. 2010) $
+ */
+public class QuinticFunction implements DifferentiableUnivariateRealFunction {
+
+    /* Evaluate quintic.
+     * @see org.apache.commons.math.UnivariateRealFunction#value(double)
+     */
+    public double value(double x) {
+        return (x-1)*(x-0.5)*x*(x+0.5)*(x+1);
+    }
+
+    public UnivariateRealFunction derivative() {
+        return new UnivariateRealFunction() {
+            public double value(double x) {
+                return (5*x*x-3.75)*x*x+0.25;
+            }
+        };
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/analysis/SinFunction.java b/src/test/java/org/apache/commons/math/analysis/SinFunction.java
new file mode 100644
index 0000000..32bf95a
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/SinFunction.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.analysis;
+
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Auxillary class for testing solvers.
+ *
+ * The function is extraordinarily well behaved around zero roots: it
+ * has an inflection point there (second order derivative is zero),
+ * which means linear approximation (Regula Falsi) will converge
+ * quadratically.
+ *
+ * @version $Revision: 1037327 $ $Date: 2010-11-20 21:57:37 +0100 (sam. 20 nov. 2010) $
+ */
+public class SinFunction implements DifferentiableUnivariateRealFunction {
+
+    /* Evaluate sinus fuction.
+     * @see org.apache.commons.math.UnivariateRealFunction#value(double)
+     */
+    public double value(double x) {
+        return FastMath.sin(x);
+    }
+
+    /* First derivative of sinus function
+     */
+    public UnivariateRealFunction derivative() {
+        return new UnivariateRealFunction() {
+            public double value(double x) {
+                return FastMath.cos(x);
+            }
+        };
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/analysis/SincFunction.java b/src/test/java/org/apache/commons/math/analysis/SincFunction.java
new file mode 100644
index 0000000..d84ece0
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/SincFunction.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.analysis;
+
+
+/**
+ * Auxiliary class for testing optimizers.
+ *
+ * @version $Revision$ $Date$
+ */
+public class SincFunction implements DifferentiableUnivariateRealFunction {
+    private static final double EPS = 1e-12;
+
+    /**
+     * @param x Argument.
+     * @return the value of this function at point {@code x}.
+     */
+    public double value(double x) {
+        return (Math.abs(x) < EPS ?
+                1 :
+                Math.sin(x) / x);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public UnivariateRealFunction derivative() {
+        return new UnivariateRealFunction() {
+            public double value(double x) {
+                return (Math.abs(x) < EPS ?
+                        0 :
+                        (x * Math.cos(x) - Math.sin(x)) / (x * x));
+            }
+        };
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/analysis/SumSincFunction.java b/src/test/java/org/apache/commons/math/analysis/SumSincFunction.java
new file mode 100644
index 0000000..707507c
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/SumSincFunction.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.analysis;
+
+import org.apache.commons.math.FunctionEvaluationException;
+
+/**
+ * Auxiliary class for testing optimizers.
+ *
+ * @version $Revision$ $Date$
+ */
+public class SumSincFunction implements DifferentiableMultivariateRealFunction {
+    private static final DifferentiableUnivariateRealFunction sinc = new SincFunction();
+    private static final UnivariateRealFunction sincDeriv = sinc.derivative();
+
+    /**
+     * Factor that will multiply each term of the sum.
+     */
+    private final double factor;
+
+    /**
+     * @param factor Factor that will multiply each term of the sum.
+     */
+    public SumSincFunction(double factor) {
+        this.factor = factor;
+    }
+
+    /**
+     * @param point Argument.
+     * @return the value of this function at point {@code x}.
+     */
+    public double value(double[] point) throws FunctionEvaluationException {
+        double sum = 0;
+        for (int i = 0, max = point.length; i < max; i++) {
+            final double x = point[i];
+            final double v = sinc.value(x);
+            sum += v;
+        }
+        return factor * sum;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public MultivariateRealFunction partialDerivative(final int k) {
+        return new MultivariateRealFunction() {
+            public double value(double[] point) throws FunctionEvaluationException {
+                return sincDeriv.value(point[k]);
+            }
+        };
+    }
+
+    /**                                                                            
+     * {@inheritDoc}
+     */
+    public MultivariateVectorialFunction gradient() {
+        return new MultivariateVectorialFunction() {
+            public double[] value(double[] point)
+                throws FunctionEvaluationException {
+                final int n = point.length;
+                final double[] r = new double[n];
+                for (int i = 0; i < n; i++) {
+                    final double x = point[i];
+                    r[i] = factor * sincDeriv.value(x);
+                }
+                return r;
+            }
+        };
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/analysis/integration/LegendreGaussIntegratorTest.java b/src/test/java/org/apache/commons/math/analysis/integration/LegendreGaussIntegratorTest.java
new file mode 100644
index 0000000..3145506
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/integration/LegendreGaussIntegratorTest.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.analysis.integration;
+
+import java.util.Random;
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.ConvergenceException;
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.analysis.QuinticFunction;
+import org.apache.commons.math.analysis.SinFunction;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.analysis.polynomials.PolynomialFunction;
+import org.apache.commons.math.util.FastMath;
+
+import junit.framework.*;
+
+public class LegendreGaussIntegratorTest
+extends TestCase {
+
+    public LegendreGaussIntegratorTest(String name) {
+        super(name);
+    }
+
+    public void testSinFunction() throws MathException {
+        UnivariateRealFunction f = new SinFunction();
+        UnivariateRealIntegrator integrator = new LegendreGaussIntegrator(5, 64);
+        integrator.setAbsoluteAccuracy(1.0e-10);
+        integrator.setRelativeAccuracy(1.0e-14);
+        integrator.setMinimalIterationCount(2);
+        integrator.setMaximalIterationCount(15);
+        double min, max, expected, result, tolerance;
+
+        min = 0; max = FastMath.PI; expected = 2;
+        tolerance = FastMath.max(integrator.getAbsoluteAccuracy(),
+                             FastMath.abs(expected * integrator.getRelativeAccuracy()));
+        result = integrator.integrate(f, min, max);
+        assertEquals(expected, result, tolerance);
+
+        min = -FastMath.PI/3; max = 0; expected = -0.5;
+        tolerance = FastMath.max(integrator.getAbsoluteAccuracy(),
+                FastMath.abs(expected * integrator.getRelativeAccuracy()));
+        result = integrator.integrate(f, min, max);
+        assertEquals(expected, result, tolerance);
+    }
+
+    public void testQuinticFunction() throws MathException {
+        UnivariateRealFunction f = new QuinticFunction();
+        UnivariateRealIntegrator integrator = new LegendreGaussIntegrator(3, 64);
+        double min, max, expected, result;
+
+        min = 0; max = 1; expected = -1.0/48;
+        result = integrator.integrate(f, min, max);
+        assertEquals(expected, result, 1.0e-16);
+
+        min = 0; max = 0.5; expected = 11.0/768;
+        result = integrator.integrate(f, min, max);
+        assertEquals(expected, result, 1.0e-16);
+
+        min = -1; max = 4; expected = 2048/3.0 - 78 + 1.0/48;
+        result = integrator.integrate(f, min, max);
+        assertEquals(expected, result, 1.0e-16);
+    }
+
+    public void testExactIntegration()
+        throws ConvergenceException, FunctionEvaluationException {
+        Random random = new Random(86343623467878363l);
+        for (int n = 2; n < 6; ++n) {
+            LegendreGaussIntegrator integrator =
+                new LegendreGaussIntegrator(n, 64);
+
+            // an n points Gauss-Legendre integrator integrates 2n-1 degree polynoms exactly
+            for (int degree = 0; degree <= 2 * n - 1; ++degree) {
+                for (int i = 0; i < 10; ++i) {
+                    double[] coeff = new double[degree + 1];
+                    for (int k = 0; k < coeff.length; ++k) {
+                        coeff[k] = 2 * random.nextDouble() - 1;
+                    }
+                    PolynomialFunction p = new PolynomialFunction(coeff);
+                    double result    = integrator.integrate(p, -5.0, 15.0);
+                    double reference = exactIntegration(p, -5.0, 15.0);
+                    assertEquals(n + " " + degree + " " + i, reference, result, 1.0e-12 * (1.0 + FastMath.abs(reference)));
+                }
+            }
+
+        }
+    }
+
+    private double exactIntegration(PolynomialFunction p, double a, double b) {
+        final double[] coeffs = p.getCoefficients();
+        double yb = coeffs[coeffs.length - 1] / coeffs.length;
+        double ya = yb;
+        for (int i = coeffs.length - 2; i >= 0; --i) {
+            yb = yb * b + coeffs[i] / (i + 1);
+            ya = ya * a + coeffs[i] / (i + 1);
+        }
+        return yb * b - ya * a;
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/analysis/integration/RombergIntegratorTest.java b/src/test/java/org/apache/commons/math/analysis/integration/RombergIntegratorTest.java
new file mode 100644
index 0000000..1174107
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/integration/RombergIntegratorTest.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.analysis.integration;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.analysis.QuinticFunction;
+import org.apache.commons.math.analysis.SinFunction;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.util.FastMath;
+
+import junit.framework.TestCase;
+
+/**
+ * Testcase for Romberg integrator.
+ * <p>
+ * Romberg algorithm is very fast for good behavior integrand. Test runs
+ * show that for a default relative accuracy of 1E-6, it generally takes
+ * takes less than 5 iterations for the integral to converge.
+ *
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ */
+public final class RombergIntegratorTest extends TestCase {
+
+    /**
+     * Test of integrator for the sine function.
+     */
+    public void testSinFunction() throws MathException {
+        UnivariateRealFunction f = new SinFunction();
+        UnivariateRealIntegrator integrator = new RombergIntegrator();
+        double min, max, expected, result, tolerance;
+
+        min = 0; max = FastMath.PI; expected = 2;
+        tolerance = FastMath.abs(expected * integrator.getRelativeAccuracy());
+        result = integrator.integrate(f, min, max);
+        assertEquals(expected, result, tolerance);
+
+        min = -FastMath.PI/3; max = 0; expected = -0.5;
+        tolerance = FastMath.abs(expected * integrator.getRelativeAccuracy());
+        result = integrator.integrate(f, min, max);
+        assertEquals(expected, result, tolerance);
+    }
+
+    /**
+     * Test of integrator for the quintic function.
+     */
+    public void testQuinticFunction() throws MathException {
+        UnivariateRealFunction f = new QuinticFunction();
+        UnivariateRealIntegrator integrator = new RombergIntegrator();
+        double min, max, expected, result, tolerance;
+
+        min = 0; max = 1; expected = -1.0/48;
+        tolerance = FastMath.abs(expected * integrator.getRelativeAccuracy());
+        result = integrator.integrate(f, min, max);
+        assertEquals(expected, result, tolerance);
+
+        min = 0; max = 0.5; expected = 11.0/768;
+        tolerance = FastMath.abs(expected * integrator.getRelativeAccuracy());
+        result = integrator.integrate(f, min, max);
+        assertEquals(expected, result, tolerance);
+
+        min = -1; max = 4; expected = 2048/3.0 - 78 + 1.0/48;
+        tolerance = FastMath.abs(expected * integrator.getRelativeAccuracy());
+        result = integrator.integrate(f, min, max);
+        assertEquals(expected, result, tolerance);
+    }
+
+    /**
+     * Test of parameters for the integrator.
+     */
+    public void testParameters() throws Exception {
+        UnivariateRealFunction f = new SinFunction();
+        UnivariateRealIntegrator integrator = new RombergIntegrator();
+
+        try {
+            // bad interval
+            integrator.integrate(f, 1, -1);
+            fail("Expecting IllegalArgumentException - bad interval");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            // bad iteration limits
+            integrator.setMinimalIterationCount(5);
+            integrator.setMaximalIterationCount(4);
+            integrator.integrate(f, -1, 1);
+            fail("Expecting IllegalArgumentException - bad iteration limits");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            // bad iteration limits
+            integrator.setMinimalIterationCount(10);
+            integrator.setMaximalIterationCount(50);
+            integrator.integrate(f, -1, 1);
+            fail("Expecting IllegalArgumentException - bad iteration limits");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/analysis/integration/SimpsonIntegratorTest.java b/src/test/java/org/apache/commons/math/analysis/integration/SimpsonIntegratorTest.java
new file mode 100644
index 0000000..a60915a
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/integration/SimpsonIntegratorTest.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.analysis.integration;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.analysis.QuinticFunction;
+import org.apache.commons.math.analysis.SinFunction;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.util.FastMath;
+
+import junit.framework.TestCase;
+
+/**
+ * Testcase for Simpson integrator.
+ * <p>
+ * Test runs show that for a default relative accuracy of 1E-6, it
+ * generally takes 5 to 10 iterations for the integral to converge.
+ *
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ */
+public final class SimpsonIntegratorTest extends TestCase {
+
+    /**
+     * Test of integrator for the sine function.
+     */
+    public void testSinFunction() throws MathException {
+        UnivariateRealFunction f = new SinFunction();
+        UnivariateRealIntegrator integrator = new SimpsonIntegrator();
+        double min, max, expected, result, tolerance;
+
+        min = 0; max = FastMath.PI; expected = 2;
+        tolerance = FastMath.abs(expected * integrator.getRelativeAccuracy());
+        result = integrator.integrate(f, min, max);
+        assertEquals(expected, result, tolerance);
+
+        min = -FastMath.PI/3; max = 0; expected = -0.5;
+        tolerance = FastMath.abs(expected * integrator.getRelativeAccuracy());
+        result = integrator.integrate(f, min, max);
+        assertEquals(expected, result, tolerance);
+    }
+
+    /**
+     * Test of integrator for the quintic function.
+     */
+    public void testQuinticFunction() throws MathException {
+        UnivariateRealFunction f = new QuinticFunction();
+        UnivariateRealIntegrator integrator = new SimpsonIntegrator();
+        double min, max, expected, result, tolerance;
+
+        min = 0; max = 1; expected = -1.0/48;
+        tolerance = FastMath.abs(expected * integrator.getRelativeAccuracy());
+        result = integrator.integrate(f, min, max);
+        assertEquals(expected, result, tolerance);
+
+        min = 0; max = 0.5; expected = 11.0/768;
+        tolerance = FastMath.abs(expected * integrator.getRelativeAccuracy());
+        result = integrator.integrate(f, min, max);
+        assertEquals(expected, result, tolerance);
+
+        min = -1; max = 4; expected = 2048/3.0 - 78 + 1.0/48;
+        tolerance = FastMath.abs(expected * integrator.getRelativeAccuracy());
+        result = integrator.integrate(f, min, max);
+        assertEquals(expected, result, tolerance);
+    }
+
+    /**
+     * Test of parameters for the integrator.
+     */
+    public void testParameters() throws Exception {
+        UnivariateRealFunction f = new SinFunction();
+        UnivariateRealIntegrator integrator = new SimpsonIntegrator();
+
+        try {
+            // bad interval
+            integrator.integrate(f, 1, -1);
+            fail("Expecting IllegalArgumentException - bad interval");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            // bad iteration limits
+            integrator.setMinimalIterationCount(5);
+            integrator.setMaximalIterationCount(4);
+            integrator.integrate(f, -1, 1);
+            fail("Expecting IllegalArgumentException - bad iteration limits");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            // bad iteration limits
+            integrator.setMinimalIterationCount(10);
+            integrator.setMaximalIterationCount(99);
+            integrator.integrate(f, -1, 1);
+            fail("Expecting IllegalArgumentException - bad iteration limits");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/analysis/integration/TrapezoidIntegratorTest.java b/src/test/java/org/apache/commons/math/analysis/integration/TrapezoidIntegratorTest.java
new file mode 100644
index 0000000..dbecce1
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/integration/TrapezoidIntegratorTest.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.analysis.integration;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.analysis.QuinticFunction;
+import org.apache.commons.math.analysis.SinFunction;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.util.FastMath;
+
+import junit.framework.TestCase;
+
+/**
+ * Testcase for trapezoid integrator.
+ * <p>
+ * Test runs show that for a default relative accuracy of 1E-6, it
+ * generally takes 10 to 15 iterations for the integral to converge.
+ *
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ */
+public final class TrapezoidIntegratorTest extends TestCase {
+
+    /**
+     * Test of integrator for the sine function.
+     */
+    public void testSinFunction() throws MathException {
+        UnivariateRealFunction f = new SinFunction();
+        UnivariateRealIntegrator integrator = new TrapezoidIntegrator();
+        double min, max, expected, result, tolerance;
+
+        min = 0; max = FastMath.PI; expected = 2;
+        tolerance = FastMath.abs(expected * integrator.getRelativeAccuracy());
+        result = integrator.integrate(f, min, max);
+        assertEquals(expected, result, tolerance);
+
+        min = -FastMath.PI/3; max = 0; expected = -0.5;
+        tolerance = FastMath.abs(expected * integrator.getRelativeAccuracy());
+        result = integrator.integrate(f, min, max);
+        assertEquals(expected, result, tolerance);
+    }
+
+    /**
+     * Test of integrator for the quintic function.
+     */
+    public void testQuinticFunction() throws MathException {
+        UnivariateRealFunction f = new QuinticFunction();
+        UnivariateRealIntegrator integrator = new TrapezoidIntegrator();
+        double min, max, expected, result, tolerance;
+
+        min = 0; max = 1; expected = -1.0/48;
+        tolerance = FastMath.abs(expected * integrator.getRelativeAccuracy());
+        result = integrator.integrate(f, min, max);
+        assertEquals(expected, result, tolerance);
+
+        min = 0; max = 0.5; expected = 11.0/768;
+        tolerance = FastMath.abs(expected * integrator.getRelativeAccuracy());
+        result = integrator.integrate(f, min, max);
+        assertEquals(expected, result, tolerance);
+
+        min = -1; max = 4; expected = 2048/3.0 - 78 + 1.0/48;
+        tolerance = FastMath.abs(expected * integrator.getRelativeAccuracy());
+        result = integrator.integrate(f, min, max);
+        assertEquals(expected, result, tolerance);
+    }
+
+    /**
+     * Test of parameters for the integrator.
+     */
+    public void testParameters() throws Exception {
+        UnivariateRealFunction f = new SinFunction();
+        UnivariateRealIntegrator integrator = new TrapezoidIntegrator();
+
+        try {
+            // bad interval
+            integrator.integrate(f, 1, -1);
+            fail("Expecting IllegalArgumentException - bad interval");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            // bad iteration limits
+            integrator.setMinimalIterationCount(5);
+            integrator.setMaximalIterationCount(4);
+            integrator.integrate(f, -1, 1);
+            fail("Expecting IllegalArgumentException - bad iteration limits");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            // bad iteration limits
+            integrator.setMinimalIterationCount(10);
+            integrator.setMaximalIterationCount(99);
+            integrator.integrate(f, -1, 1);
+            fail("Expecting IllegalArgumentException - bad iteration limits");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/analysis/interpolation/BicubicSplineInterpolatingFunctionTest.java b/src/test/java/org/apache/commons/math/analysis/interpolation/BicubicSplineInterpolatingFunctionTest.java
new file mode 100644
index 0000000..32ca91b
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/interpolation/BicubicSplineInterpolatingFunctionTest.java
@@ -0,0 +1,448 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.FunctionEvaluationException;
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.DimensionMismatchException;
+import org.apache.commons.math.analysis.BivariateRealFunction;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Testcase for the bicubic function.
+ * 
+ * @version $Revision: 821626 $ $Date: 2009-10-04 23:57:30 +0200 (Sun, 04 Oct 2009) $ 
+ */
+public final class BicubicSplineInterpolatingFunctionTest {
+    /**
+     * Test preconditions.
+     */
+    @Test
+    public void testPreconditions() throws MathException {
+        double[] xval = new double[] {3, 4, 5, 6.5};
+        double[] yval = new double[] {-4, -3, -1, 2.5};
+        double[][] zval = new double[xval.length][yval.length];
+
+        @SuppressWarnings("unused")
+        BivariateRealFunction bcf = new BicubicSplineInterpolatingFunction(xval, yval, zval,
+                                                                           zval, zval, zval);
+        
+        double[] wxval = new double[] {3, 2, 5, 6.5};
+        try {
+            bcf = new BicubicSplineInterpolatingFunction(wxval, yval, zval, zval, zval, zval);
+            Assert.fail("an exception should have been thrown");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+        double[] wyval = new double[] {-4, -1, -1, 2.5};
+        try {
+            bcf = new BicubicSplineInterpolatingFunction(xval, wyval, zval, zval, zval, zval);
+            Assert.fail("an exception should have been thrown");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+        double[][] wzval = new double[xval.length][yval.length - 1];
+        try {
+            bcf = new BicubicSplineInterpolatingFunction(xval, yval, wzval, zval, zval, zval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        try {
+            bcf = new BicubicSplineInterpolatingFunction(xval, yval, zval, wzval, zval, zval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        try {
+            bcf = new BicubicSplineInterpolatingFunction(xval, yval, zval, zval, wzval, zval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        try {
+            bcf = new BicubicSplineInterpolatingFunction(xval, yval, zval, zval, zval, wzval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+
+        wzval = new double[xval.length - 1][yval.length];
+        try {
+            bcf = new BicubicSplineInterpolatingFunction(xval, yval, wzval, zval, zval, zval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        try {
+            bcf = new BicubicSplineInterpolatingFunction(xval, yval, zval, wzval, zval, zval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        try {
+            bcf = new BicubicSplineInterpolatingFunction(xval, yval, zval, zval, wzval, zval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        try {
+            bcf = new BicubicSplineInterpolatingFunction(xval, yval, zval, zval, zval, wzval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        Assert.assertNotNull(bcf); // Avoid Findbugs "dead store" warning
+    }
+
+    /**
+     * Test for a plane.
+     * <p>
+     * z = 2 x - 3 y + 5
+     */
+    @Test
+    public void testPlane() throws MathException {
+        double[] xval = new double[] {3, 4, 5, 6.5};
+        double[] yval = new double[] {-4, -3, -1, 2, 2.5};
+        // Function values
+        BivariateRealFunction f = new BivariateRealFunction() {
+                public double value(double x, double y) {
+                    return 2 * x - 3 * y + 5;
+                }
+            };
+        double[][] zval = new double[xval.length][yval.length];
+        for (int i = 0; i < xval.length; i++) {
+            for (int j = 0; j < yval.length; j++) {
+                zval[i][j] = f.value(xval[i], yval[j]);
+            }
+        }
+        // Partial derivatives with respect to x
+        double[][] dZdX = new double[xval.length][yval.length];
+        for (int i = 0; i < xval.length; i++) {
+            for (int j = 0; j < yval.length; j++) {
+                dZdX[i][j] = 2;
+            }
+        }
+        // Partial derivatives with respect to y
+        double[][] dZdY = new double[xval.length][yval.length];
+        for (int i = 0; i < xval.length; i++) {
+            for (int j = 0; j < yval.length; j++) {
+                dZdY[i][j] = -3;
+            }
+        }
+        // Partial cross-derivatives
+        double[][] dZdXdY = new double[xval.length][yval.length];
+        for (int i = 0; i < xval.length; i++) {
+            for (int j = 0; j < yval.length; j++) {
+                dZdXdY[i][j] = 0;
+            }
+        }
+
+        BivariateRealFunction bcf = new BicubicSplineInterpolatingFunction(xval, yval, zval,
+                                                                           dZdX, dZdY, dZdXdY);
+        double x, y;
+        double expected, result;
+
+        x = 4;
+        y = -3;
+        expected = f.value(x, y);
+        result = bcf.value(x, y);
+        Assert.assertEquals("On sample point",
+                            expected, result, 1e-15);
+
+        x = 4.5;
+        y = -1.5;
+        expected = f.value(x, y);
+        result = bcf.value(x, y);
+        Assert.assertEquals("Half-way between sample points (middle of the patch)",
+                            expected, result, 0.3);
+
+        x = 3.5;
+        y = -3.5;
+        expected = f.value(x, y);
+        result = bcf.value(x, y);
+        Assert.assertEquals("Half-way between sample points (border of the patch)",
+                            expected, result, 0.3);
+    }
+
+    /**
+     * Test for a paraboloid.
+     * <p>
+     * z = 2 x<sup>2</sup> - 3 y<sup>2</sup> + 4 x y - 5
+     */
+    @Test
+    public void testParaboloid() throws MathException {
+        double[] xval = new double[] {3, 4, 5, 6.5};
+        double[] yval = new double[] {-4, -3, -1, 2, 2.5};
+        // Function values
+        BivariateRealFunction f = new BivariateRealFunction() {
+                public double value(double x, double y) {
+                    return 2 * x * x - 3 * y * y + 4 * x * y - 5;
+                }
+            };
+        double[][] zval = new double[xval.length][yval.length];
+        for (int i = 0; i < xval.length; i++) {
+            for (int j = 0; j < yval.length; j++) {
+                zval[i][j] = f.value(xval[i], yval[j]);
+            }
+        }
+        // Partial derivatives with respect to x
+        double[][] dZdX = new double[xval.length][yval.length];
+        BivariateRealFunction dfdX = new BivariateRealFunction() {
+                public double value(double x, double y) {
+                    return 4 * (x + y);
+                }
+            };
+        for (int i = 0; i < xval.length; i++) {
+            for (int j = 0; j < yval.length; j++) {
+                dZdX[i][j] = dfdX.value(xval[i], yval[j]);
+            }
+        }
+        // Partial derivatives with respect to y
+        double[][] dZdY = new double[xval.length][yval.length];
+        BivariateRealFunction dfdY = new BivariateRealFunction() {
+                public double value(double x, double y) {
+                    return 4 * x - 6 * y;
+                }
+            };
+        for (int i = 0; i < xval.length; i++) {
+            for (int j = 0; j < yval.length; j++) {
+                dZdY[i][j] = dfdY.value(xval[i], yval[j]);
+            }
+        }
+        // Partial cross-derivatives
+        double[][] dZdXdY = new double[xval.length][yval.length];
+        for (int i = 0; i < xval.length; i++) {
+            for (int j = 0; j < yval.length; j++) {
+                dZdXdY[i][j] = 4;
+            }
+        }
+
+        BivariateRealFunction bcf = new BicubicSplineInterpolatingFunction(xval, yval, zval,
+                                                                           dZdX, dZdY, dZdXdY);
+        double x, y;
+        double expected, result;
+        
+        x = 4;
+        y = -3;
+        expected = f.value(x, y);
+        result = bcf.value(x, y);
+        Assert.assertEquals("On sample point",
+                            expected, result, 1e-15);
+
+        x = 4.5;
+        y = -1.5;
+        expected = f.value(x, y);
+        result = bcf.value(x, y);
+        Assert.assertEquals("Half-way between sample points (middle of the patch)",
+                            expected, result, 2);
+
+        x = 3.5;
+        y = -3.5;
+        expected = f.value(x, y);
+        result = bcf.value(x, y);
+        Assert.assertEquals("Half-way between sample points (border of the patch)",
+                            expected, result, 2);
+    }
+
+    /**
+     * Test for partial derivatives of {@link BicubicSplineFunction}.
+     * <p>
+     * f(x, y) = Σ<sub>i</sub>Σ<sub>j</sub> (i+1) (j+2) x<sup>i</sup> y<sup>j</sup>
+     */
+    @Test
+    public void testSplinePartialDerivatives() throws FunctionEvaluationException {
+        final int N = 4;
+        final double[] coeff = new double[16];
+
+        for (int i = 0; i < N; i++) {
+            for (int j = 0; j < N; j++) {
+                coeff[i + N * j] = (i + 1) * (j + 2);
+            }
+        }
+
+        final BicubicSplineFunction f = new BicubicSplineFunction(coeff);
+        BivariateRealFunction derivative;
+        final double x = 0.435;
+        final double y = 0.776;
+        final double tol = 1e-13;
+
+        derivative = new BivariateRealFunction() {
+                public double value(double x, double y) {
+                    final double x2 = x * x;
+                    final double y2 = y * y;
+                    final double y3 = y2 * y;
+                    final double yFactor = 2 + 3 * y + 4 * y2 + 5 * y3;
+                    return yFactor * (2 + 6 * x + 12 * x2);
+                }
+            };
+        Assert.assertEquals("dFdX", derivative.value(x, y),
+                            f.partialDerivativeX().value(x, y), tol);
+        
+        derivative = new BivariateRealFunction() {
+                public double value(double x, double y) {
+                    final double x2 = x * x;
+                    final double x3 = x2 * x;
+                    final double y2 = y * y;
+                    final double xFactor = 1 + 2 * x + 3 * x2 + 4 * x3;
+                    return xFactor * (3 + 8 * y + 15 * y2);
+                }
+            };
+        Assert.assertEquals("dFdY", derivative.value(x, y),
+                            f.partialDerivativeY().value(x, y), tol);
+
+        derivative = new BivariateRealFunction() {
+                public double value(double x, double y) {
+                    final double y2 = y * y;
+                    final double y3 = y2 * y;
+                    final double yFactor = 2 + 3 * y + 4 * y2 + 5 * y3;
+                    return yFactor * (6 + 24 * x);
+                }
+            };
+        Assert.assertEquals("d2FdX2", derivative.value(x, y),
+                            f.partialDerivativeXX().value(x, y), tol);
+
+        derivative = new BivariateRealFunction() {
+                public double value(double x, double y) {
+                    final double x2 = x * x;
+                    final double x3 = x2 * x;
+                    final double xFactor = 1 + 2 * x + 3 * x2 + 4 * x3;
+                    return xFactor * (8 + 30 * y);
+                }
+            };
+        Assert.assertEquals("d2FdY2", derivative.value(x, y),
+                            f.partialDerivativeYY().value(x, y), tol);
+
+        derivative = new BivariateRealFunction() {
+                public double value(double x, double y) {
+                    final double x2 = x * x;
+                    final double y2 = y * y;
+                    final double yFactor = 3 + 8 * y + 15 * y2;
+                    return yFactor * (2 + 6 * x + 12 * x2);
+                }
+            };
+        Assert.assertEquals("d2FdXdY", derivative.value(x, y),
+                            f.partialDerivativeXY().value(x, y), tol);
+    }
+
+    /**
+     * Test that the partial derivatives computed from a
+     * {@link BicubicSplineInterpolatingFunction} match the input data.
+     * <p>
+     * f(x, y) = 5
+     *           - 3 x + 2 y
+     *           - x y + 2 x<sup>2</sup> - 3 y<sup>2</sup>
+     *           + 4 x<sup>2</sup> y - x y<sup>2</sup> - 3 x<sup>3</sup> + y<sup>3</sup>
+     */
+    @Test
+    public void testMatchingPartialDerivatives() throws MathException {
+        final int sz = 21;
+        double[] val = new double[sz];
+        // Coordinate values
+        final double delta = 1d / (sz - 1);
+        for (int i = 0; i < sz; i++) {
+            val[i] = i * delta;
+        }
+        // Function values
+        BivariateRealFunction f = new BivariateRealFunction() {
+                public double value(double x, double y) {
+                    final double x2 = x * x;
+                    final double x3 = x2 * x;
+                    final double y2 = y * y;
+                    final double y3 = y2 * y;
+
+                    return 5
+                        - 3 * x + 2 * y
+                        - x * y + 2 * x2 - 3 * y2
+                        + 4 * x2 * y - x * y2 - 3 * x3 + y3;
+                }
+            };
+        double[][] fval = new double[sz][sz];
+        for (int i = 0; i < sz; i++) {
+            for (int j = 0; j < sz; j++) {
+                fval[i][j] = f.value(val[i], val[j]);
+            }
+        }
+        // Partial derivatives with respect to x
+        double[][] dFdX = new double[sz][sz];
+        BivariateRealFunction dfdX = new BivariateRealFunction() {
+                public double value(double x, double y) {
+                    final double x2 = x * x;
+                    final double y2 = y * y;                    
+                    return - 3 - y + 4 * x + 8 * x * y - y2 - 9 * x2;
+                }
+            };
+        for (int i = 0; i < sz; i++) {
+            for (int j = 0; j < sz; j++) {
+                dFdX[i][j] = dfdX.value(val[i], val[j]);
+            }
+        }
+        // Partial derivatives with respect to y
+        double[][] dFdY = new double[sz][sz];
+        BivariateRealFunction dfdY = new BivariateRealFunction() {
+                public double value(double x, double y) {
+                    final double x2 = x * x;
+                    final double y2 = y * y;                    
+                    return 2 - x - 6 * y + 4 * x2 - 2 * x * y + 3 * y2;
+                }
+            };
+        for (int i = 0; i < sz; i++) {
+            for (int j = 0; j < sz; j++) {
+                dFdY[i][j] = dfdY.value(val[i], val[j]);
+            }
+        }
+        // Partial cross-derivatives
+        double[][] d2FdXdY = new double[sz][sz];
+        BivariateRealFunction d2fdXdY = new BivariateRealFunction() {
+                public double value(double x, double y) {
+                    return -1 + 8 * x - 2 * y;
+                }
+            };
+        for (int i = 0; i < sz; i++) {
+            for (int j = 0; j < sz; j++) {
+                d2FdXdY[i][j] = d2fdXdY.value(val[i], val[j]);
+            }
+        }
+
+        BicubicSplineInterpolatingFunction bcf
+            = new BicubicSplineInterpolatingFunction(val, val, fval, dFdX, dFdY, d2FdXdY);
+
+        double x, y;
+        double expected, result;
+
+        final double tol = 1e-12;
+        for (int i = 0; i < sz; i++) {
+            x = val[i];
+            for (int j = 0; j < sz; j++) {
+                y = val[j];
+                
+                expected = dfdX.value(x, y);
+                result = bcf.partialDerivativeX(x, y);
+                Assert.assertEquals(x + " " + y + " dFdX", expected, result, tol);
+
+                expected = dfdY.value(x, y);
+                result = bcf.partialDerivativeY(x, y);
+                Assert.assertEquals(x + " " + y + " dFdY", expected, result, tol);
+                
+                expected = d2fdXdY.value(x, y);
+                result = bcf.partialDerivativeXY(x, y);
+                Assert.assertEquals(x + " " + y + " d2FdXdY", expected, result, tol);
+            }
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/analysis/interpolation/BicubicSplineInterpolatorTest.java b/src/test/java/org/apache/commons/math/analysis/interpolation/BicubicSplineInterpolatorTest.java
new file mode 100644
index 0000000..de807a4
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/interpolation/BicubicSplineInterpolatorTest.java
@@ -0,0 +1,170 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.analysis.interpolation;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.DimensionMismatchException;
+import org.apache.commons.math.analysis.BivariateRealFunction;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Testcase for the bicubic interpolator.
+ * 
+ * @version $Revision: 821626 $ $Date: 2009-10-04 23:57:30 +0200 (Sun, 04 Oct 2009) $ 
+ */
+public final class BicubicSplineInterpolatorTest {
+    /**
+     * Test preconditions.
+     */
+    @Test
+    public void testPreconditions() throws MathException {
+        double[] xval = new double[] {3, 4, 5, 6.5};
+        double[] yval = new double[] {-4, -3, -1, 2.5};
+        double[][] zval = new double[xval.length][yval.length];
+
+        BivariateRealGridInterpolator interpolator = new BicubicSplineInterpolator();
+        
+        @SuppressWarnings("unused")
+        BivariateRealFunction p = interpolator.interpolate(xval, yval, zval);
+        
+        double[] wxval = new double[] {3, 2, 5, 6.5};
+        try {
+            p = interpolator.interpolate(wxval, yval, zval);
+            Assert.fail("an exception should have been thrown");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        double[] wyval = new double[] {-4, -3, -1, -1};
+        try {
+            p = interpolator.interpolate(xval, wyval, zval);
+            Assert.fail("an exception should have been thrown");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        double[][] wzval = new double[xval.length][yval.length + 1];
+        try {
+            p = interpolator.interpolate(xval, yval, wzval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        wzval = new double[xval.length - 1][yval.length];
+        try {
+            p = interpolator.interpolate(xval, yval, wzval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+    }
+
+    /**
+     * Test of interpolator for a plane.
+     * <p>
+     * z = 2 x - 3 y + 5
+     */
+    @Test
+    public void testPlane() throws MathException {
+        BivariateRealFunction f = new BivariateRealFunction() {
+                public double value(double x, double y) {
+                    return 2 * x - 3 * y + 5;
+                }
+            };
+
+        BivariateRealGridInterpolator interpolator = new BicubicSplineInterpolator();
+
+        double[] xval = new double[] {3, 4, 5, 6.5};
+        double[] yval = new double[] {-4, -3, -1, 2, 2.5};
+        double[][] zval = new double[xval.length][yval.length];
+        for (int i = 0; i < xval.length; i++) {
+            for (int j = 0; j < yval.length; j++) {
+                zval[i][j] = f.value(xval[i], yval[j]);
+            }
+        }
+
+        BivariateRealFunction p = interpolator.interpolate(xval, yval, zval);
+        double x, y;
+        double expected, result;
+        
+        x = 4;
+        y = -3;
+        expected = f.value(x, y);
+        result = p.value(x, y);
+        Assert.assertEquals("On sample point", expected, result, 1e-15);
+
+        x = 4.5;
+        y = -1.5;
+        expected = f.value(x, y);
+        result = p.value(x, y);
+        Assert.assertEquals("half-way between sample points (middle of the patch)", expected, result, 0.3);
+
+        x = 3.5;
+        y = -3.5;
+        expected = f.value(x, y);
+        result = p.value(x, y);
+        Assert.assertEquals("half-way between sample points (border of the patch)", expected, result, 0.3);
+    }
+
+    /**
+     * Test of interpolator for a paraboloid.
+     * <p>
+     * z = 2 x<sup>2</sup> - 3 y<sup>2</sup> + 4 x y - 5
+     */
+    @Test
+    public void testParaboloid() throws MathException {
+        BivariateRealFunction f = new BivariateRealFunction() {
+                public double value(double x, double y) {
+                    return 2 * x * x - 3 * y * y + 4 * x * y - 5;
+                }
+            };
+
+        BivariateRealGridInterpolator interpolator = new BicubicSplineInterpolator();
+
+        double[] xval = new double[] {3, 4, 5, 6.5};
+        double[] yval = new double[] {-4, -3, -2, -1, 0.5, 2.5};
+        double[][] zval = new double[xval.length][yval.length];
+        for (int i = 0; i < xval.length; i++) {
+            for (int j = 0; j < yval.length; j++) {
+                zval[i][j] = f.value(xval[i], yval[j]);
+            }
+        }
+
+        BivariateRealFunction p = interpolator.interpolate(xval, yval, zval);
+        double x, y;
+        double expected, result;
+        
+        x = 5;
+        y = 0.5;
+        expected = f.value(x, y);
+        result = p.value(x, y);
+        Assert.assertEquals("On sample point", expected, result, 1e-13);
+
+        x = 4.5;
+        y = -1.5;
+        expected = f.value(x, y);
+        result = p.value(x, y);
+        Assert.assertEquals("half-way between sample points (middle of the patch)", expected, result, 0.2);
+
+        x = 3.5;
+        y = -3.5;
+        expected = f.value(x, y);
+        result = p.value(x, y);
+        Assert.assertEquals("half-way between sample points (border of the patch)", expected, result, 0.2);
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/analysis/interpolation/DividedDifferenceInterpolatorTest.java b/src/test/java/org/apache/commons/math/analysis/interpolation/DividedDifferenceInterpolatorTest.java
new file mode 100644
index 0000000..3054cfb
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/interpolation/DividedDifferenceInterpolatorTest.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.MathException;
+import org.apache.commons.math.analysis.Expm1Function;
+import org.apache.commons.math.analysis.SinFunction;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.util.FastMath;
+
+import junit.framework.TestCase;
+
+/**
+ * Testcase for Divided Difference interpolator.
+ * <p>
+ * The error of polynomial interpolation is
+ *     f(z) - p(z) = f^(n)(zeta) * (z-x[0])(z-x[1])...(z-x[n-1]) / n!
+ * where f^(n) is the n-th derivative of the approximated function and
+ * zeta is some point in the interval determined by x[] and z.
+ * <p>
+ * Since zeta is unknown, f^(n)(zeta) cannot be calculated. But we can bound
+ * it and use the absolute value upper bound for estimates. For reference,
+ * see <b>Introduction to Numerical Analysis</b>, ISBN 038795452X, chapter 2.
+ *
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ */
+public final class DividedDifferenceInterpolatorTest extends TestCase {
+
+    /**
+     * Test of interpolator for the sine function.
+     * <p>
+     * |sin^(n)(zeta)| <= 1.0, zeta in [0, 2*PI]
+     */
+    public void testSinFunction() throws MathException {
+        UnivariateRealFunction f = new SinFunction();
+        UnivariateRealInterpolator interpolator = new DividedDifferenceInterpolator();
+        double x[], y[], z, expected, result, tolerance;
+
+        // 6 interpolating points on interval [0, 2*PI]
+        int n = 6;
+        double min = 0.0, max = 2 * FastMath.PI;
+        x = new double[n];
+        y = new double[n];
+        for (int i = 0; i < n; i++) {
+            x[i] = min + i * (max - min) / n;
+            y[i] = f.value(x[i]);
+        }
+        double derivativebound = 1.0;
+        UnivariateRealFunction p = interpolator.interpolate(x, y);
+
+        z = FastMath.PI / 4; expected = f.value(z); result = p.value(z);
+        tolerance = FastMath.abs(derivativebound * partialerror(x, z));
+        assertEquals(expected, result, tolerance);
+
+        z = FastMath.PI * 1.5; expected = f.value(z); result = p.value(z);
+        tolerance = FastMath.abs(derivativebound * partialerror(x, z));
+        assertEquals(expected, result, tolerance);
+    }
+
+    /**
+     * Test of interpolator for the exponential function.
+     * <p>
+     * |expm1^(n)(zeta)| <= e, zeta in [-1, 1]
+     */
+    public void testExpm1Function() throws MathException {
+        UnivariateRealFunction f = new Expm1Function();
+        UnivariateRealInterpolator interpolator = new DividedDifferenceInterpolator();
+        double x[], y[], z, expected, result, tolerance;
+
+        // 5 interpolating points on interval [-1, 1]
+        int n = 5;
+        double min = -1.0, max = 1.0;
+        x = new double[n];
+        y = new double[n];
+        for (int i = 0; i < n; i++) {
+            x[i] = min + i * (max - min) / n;
+            y[i] = f.value(x[i]);
+        }
+        double derivativebound = FastMath.E;
+        UnivariateRealFunction p = interpolator.interpolate(x, y);
+
+        z = 0.0; expected = f.value(z); result = p.value(z);
+        tolerance = FastMath.abs(derivativebound * partialerror(x, z));
+        assertEquals(expected, result, tolerance);
+
+        z = 0.5; expected = f.value(z); result = p.value(z);
+        tolerance = FastMath.abs(derivativebound * partialerror(x, z));
+        assertEquals(expected, result, tolerance);
+
+        z = -0.5; expected = f.value(z); result = p.value(z);
+        tolerance = FastMath.abs(derivativebound * partialerror(x, z));
+        assertEquals(expected, result, tolerance);
+    }
+
+    /**
+     * Test of parameters for the interpolator.
+     */
+    public void testParameters() throws Exception {
+        UnivariateRealInterpolator interpolator = new DividedDifferenceInterpolator();
+
+        try {
+            // bad abscissas array
+            double x[] = { 1.0, 2.0, 2.0, 4.0 };
+            double y[] = { 0.0, 4.0, 4.0, 2.5 };
+            UnivariateRealFunction p = interpolator.interpolate(x, y);
+            p.value(0.0);
+            fail("Expecting MathException - bad abscissas array");
+        } catch (MathException ex) {
+            // expected
+        }
+    }
+
+    /**
+     * Returns the partial error term (z-x[0])(z-x[1])...(z-x[n-1])/n!
+     */
+    protected double partialerror(double x[], double z) throws
+        IllegalArgumentException {
+
+        if (x.length < 1) {
+            throw new IllegalArgumentException
+                ("Interpolation array cannot be empty.");
+        }
+        double out = 1;
+        for (int i = 0; i < x.length; i++) {
+            out *= (z - x[i]) / (i + 1);
+        }
+        return out;
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/analysis/interpolation/LinearInterpolatorTest.java b/src/test/java/org/apache/commons/math/analysis/interpolation/LinearInterpolatorTest.java
new file mode 100644
index 0000000..19c96e3
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/interpolation/LinearInterpolatorTest.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.analysis.interpolation;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.exception.NonMonotonousSequenceException;
+import org.apache.commons.math.exception.DimensionMismatchException;
+import org.apache.commons.math.exception.NumberIsTooSmallException;
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.analysis.polynomials.PolynomialFunction;
+import org.apache.commons.math.analysis.polynomials.PolynomialSplineFunction;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Test the LinearInterpolator.
+ */
+public class LinearInterpolatorTest {
+
+    /** error tolerance for spline interpolator value at knot points */
+    protected double knotTolerance = 1E-12;
+
+    /** error tolerance for interpolating polynomial coefficients */
+    protected double coefficientTolerance = 1E-6;
+
+    /** error tolerance for interpolated values */
+    protected double interpolationTolerance = 1E-12;
+
+    @Test
+    public void testInterpolateLinearDegenerateTwoSegment()
+        throws Exception {
+        double x[] = { 0.0, 0.5, 1.0 };
+        double y[] = { 0.0, 0.5, 1.0 };
+        UnivariateRealInterpolator i = new LinearInterpolator();
+        UnivariateRealFunction f = i.interpolate(x, y);
+        verifyInterpolation(f, x, y);
+
+        // Verify coefficients using analytical values
+        PolynomialFunction polynomials[] = ((PolynomialSplineFunction) f).getPolynomials();
+        double target[] = {y[0], 1d};
+        TestUtils.assertEquals(polynomials[0].getCoefficients(), target, coefficientTolerance);
+        target = new double[]{y[1], 1d};
+        TestUtils.assertEquals(polynomials[1].getCoefficients(), target, coefficientTolerance);
+
+        // Check interpolation
+        Assert.assertEquals(0.0,f.value(0.0), interpolationTolerance);
+        Assert.assertEquals(0.4,f.value(0.4), interpolationTolerance);
+        Assert.assertEquals(1.0,f.value(1.0), interpolationTolerance);
+    }
+
+    @Test
+    public void testInterpolateLinearDegenerateThreeSegment()
+        throws Exception {
+        double x[] = { 0.0, 0.5, 1.0, 1.5 };
+        double y[] = { 0.0, 0.5, 1.0, 1.5 };
+        UnivariateRealInterpolator i = new LinearInterpolator();
+        UnivariateRealFunction f = i.interpolate(x, y);
+        verifyInterpolation(f, x, y);
+
+        // Verify coefficients using analytical values
+        PolynomialFunction polynomials[] = ((PolynomialSplineFunction) f).getPolynomials();
+        double target[] = {y[0], 1d};
+        TestUtils.assertEquals(polynomials[0].getCoefficients(), target, coefficientTolerance);
+        target = new double[]{y[1], 1d};
+        TestUtils.assertEquals(polynomials[1].getCoefficients(), target, coefficientTolerance);
+        target = new double[]{y[2], 1d};
+        TestUtils.assertEquals(polynomials[2].getCoefficients(), target, coefficientTolerance);
+
+        // Check interpolation
+        Assert.assertEquals(0,f.value(0), interpolationTolerance);
+        Assert.assertEquals(1.4,f.value(1.4), interpolationTolerance);
+        Assert.assertEquals(1.5,f.value(1.5), interpolationTolerance);
+    }
+
+    @Test
+    public void testInterpolateLinear() throws Exception {
+        double x[] = { 0.0, 0.5, 1.0 };
+        double y[] = { 0.0, 0.5, 0.0 };
+        UnivariateRealInterpolator i = new LinearInterpolator();
+        UnivariateRealFunction f = i.interpolate(x, y);
+        verifyInterpolation(f, x, y);
+
+        // Verify coefficients using analytical values
+        PolynomialFunction polynomials[] = ((PolynomialSplineFunction) f).getPolynomials();
+        double target[] = {y[0], 1d};
+        TestUtils.assertEquals(polynomials[0].getCoefficients(), target, coefficientTolerance);
+        target = new double[]{y[1], -1d};
+        TestUtils.assertEquals(polynomials[1].getCoefficients(), target, coefficientTolerance);
+    }
+
+    @Test
+    public void testIllegalArguments() throws MathException {
+        // Data set arrays of different size.
+        UnivariateRealInterpolator i = new LinearInterpolator();
+        try {
+            double xval[] = { 0.0, 1.0 };
+            double yval[] = { 0.0, 1.0, 2.0 };
+            i.interpolate(xval, yval);
+            Assert.fail("Failed to detect data set array with different sizes.");
+        } catch (DimensionMismatchException iae) {
+            // Expected.
+        }
+        // X values not sorted.
+        try {
+            double xval[] = { 0.0, 1.0, 0.5 };
+            double yval[] = { 0.0, 1.0, 2.0 };
+            i.interpolate(xval, yval);
+            Assert.fail("Failed to detect unsorted arguments.");
+        } catch (NonMonotonousSequenceException iae) {
+            // Expected.
+        }
+        // Not enough data to interpolate.
+        try {
+            double xval[] = { 0.0 };
+            double yval[] = { 0.0 };
+            i.interpolate(xval, yval);
+            Assert.fail("Failed to detect unsorted arguments.");
+        } catch (NumberIsTooSmallException iae) {
+            // Expected.
+        }
+    }
+
+    /**
+     * verifies that f(x[i]) = y[i] for i = 0..n-1 where n is common length.
+     */
+    protected void verifyInterpolation(UnivariateRealFunction f, double x[], double y[])
+        throws Exception{
+        for (int i = 0; i < x.length; i++) {
+            Assert.assertEquals(f.value(x[i]), y[i], knotTolerance);
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/analysis/interpolation/LoessInterpolatorTest.java b/src/test/java/org/apache/commons/math/analysis/interpolation/LoessInterpolatorTest.java
new file mode 100644
index 0000000..1fea733
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/interpolation/LoessInterpolatorTest.java
@@ -0,0 +1,255 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.util.FastMath;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Test of the LoessInterpolator class.
+ */
+public class LoessInterpolatorTest {
+
+    @Test
+    public void testOnOnePoint() throws MathException {
+        double[] xval = {0.5};
+        double[] yval = {0.7};
+        double[] res = new LoessInterpolator().smooth(xval, yval);
+        Assert.assertEquals(1, res.length);
+        Assert.assertEquals(0.7, res[0], 0.0);
+    }
+
+    @Test
+    public void testOnTwoPoints() throws MathException {
+        double[] xval = {0.5, 0.6};
+        double[] yval = {0.7, 0.8};
+        double[] res = new LoessInterpolator().smooth(xval, yval);
+        Assert.assertEquals(2, res.length);
+        Assert.assertEquals(0.7, res[0], 0.0);
+        Assert.assertEquals(0.8, res[1], 0.0);
+    }
+
+    @Test
+    public void testOnStraightLine() throws MathException {
+        double[] xval = {1,2,3,4,5};
+        double[] yval = {2,4,6,8,10};
+        LoessInterpolator li = new LoessInterpolator(0.6, 2, 1e-12);
+        double[] res = li.smooth(xval, yval);
+        Assert.assertEquals(5, res.length);
+        for(int i = 0; i < 5; ++i) {
+            Assert.assertEquals(yval[i], res[i], 1e-8);
+        }
+    }
+
+    @Test
+    public void testOnDistortedSine() throws MathException {
+        int numPoints = 100;
+        double[] xval = new double[numPoints];
+        double[] yval = new double[numPoints];
+        double xnoise = 0.1;
+        double ynoise = 0.2;
+
+        generateSineData(xval, yval, xnoise, ynoise);
+
+        LoessInterpolator li = new LoessInterpolator(0.3, 4, 1e-12);
+
+        double[] res = li.smooth(xval, yval);
+
+        // Check that the resulting curve differs from
+        // the "real" sine less than the jittered one
+
+        double noisyResidualSum = 0;
+        double fitResidualSum = 0;
+
+        for(int i = 0; i < numPoints; ++i) {
+            double expected = FastMath.sin(xval[i]);
+            double noisy = yval[i];
+            double fit = res[i];
+
+            noisyResidualSum += FastMath.pow(noisy - expected, 2);
+            fitResidualSum += FastMath.pow(fit - expected, 2);
+        }
+
+        Assert.assertTrue(fitResidualSum < noisyResidualSum);
+    }
+
+    @Test
+    public void testIncreasingBandwidthIncreasesSmoothness() throws MathException {
+        int numPoints = 100;
+        double[] xval = new double[numPoints];
+        double[] yval = new double[numPoints];
+        double xnoise = 0.1;
+        double ynoise = 0.1;
+
+        generateSineData(xval, yval, xnoise, ynoise);
+
+        // Check that variance decreases as bandwidth increases
+
+        double[] bandwidths = {0.1, 0.5, 1.0};
+        double[] variances = new double[bandwidths.length];
+        for (int i = 0; i < bandwidths.length; i++) {
+            double bw = bandwidths[i];
+
+            LoessInterpolator li = new LoessInterpolator(bw, 4, 1e-12);
+
+            double[] res = li.smooth(xval, yval);
+
+            for (int j = 1; j < res.length; ++j) {
+                variances[i] += FastMath.pow(res[j] - res[j-1], 2);
+            }
+        }
+
+        for(int i = 1; i < variances.length; ++i) {
+            Assert.assertTrue(variances[i] < variances[i-1]);
+        }
+    }
+
+    @Test
+    public void testIncreasingRobustnessItersIncreasesSmoothnessWithOutliers() throws MathException {
+        int numPoints = 100;
+        double[] xval = new double[numPoints];
+        double[] yval = new double[numPoints];
+        double xnoise = 0.1;
+        double ynoise = 0.1;
+
+        generateSineData(xval, yval, xnoise, ynoise);
+
+        // Introduce a couple of outliers
+        yval[numPoints/3] *= 100;
+        yval[2 * numPoints/3] *= -100;
+
+        // Check that variance decreases as the number of robustness
+        // iterations increases
+
+        double[] variances = new double[4];
+        for (int i = 0; i < 4; i++) {
+            LoessInterpolator li = new LoessInterpolator(0.3, i, 1e-12);
+
+            double[] res = li.smooth(xval, yval);
+
+            for (int j = 1; j < res.length; ++j) {
+                variances[i] += FastMath.abs(res[j] - res[j-1]);
+            }
+        }
+
+        for(int i = 1; i < variances.length; ++i) {
+            Assert.assertTrue(variances[i] < variances[i-1]);
+        }
+    }
+
+    @Test(expected=MathException.class)
+    public void testUnequalSizeArguments() throws MathException {
+        new LoessInterpolator().smooth(new double[] {1,2,3}, new double[] {1,2,3,4});
+    }
+
+    @Test(expected=MathException.class)
+    public void testEmptyData() throws MathException {
+        new LoessInterpolator().smooth(new double[] {}, new double[] {});
+    }
+
+    @Test(expected=MathException.class)
+    public void testNonStrictlyIncreasing1() throws MathException {
+        new LoessInterpolator().smooth(new double[] {4,3,1,2}, new double[] {3,4,5,6});
+    }
+
+    @Test(expected=MathException.class)
+    public void testNonStrictlyIncreasing2() throws MathException {
+        new LoessInterpolator().smooth(new double[] {1,2,2,3}, new double[] {3,4,5,6});
+    }
+
+    @Test(expected=MathException.class)
+    public void testNotAllFiniteReal1() throws MathException {
+        new LoessInterpolator().smooth(new double[] {1,2,Double.NaN}, new double[] {3,4,5});
+    }
+
+    @Test(expected=MathException.class)
+    public void testNotAllFiniteReal2() throws MathException {
+        new LoessInterpolator().smooth(new double[] {1,2,Double.POSITIVE_INFINITY}, new double[] {3,4,5});
+    }
+
+    @Test(expected=MathException.class)
+    public void testNotAllFiniteReal3() throws MathException {
+        new LoessInterpolator().smooth(new double[] {1,2,Double.NEGATIVE_INFINITY}, new double[] {3,4,5});
+    }
+
+    @Test(expected=MathException.class)
+    public void testNotAllFiniteReal4() throws MathException {
+        new LoessInterpolator().smooth(new double[] {3,4,5}, new double[] {1,2,Double.NaN});
+    }
+
+    @Test(expected=MathException.class)
+    public void testNotAllFiniteReal5() throws MathException {
+        new LoessInterpolator().smooth(new double[] {3,4,5}, new double[] {1,2,Double.POSITIVE_INFINITY});
+    }
+
+    @Test(expected=MathException.class)
+    public void testNotAllFiniteReal6() throws MathException {
+        new LoessInterpolator().smooth(new double[] {3,4,5}, new double[] {1,2,Double.NEGATIVE_INFINITY});
+    }
+
+    @Test(expected=MathException.class)
+    public void testInsufficientBandwidth() throws MathException {
+        LoessInterpolator li = new LoessInterpolator(0.1, 3, 1e-12);
+        li.smooth(new double[] {1,2,3,4,5,6,7,8,9,10,11,12}, new double[] {1,2,3,4,5,6,7,8,9,10,11,12});
+    }
+
+    @Test(expected=MathException.class)
+    public void testCompletelyIncorrectBandwidth1() throws MathException {
+        new LoessInterpolator(-0.2, 3, 1e-12);
+    }
+
+    @Test(expected=MathException.class)
+    public void testCompletelyIncorrectBandwidth2() throws MathException {
+        new LoessInterpolator(1.1, 3, 1e-12);
+    }
+
+    @Test
+    public void testMath296withoutWeights() throws MathException {
+        double[] xval = {
+                0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0,
+                 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 2.0};
+        double[] yval = {
+                0.47, 0.48, 0.55, 0.56, -0.08, -0.04, -0.07, -0.07,
+                -0.56, -0.46, -0.56, -0.52, -3.03, -3.08, -3.09,
+                -3.04, 3.54, 3.46, 3.36, 3.35};
+        // Output from R, rounded to .001
+        double[] yref = {
+                0.461, 0.499, 0.541, 0.308, 0.175, -0.042, -0.072,
+                -0.196, -0.311, -0.446, -0.557, -1.497, -2.133,
+                -3.08, -3.09, -0.621, 0.982, 3.449, 3.389, 3.336
+        };
+        LoessInterpolator li = new LoessInterpolator(0.3, 4, 1e-12);
+        double[] res = li.smooth(xval, yval);
+        Assert.assertEquals(xval.length, res.length);
+        for(int i = 0; i < res.length; ++i) {
+            Assert.assertEquals(yref[i], res[i], 0.02);
+        }
+    }
+
+    private void generateSineData(double[] xval, double[] yval, double xnoise, double ynoise) {
+        double dx = 2 * FastMath.PI / xval.length;
+        double x = 0;
+        for(int i = 0; i < xval.length; ++i) {
+            xval[i] = x;
+            yval[i] = FastMath.sin(x) + (2 * FastMath.random() - 1) * ynoise;
+            x += dx * (1 + (2 * FastMath.random() - 1) * xnoise);
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/analysis/interpolation/MicrosphereInterpolatorTest.java b/src/test/java/org/apache/commons/math/analysis/interpolation/MicrosphereInterpolatorTest.java
new file mode 100644
index 0000000..742d59d
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/interpolation/MicrosphereInterpolatorTest.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.interpolation;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.analysis.MultivariateRealFunction;
+import org.apache.commons.math.util.FastMath;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Testcase for the "microsphere projection" interpolator.
+ *
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ */
+public final class MicrosphereInterpolatorTest {
+    /**
+     * Test of interpolator for a plane.
+     * <p>
+     * y = 2 x<sub>1</sub> - 3 x<sub>2</sub> + 5
+     */
+    @Test
+    public void testLinearFunction2D() throws MathException {
+        MultivariateRealFunction f = new MultivariateRealFunction() {
+                public double value(double[] x) {
+                    if (x.length != 2) {
+                        throw new IllegalArgumentException();
+                    }
+                    return 2 * x[0] - 3 * x[1] + 5;
+                }
+            };
+
+        MultivariateRealInterpolator interpolator = new MicrosphereInterpolator();
+
+        // Interpolating points in [-1, 1][-1, 1] by steps of 1.
+        final int n = 9;
+        final int dim = 2;
+        double[][] x = new double[n][dim];
+        double[] y = new double[n];
+        int index = 0;
+        for (int i = -1; i <= 1; i++) {
+            for (int j = -1; j <= 1; j++) {
+                x[index][0] = i;
+                x[index][1] = j;
+                y[index] = f.value(x[index]);
+                ++index;
+            }
+        }
+
+        MultivariateRealFunction p = interpolator.interpolate(x, y);
+
+        double[] c = new double[dim];
+        double expected, result;
+
+        c[0] = 0;
+        c[1] = 0;
+        expected = f.value(c);
+        result = p.value(c);
+        Assert.assertEquals("On sample point", expected, result, FastMath.ulp(1d));
+
+        c[0] = 0 + 1e-5;
+        c[1] = 1 - 1e-5;
+        expected = f.value(c);
+        result = p.value(c);
+        Assert.assertEquals("1e-5 away from sample point", expected, result, 1e-4);
+    }
+
+    /**
+     * Test of interpolator for a quadratic function.
+     * <p>
+     * y = 2 x<sub>1</sub><sup>2</sup> - 3 x<sub>2</sub><sup>2</sup>
+     *     + 4 x<sub>1</sub> x<sub>2</sub> - 5
+     */
+    @Test
+    public void testParaboloid2D() throws MathException {
+        MultivariateRealFunction f = new MultivariateRealFunction() {
+                public double value(double[] x) {
+                    if (x.length != 2) {
+                        throw new IllegalArgumentException();
+                    }
+                    return 2 * x[0] * x[0] - 3 * x[1] * x[1] + 4 * x[0] * x[1] - 5;
+                }
+            };
+
+        MultivariateRealInterpolator interpolator = new MicrosphereInterpolator();
+
+        // Interpolating points in [-10, 10][-10, 10] by steps of 2.
+        final int n = 121;
+        final int dim = 2;
+        double[][] x = new double[n][dim];
+        double[] y = new double[n];
+        int index = 0;
+        for (int i = -10; i <= 10; i += 2) {
+            for (int j = -10; j <= 10; j += 2) {
+                x[index][0] = i;
+                x[index][1] = j;
+                y[index] = f.value(x[index]);
+                ++index;
+            }
+        }
+
+        MultivariateRealFunction p = interpolator.interpolate(x, y);
+
+        double[] c = new double[dim];
+        double expected, result;
+
+        c[0] = 0;
+        c[1] = 0;
+        expected = f.value(c);
+        result = p.value(c);
+        Assert.assertEquals("On sample point", expected, result, FastMath.ulp(1d));
+
+        c[0] = 2 + 1e-5;
+        c[1] = 2 - 1e-5;
+        expected = f.value(c);
+        result = p.value(c);
+        Assert.assertEquals("1e-5 away from sample point", expected, result, 1e-3);
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/analysis/interpolation/NevilleInterpolatorTest.java b/src/test/java/org/apache/commons/math/analysis/interpolation/NevilleInterpolatorTest.java
new file mode 100644
index 0000000..72e87ac
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/interpolation/NevilleInterpolatorTest.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.analysis.interpolation;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.analysis.Expm1Function;
+import org.apache.commons.math.analysis.SinFunction;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.util.FastMath;
+
+import junit.framework.TestCase;
+
+/**
+ * Testcase for Neville interpolator.
+ * <p>
+ * The error of polynomial interpolation is
+ *     f(z) - p(z) = f^(n)(zeta) * (z-x[0])(z-x[1])...(z-x[n-1]) / n!
+ * where f^(n) is the n-th derivative of the approximated function and
+ * zeta is some point in the interval determined by x[] and z.
+ * <p>
+ * Since zeta is unknown, f^(n)(zeta) cannot be calculated. But we can bound
+ * it and use the absolute value upper bound for estimates. For reference,
+ * see <b>Introduction to Numerical Analysis</b>, ISBN 038795452X, chapter 2.
+ *
+ * @version $Revision: 1073498 $ $Date: 2011-02-22 21:57:26 +0100 (mar. 22 févr. 2011) $
+ */
+public final class NevilleInterpolatorTest extends TestCase {
+
+    /**
+     * Test of interpolator for the sine function.
+     * <p>
+     * |sin^(n)(zeta)| <= 1.0, zeta in [0, 2*PI]
+     */
+    public void testSinFunction() throws MathException {
+        UnivariateRealFunction f = new SinFunction();
+        UnivariateRealInterpolator interpolator = new NevilleInterpolator();
+        double x[], y[], z, expected, result, tolerance;
+
+        // 6 interpolating points on interval [0, 2*PI]
+        int n = 6;
+        double min = 0.0, max = 2 * FastMath.PI;
+        x = new double[n];
+        y = new double[n];
+        for (int i = 0; i < n; i++) {
+            x[i] = min + i * (max - min) / n;
+            y[i] = f.value(x[i]);
+        }
+        double derivativebound = 1.0;
+        UnivariateRealFunction p = interpolator.interpolate(x, y);
+
+        z = FastMath.PI / 4; expected = f.value(z); result = p.value(z);
+        tolerance = FastMath.abs(derivativebound * partialerror(x, z));
+        assertEquals(expected, result, tolerance);
+
+        z = FastMath.PI * 1.5; expected = f.value(z); result = p.value(z);
+        tolerance = FastMath.abs(derivativebound * partialerror(x, z));
+        assertEquals(expected, result, tolerance);
+    }
+
+    /**
+     * Test of interpolator for the exponential function.
+     * <p>
+     * |expm1^(n)(zeta)| <= e, zeta in [-1, 1]
+     */
+    public void testExpm1Function() throws MathException {
+        UnivariateRealFunction f = new Expm1Function();
+        UnivariateRealInterpolator interpolator = new NevilleInterpolator();
+        double x[], y[], z, expected, result, tolerance;
+
+        // 5 interpolating points on interval [-1, 1]
+        int n = 5;
+        double min = -1.0, max = 1.0;
+        x = new double[n];
+        y = new double[n];
+        for (int i = 0; i < n; i++) {
+            x[i] = min + i * (max - min) / n;
+            y[i] = f.value(x[i]);
+        }
+        double derivativebound = FastMath.E;
+        UnivariateRealFunction p = interpolator.interpolate(x, y);
+
+        z = 0.0; expected = f.value(z); result = p.value(z);
+        tolerance = FastMath.abs(derivativebound * partialerror(x, z));
+        assertEquals(expected, result, tolerance);
+
+        z = 0.5; expected = f.value(z); result = p.value(z);
+        tolerance = FastMath.abs(derivativebound * partialerror(x, z));
+        assertEquals(expected, result, tolerance);
+
+        z = -0.5; expected = f.value(z); result = p.value(z);
+        tolerance = FastMath.abs(derivativebound * partialerror(x, z));
+        assertEquals(expected, result, tolerance);
+    }
+
+    /**
+     * Test of parameters for the interpolator.
+     */
+    public void testParameters() throws Exception {
+        UnivariateRealInterpolator interpolator = new NevilleInterpolator();
+
+        try {
+            // bad abscissas array
+            double x[] = { 1.0, 2.0, 2.0, 4.0 };
+            double y[] = { 0.0, 4.0, 4.0, 2.5 };
+            UnivariateRealFunction p = interpolator.interpolate(x, y);
+            p.value(0.0);
+            fail("Expecting FunctionEvaluationException - bad abscissas array");
+        } catch (FunctionEvaluationException ex) {
+            // expected
+        }
+    }
+
+    /**
+     * Returns the partial error term (z-x[0])(z-x[1])...(z-x[n-1])/n!
+     */
+    protected double partialerror(double x[], double z) throws
+        IllegalArgumentException {
+
+        if (x.length < 1) {
+            throw new IllegalArgumentException
+                ("Interpolation array cannot be empty.");
+        }
+        double out = 1;
+        for (int i = 0; i < x.length; i++) {
+            out *= (z - x[i]) / (i + 1);
+        }
+        return out;
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/analysis/interpolation/SmoothingBicubicSplineInterpolatorTest.java b/src/test/java/org/apache/commons/math/analysis/interpolation/SmoothingBicubicSplineInterpolatorTest.java
new file mode 100644
index 0000000..a022d35
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/interpolation/SmoothingBicubicSplineInterpolatorTest.java
@@ -0,0 +1,179 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.DimensionMismatchException;
+import org.apache.commons.math.analysis.BivariateRealFunction;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Testcase for the bicubic interpolator.
+ * 
+ * @version $Revision: 1042336 $ $Date: 2010-12-05 13:40:48 +0100 (dim. 05 déc. 2010) $ 
+ * @deprecated To be removed in math 3.0 (when the class for which it is a test will also be removed).
+ */
+ at Deprecated
+public final class SmoothingBicubicSplineInterpolatorTest {
+    /**
+     * Test preconditions.
+     */
+    @Test
+    public void testPreconditions() throws MathException {
+        double[] xval = new double[] {3, 4, 5, 6.5};
+        double[] yval = new double[] {-4, -3, -1, 2.5};
+        double[][] zval = new double[xval.length][yval.length];
+
+        BivariateRealGridInterpolator interpolator = new SmoothingBicubicSplineInterpolator();
+        
+        @SuppressWarnings("unused")
+        BivariateRealFunction p = interpolator.interpolate(xval, yval, zval);
+        
+        double[] wxval = new double[] {3, 2, 5, 6.5};
+        try {
+            p = interpolator.interpolate(wxval, yval, zval);
+            Assert.fail("an exception should have been thrown");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        double[] wyval = new double[] {-4, -3, -1, -1};
+        try {
+            p = interpolator.interpolate(xval, wyval, zval);
+            Assert.fail("an exception should have been thrown");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        double[][] wzval = new double[xval.length][yval.length + 1];
+        try {
+            p = interpolator.interpolate(xval, yval, wzval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        wzval = new double[xval.length - 1][yval.length];
+        try {
+            p = interpolator.interpolate(xval, yval, wzval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        wzval = new double[xval.length][yval.length - 1];
+        try {
+            p = interpolator.interpolate(xval, yval, wzval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+    }
+
+    /**
+     * Test of interpolator for a plane.
+     * <p>
+     * z = 2 x - 3 y + 5
+     */
+    @Test
+    public void testPlane() throws MathException {
+        BivariateRealFunction f = new BivariateRealFunction() {
+                public double value(double x, double y) {
+                    return 2 * x - 3 * y + 5;
+                }
+            };
+
+        BivariateRealGridInterpolator interpolator = new SmoothingBicubicSplineInterpolator();
+
+        double[] xval = new double[] {3, 4, 5, 6.5};
+        double[] yval = new double[] {-4, -3, -1, 2, 2.5};
+        double[][] zval = new double[xval.length][yval.length];
+        for (int i = 0; i < xval.length; i++) {
+            for (int j = 0; j < yval.length; j++) {
+                zval[i][j] = f.value(xval[i], yval[j]);
+            }
+        }
+
+        BivariateRealFunction p = interpolator.interpolate(xval, yval, zval);
+        double x, y;
+        double expected, result;
+        
+        x = 4;
+        y = -3;
+        expected = f.value(x, y);
+        result = p.value(x, y);
+        Assert.assertEquals("On sample point", expected, result, 1e-15);
+
+        x = 4.5;
+        y = -1.5;
+        expected = f.value(x, y);
+        result = p.value(x, y);
+        Assert.assertEquals("half-way between sample points (middle of the patch)", expected, result, 0.3);
+
+        x = 3.5;
+        y = -3.5;
+        expected = f.value(x, y);
+        result = p.value(x, y);
+        Assert.assertEquals("half-way between sample points (border of the patch)", expected, result, 0.3);
+    }
+
+    /**
+     * Test of interpolator for a paraboloid.
+     * <p>
+     * z = 2 x<sup>2</sup> - 3 y<sup>2</sup> + 4 x y - 5
+     */
+    @Test
+    public void testParaboloid() throws MathException {
+        BivariateRealFunction f = new BivariateRealFunction() {
+                public double value(double x, double y) {
+                    return 2 * x * x - 3 * y * y + 4 * x * y - 5;
+                }
+            };
+
+        BivariateRealGridInterpolator interpolator = new SmoothingBicubicSplineInterpolator();
+
+        double[] xval = new double[] {3, 4, 5, 6.5};
+        double[] yval = new double[] {-4, -3, -2, -1, 0.5, 2.5};
+        double[][] zval = new double[xval.length][yval.length];
+        for (int i = 0; i < xval.length; i++) {
+            for (int j = 0; j < yval.length; j++) {
+                zval[i][j] = f.value(xval[i], yval[j]);
+            }
+        }
+
+        BivariateRealFunction p = interpolator.interpolate(xval, yval, zval);
+        double x, y;
+        double expected, result;
+        
+        x = 5;
+        y = 0.5;
+        expected = f.value(x, y);
+        result = p.value(x, y);
+        Assert.assertEquals("On sample point", expected, result, 1e-13);
+
+        x = 4.5;
+        y = -1.5;
+        expected = f.value(x, y);
+        result = p.value(x, y);
+        Assert.assertEquals("half-way between sample points (middle of the patch)", expected, result, 0.2);
+
+        x = 3.5;
+        y = -3.5;
+        expected = f.value(x, y);
+        result = p.value(x, y);
+        Assert.assertEquals("half-way between sample points (border of the patch)", expected, result, 0.2);
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/analysis/interpolation/SmoothingPolynomialBicubicSplineInterpolatorTest.java b/src/test/java/org/apache/commons/math/analysis/interpolation/SmoothingPolynomialBicubicSplineInterpolatorTest.java
new file mode 100644
index 0000000..34ba81e
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/interpolation/SmoothingPolynomialBicubicSplineInterpolatorTest.java
@@ -0,0 +1,180 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.analysis.interpolation;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.exception.DimensionMismatchException;
+import org.apache.commons.math.util.FastMath;
+import org.apache.commons.math.analysis.BivariateRealFunction;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Testcase for the smoothing bicubic interpolator.
+ * 
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ */
+public final class SmoothingPolynomialBicubicSplineInterpolatorTest {
+    /**
+     * Test preconditions.
+     */
+    @Test
+    public void testPreconditions() throws MathException {
+        double[] xval = new double[] {3, 4, 5, 6.5};
+        double[] yval = new double[] {-4, -3, -1, 2.5};
+        double[][] zval = new double[xval.length][yval.length];
+
+        BivariateRealGridInterpolator interpolator = new SmoothingPolynomialBicubicSplineInterpolator(0);
+        
+        @SuppressWarnings("unused")
+        BivariateRealFunction p = interpolator.interpolate(xval, yval, zval);
+        
+        double[] wxval = new double[] {3, 2, 5, 6.5};
+        try {
+            p = interpolator.interpolate(wxval, yval, zval);
+            Assert.fail("an exception should have been thrown");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        double[] wyval = new double[] {-4, -3, -1, -1};
+        try {
+            p = interpolator.interpolate(xval, wyval, zval);
+            Assert.fail("an exception should have been thrown");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        double[][] wzval = new double[xval.length][yval.length + 1];
+        try {
+            p = interpolator.interpolate(xval, yval, wzval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        wzval = new double[xval.length - 1][yval.length];
+        try {
+            p = interpolator.interpolate(xval, yval, wzval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        wzval = new double[xval.length][yval.length - 1];
+        try {
+            p = interpolator.interpolate(xval, yval, wzval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+    }
+
+    /**
+     * Test of interpolator for a plane.
+     * <p>
+     * z = 2 x - 3 y + 5
+     */
+    @Test
+    public void testPlane() throws MathException {
+        BivariateRealFunction f = new BivariateRealFunction() {
+                public double value(double x, double y) {
+                    return 2 * x - 3 * y + 5
+                        + ((int) (FastMath.abs(5 * x + 3 * y)) % 2 == 0 ? 1 : -1);
+                }
+            };
+
+        BivariateRealGridInterpolator interpolator = new SmoothingPolynomialBicubicSplineInterpolator(1);
+
+        double[] xval = new double[] {3, 4, 5, 6.5};
+        double[] yval = new double[] {-4, -3, -1, 2, 2.5};
+        double[][] zval = new double[xval.length][yval.length];
+        for (int i = 0; i < xval.length; i++) {
+            for (int j = 0; j < yval.length; j++) {
+                zval[i][j] = f.value(xval[i], yval[j]);
+            }
+        }
+
+        BivariateRealFunction p = interpolator.interpolate(xval, yval, zval);
+        double x, y;
+        double expected, result;
+        
+        x = 4;
+        y = -3;
+        expected = f.value(x, y);
+        result = p.value(x, y);
+        Assert.assertEquals("On sample point", expected, result, 2);
+
+        x = 4.5;
+        y = -1.5;
+        expected = f.value(x, y);
+        result = p.value(x, y);
+        Assert.assertEquals("half-way between sample points (middle of the patch)", expected, result, 2);
+
+        x = 3.5;
+        y = -3.5;
+        expected = f.value(x, y);
+        result = p.value(x, y);
+        Assert.assertEquals("half-way between sample points (border of the patch)", expected, result, 2);
+    }
+
+    /**
+     * Test of interpolator for a paraboloid.
+     * <p>
+     * z = 2 x<sup>2</sup> - 3 y<sup>2</sup> + 4 x y - 5
+     */
+    @Test
+    public void testParaboloid() throws MathException {
+        BivariateRealFunction f = new BivariateRealFunction() {
+                public double value(double x, double y) {
+                    return 2 * x * x - 3 * y * y + 4 * x * y - 5
+                        + ((int) (FastMath.abs(5 * x + 3 * y)) % 2 == 0 ? 1 : -1);
+                }
+            };
+
+        BivariateRealGridInterpolator interpolator = new SmoothingPolynomialBicubicSplineInterpolator(4);
+
+        double[] xval = new double[] {3, 4, 5, 6.5};
+        double[] yval = new double[] {-4, -3, -2, -1, 0.5, 2.5};
+        double[][] zval = new double[xval.length][yval.length];
+        for (int i = 0; i < xval.length; i++) {
+            for (int j = 0; j < yval.length; j++) {
+                zval[i][j] = f.value(xval[i], yval[j]);
+            }
+        }
+
+        BivariateRealFunction p = interpolator.interpolate(xval, yval, zval);
+        double x, y;
+        double expected, result;
+
+        x = 5;
+        y = 0.5;
+        expected = f.value(x, y);
+        result = p.value(x, y);
+        Assert.assertEquals("On sample point", expected, result, 2);
+
+        x = 4.5;
+        y = -1.5;
+        expected = f.value(x, y);
+        result = p.value(x, y);
+        Assert.assertEquals("half-way between sample points (middle of the patch)", expected, result, 2);
+
+        x = 3.5;
+        y = -3.5;
+        expected = f.value(x, y);
+        result = p.value(x, y);
+        Assert.assertEquals("half-way between sample points (border of the patch)", expected, result, 2);
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/analysis/interpolation/SplineInterpolatorTest.java b/src/test/java/org/apache/commons/math/analysis/interpolation/SplineInterpolatorTest.java
new file mode 100644
index 0000000..cff9c07
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/interpolation/SplineInterpolatorTest.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.interpolation;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.exception.NonMonotonousSequenceException;
+import org.apache.commons.math.exception.DimensionMismatchException;
+import org.apache.commons.math.exception.NumberIsTooSmallException;
+import org.apache.commons.math.util.FastMath;
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.analysis.polynomials.PolynomialFunction;
+import org.apache.commons.math.analysis.polynomials.PolynomialSplineFunction;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Test the SplineInterpolator.
+ *
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ */
+public class SplineInterpolatorTest {
+
+    /** error tolerance for spline interpolator value at knot points */
+    protected double knotTolerance = 1E-12;
+
+    /** error tolerance for interpolating polynomial coefficients */
+    protected double coefficientTolerance = 1E-6;
+
+    /** error tolerance for interpolated values -- high value is from sin test */
+    protected double interpolationTolerance = 1E-2;
+
+    @Test
+    public void testInterpolateLinearDegenerateTwoSegment()
+        throws Exception {
+        double x[] = { 0.0, 0.5, 1.0 };
+        double y[] = { 0.0, 0.5, 1.0 };
+        UnivariateRealInterpolator i = new SplineInterpolator();
+        UnivariateRealFunction f = i.interpolate(x, y);
+        verifyInterpolation(f, x, y);
+        verifyConsistency((PolynomialSplineFunction) f, x);
+
+        // Verify coefficients using analytical values
+        PolynomialFunction polynomials[] = ((PolynomialSplineFunction) f).getPolynomials();
+        double target[] = {y[0], 1d};
+        TestUtils.assertEquals(polynomials[0].getCoefficients(), target, coefficientTolerance);
+        target = new double[]{y[1], 1d};
+        TestUtils.assertEquals(polynomials[1].getCoefficients(), target, coefficientTolerance);
+
+        // Check interpolation
+        Assert.assertEquals(0.0,f.value(0.0), interpolationTolerance);
+        Assert.assertEquals(0.4,f.value(0.4), interpolationTolerance);
+        Assert.assertEquals(1.0,f.value(1.0), interpolationTolerance);
+    }
+
+    @Test
+    public void testInterpolateLinearDegenerateThreeSegment()
+        throws Exception {
+        double x[] = { 0.0, 0.5, 1.0, 1.5 };
+        double y[] = { 0.0, 0.5, 1.0, 1.5 };
+        UnivariateRealInterpolator i = new SplineInterpolator();
+        UnivariateRealFunction f = i.interpolate(x, y);
+        verifyInterpolation(f, x, y);
+
+        // Verify coefficients using analytical values
+        PolynomialFunction polynomials[] = ((PolynomialSplineFunction) f).getPolynomials();
+        double target[] = {y[0], 1d};
+        TestUtils.assertEquals(polynomials[0].getCoefficients(), target, coefficientTolerance);
+        target = new double[]{y[1], 1d};
+        TestUtils.assertEquals(polynomials[1].getCoefficients(), target, coefficientTolerance);
+        target = new double[]{y[2], 1d};
+        TestUtils.assertEquals(polynomials[2].getCoefficients(), target, coefficientTolerance);
+
+        // Check interpolation
+        Assert.assertEquals(0,f.value(0), interpolationTolerance);
+        Assert.assertEquals(1.4,f.value(1.4), interpolationTolerance);
+        Assert.assertEquals(1.5,f.value(1.5), interpolationTolerance);
+    }
+
+    @Test
+    public void testInterpolateLinear() throws Exception {
+        double x[] = { 0.0, 0.5, 1.0 };
+        double y[] = { 0.0, 0.5, 0.0 };
+        UnivariateRealInterpolator i = new SplineInterpolator();
+        UnivariateRealFunction f = i.interpolate(x, y);
+        verifyInterpolation(f, x, y);
+        verifyConsistency((PolynomialSplineFunction) f, x);
+
+        // Verify coefficients using analytical values
+        PolynomialFunction polynomials[] = ((PolynomialSplineFunction) f).getPolynomials();
+        double target[] = {y[0], 1.5d, 0d, -2d};
+        TestUtils.assertEquals(polynomials[0].getCoefficients(), target, coefficientTolerance);
+        target = new double[]{y[1], 0d, -3d, 2d};
+        TestUtils.assertEquals(polynomials[1].getCoefficients(), target, coefficientTolerance);
+    }
+
+    @Test
+    public void testInterpolateSin() throws Exception {
+        double x[] =
+            {
+                0.0,
+                FastMath.PI / 6d,
+                FastMath.PI / 2d,
+                5d * FastMath.PI / 6d,
+                FastMath.PI,
+                7d * FastMath.PI / 6d,
+                3d * FastMath.PI / 2d,
+                11d * FastMath.PI / 6d,
+                2.d * FastMath.PI };
+        double y[] = { 0d, 0.5d, 1d, 0.5d, 0d, -0.5d, -1d, -0.5d, 0d };
+        UnivariateRealInterpolator i = new SplineInterpolator();
+        UnivariateRealFunction f = i.interpolate(x, y);
+        verifyInterpolation(f, x, y);
+        verifyConsistency((PolynomialSplineFunction) f, x);
+
+        /* Check coefficients against values computed using R (version 1.8.1, Red Hat Linux 9)
+         *
+         * To replicate in R:
+         *     x[1] <- 0
+         *     x[2] <- pi / 6, etc, same for y[] (could use y <- scan() for y values)
+         *     g <- splinefun(x, y, "natural")
+         *     splinecoef <- eval(expression(z), envir = environment(g))
+         *     print(splinecoef)
+         */
+        PolynomialFunction polynomials[] = ((PolynomialSplineFunction) f).getPolynomials();
+        double target[] = {y[0], 1.002676d, 0d, -0.17415829d};
+        TestUtils.assertEquals(polynomials[0].getCoefficients(), target, coefficientTolerance);
+        target = new double[]{y[1], 8.594367e-01, -2.735672e-01, -0.08707914};
+        TestUtils.assertEquals(polynomials[1].getCoefficients(), target, coefficientTolerance);
+        target = new double[]{y[2], 1.471804e-17,-5.471344e-01, 0.08707914};
+        TestUtils.assertEquals(polynomials[2].getCoefficients(), target, coefficientTolerance);
+        target = new double[]{y[3], -8.594367e-01, -2.735672e-01, 0.17415829};
+        TestUtils.assertEquals(polynomials[3].getCoefficients(), target, coefficientTolerance);
+        target = new double[]{y[4], -1.002676, 6.548562e-17, 0.17415829};
+        TestUtils.assertEquals(polynomials[4].getCoefficients(), target, coefficientTolerance);
+        target = new double[]{y[5], -8.594367e-01, 2.735672e-01, 0.08707914};
+        TestUtils.assertEquals(polynomials[5].getCoefficients(), target, coefficientTolerance);
+        target = new double[]{y[6], 3.466465e-16, 5.471344e-01, -0.08707914};
+        TestUtils.assertEquals(polynomials[6].getCoefficients(), target, coefficientTolerance);
+        target = new double[]{y[7], 8.594367e-01, 2.735672e-01, -0.17415829};
+        TestUtils.assertEquals(polynomials[7].getCoefficients(), target, coefficientTolerance);
+
+        //Check interpolation
+        Assert.assertEquals(FastMath.sqrt(2d) / 2d,f.value(FastMath.PI/4d),interpolationTolerance);
+        Assert.assertEquals(FastMath.sqrt(2d) / 2d,f.value(3d*FastMath.PI/4d),interpolationTolerance);
+    }
+
+    @Test
+    public void testIllegalArguments() throws MathException {
+        // Data set arrays of different size.
+        UnivariateRealInterpolator i = new SplineInterpolator();
+        try {
+            double xval[] = { 0.0, 1.0 };
+            double yval[] = { 0.0, 1.0, 2.0 };
+            i.interpolate(xval, yval);
+            Assert.fail("Failed to detect data set array with different sizes.");
+        } catch (DimensionMismatchException iae) {
+            // Expected.
+        }
+        // X values not sorted.
+        try {
+            double xval[] = { 0.0, 1.0, 0.5 };
+            double yval[] = { 0.0, 1.0, 2.0 };
+            i.interpolate(xval, yval);
+            Assert.fail("Failed to detect unsorted arguments.");
+        } catch (NonMonotonousSequenceException iae) {
+            // Expected.
+        }
+        // Not enough data to interpolate.
+        try {
+            double xval[] = { 0.0, 1.0 };
+            double yval[] = { 0.0, 1.0 };
+            i.interpolate(xval, yval);
+            Assert.fail("Failed to detect unsorted arguments.");
+        } catch (NumberIsTooSmallException iae) {
+            // Expected.
+        }
+    }
+
+    /**
+     * verifies that f(x[i]) = y[i] for i = 0..n-1 where n is common length.
+     */
+    protected void verifyInterpolation(UnivariateRealFunction f, double x[], double y[])
+        throws Exception{
+        for (int i = 0; i < x.length; i++) {
+            Assert.assertEquals(f.value(x[i]), y[i], knotTolerance);
+        }
+    }
+
+    /**
+     * Verifies that interpolating polynomials satisfy consistency requirement:
+     *    adjacent polynomials must agree through two derivatives at knot points
+     */
+    protected void verifyConsistency(PolynomialSplineFunction f, double x[])
+        throws Exception {
+        PolynomialFunction polynomials[] = f.getPolynomials();
+        for (int i = 1; i < x.length - 2; i++) {
+            // evaluate polynomials and derivatives at x[i + 1]
+            Assert.assertEquals(polynomials[i].value(x[i +1] - x[i]), polynomials[i + 1].value(0), 0.1);
+            Assert.assertEquals(polynomials[i].derivative().value(x[i +1] - x[i]),
+                                polynomials[i + 1].derivative().value(0), 0.5);
+            Assert.assertEquals(polynomials[i].polynomialDerivative().derivative().value(x[i +1] - x[i]),
+                                polynomials[i + 1].polynomialDerivative().derivative().value(0), 0.5);
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/analysis/interpolation/TricubicSplineInterpolatingFunctionTest.java b/src/test/java/org/apache/commons/math/analysis/interpolation/TricubicSplineInterpolatingFunctionTest.java
new file mode 100644
index 0000000..1d9943d
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/interpolation/TricubicSplineInterpolatingFunctionTest.java
@@ -0,0 +1,546 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.FunctionEvaluationException;
+import org.apache.commons.math.exception.DimensionMismatchException;
+import org.apache.commons.math.util.FastMath;
+import org.apache.commons.math.analysis.TrivariateRealFunction;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Testcase for the bicubic function.
+ * 
+ * @version $Revision: 821626 $ $Date: 2009-10-04 23:57:30 +0200 (Sun, 04 Oct 2009) $ 
+ */
+public final class TricubicSplineInterpolatingFunctionTest {
+    /**
+     * Test preconditions.
+     */
+    @Test
+    public void testPreconditions() {
+        double[] xval = new double[] {3, 4, 5, 6.5};
+        double[] yval = new double[] {-4, -3, -1, 2.5};
+        double[] zval = new double[] {-12, -8, -5.5, -3, 0, 2.5};
+        double[][][] fval = new double[xval.length][yval.length][zval.length];
+
+        @SuppressWarnings("unused")
+        TrivariateRealFunction tcf = new TricubicSplineInterpolatingFunction(xval, yval, zval,
+                                                                             fval, fval, fval, fval,
+                                                                             fval, fval, fval, fval);
+        
+        double[] wxval = new double[] {3, 2, 5, 6.5};
+        try {
+            tcf = new TricubicSplineInterpolatingFunction(wxval, yval, zval,
+                                                          fval, fval, fval, fval,
+                                                          fval, fval, fval, fval);
+            Assert.fail("an exception should have been thrown");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+        double[] wyval = new double[] {-4, -1, -1, 2.5};
+        try {
+            tcf = new TricubicSplineInterpolatingFunction(xval, wyval, zval,
+                                                          fval, fval, fval, fval,
+                                                          fval, fval, fval, fval);
+            Assert.fail("an exception should have been thrown");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+        double[] wzval = new double[] {-12, -8, -9, -3, 0, 2.5};
+        try {
+            tcf = new TricubicSplineInterpolatingFunction(xval, yval, wzval,
+                                                          fval, fval, fval, fval,
+                                                          fval, fval, fval, fval);
+            Assert.fail("an exception should have been thrown");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+        double[][][] wfval = new double[xval.length - 1][yval.length - 1][zval.length];
+        try {
+            tcf = new TricubicSplineInterpolatingFunction(xval, yval, zval,
+                                                          wfval, fval, fval, fval,
+                                                          fval, fval, fval, fval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        try {
+            tcf = new TricubicSplineInterpolatingFunction(xval, yval, zval,
+                                                          fval, wfval, fval, fval,
+                                                          fval, fval, fval, fval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        try {
+            tcf = new TricubicSplineInterpolatingFunction(xval, yval, zval,
+                                                          fval, fval, wfval, fval,
+                                                          fval, fval, fval, fval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        try {
+            tcf = new TricubicSplineInterpolatingFunction(xval, yval, zval,
+                                                          fval, fval, fval, wfval,
+                                                          fval, fval, fval, fval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        try {
+            tcf = new TricubicSplineInterpolatingFunction(xval, yval, zval,
+                                                          fval, fval, fval, fval,
+                                                          wfval, fval, fval, fval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        try {
+            tcf = new TricubicSplineInterpolatingFunction(xval, yval, zval,
+                                                          fval, fval, fval, fval,
+                                                          fval, wfval, fval, fval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        try {
+            tcf = new TricubicSplineInterpolatingFunction(xval, yval, zval,
+                                                          fval, fval, fval, fval,
+                                                          fval, fval, wfval, fval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        try {
+            tcf = new TricubicSplineInterpolatingFunction(xval, yval, zval,
+                                                          fval, fval, fval, fval,
+                                                          fval, fval, fval, wfval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        wfval = new double[xval.length][yval.length - 1][zval.length];
+        try {
+            tcf = new TricubicSplineInterpolatingFunction(xval, yval, zval,
+                                                          wfval, fval, fval, fval,
+                                                          fval, fval, fval, fval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        try {
+            tcf = new TricubicSplineInterpolatingFunction(xval, yval, zval,
+                                                          fval, wfval, fval, fval,
+                                                          fval, fval, fval, fval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        try {
+            tcf = new TricubicSplineInterpolatingFunction(xval, yval, zval,
+                                                          fval, fval, wfval, fval,
+                                                          fval, fval, fval, fval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        try {
+            tcf = new TricubicSplineInterpolatingFunction(xval, yval, zval,
+                                                          fval, fval, fval, wfval,
+                                                          fval, fval, fval, fval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        try {
+            tcf = new TricubicSplineInterpolatingFunction(xval, yval, zval,
+                                                          fval, fval, fval, fval,
+                                                          wfval, fval, fval, fval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        try {
+            tcf = new TricubicSplineInterpolatingFunction(xval, yval, zval,
+                                                          fval, fval, fval, fval,
+                                                          fval, wfval, fval, fval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        try {
+            tcf = new TricubicSplineInterpolatingFunction(xval, yval, zval,
+                                                          fval, fval, fval, fval,
+                                                          fval, fval, wfval, fval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        try {
+            tcf = new TricubicSplineInterpolatingFunction(xval, yval, zval,
+                                                          fval, fval, fval, fval,
+                                                          fval, fval, fval, wfval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        wfval = new double[xval.length][yval.length][zval.length - 1];
+        try {
+            tcf = new TricubicSplineInterpolatingFunction(xval, yval, zval,
+                                                          wfval, fval, fval, fval,
+                                                          fval, fval, fval, fval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        try {
+            tcf = new TricubicSplineInterpolatingFunction(xval, yval, zval,
+                                                          fval, wfval, fval, fval,
+                                                          fval, fval, fval, fval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        try {
+            tcf = new TricubicSplineInterpolatingFunction(xval, yval, zval,
+                                                          fval, fval, wfval, fval,
+                                                          fval, fval, fval, fval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        try {
+            tcf = new TricubicSplineInterpolatingFunction(xval, yval, zval,
+                                                          fval, fval, fval, wfval,
+                                                          fval, fval, fval, fval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        try {
+            tcf = new TricubicSplineInterpolatingFunction(xval, yval, zval,
+                                                          fval, fval, fval, fval,
+                                                          wfval, fval, fval, fval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        try {
+            tcf = new TricubicSplineInterpolatingFunction(xval, yval, zval,
+                                                          fval, fval, fval, fval,
+                                                          fval, wfval, fval, fval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        try {
+            tcf = new TricubicSplineInterpolatingFunction(xval, yval, zval,
+                                                          fval, fval, fval, fval,
+                                                          fval, fval, wfval, fval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        try {
+            tcf = new TricubicSplineInterpolatingFunction(xval, yval, zval,
+                                                          fval, fval, fval, fval,
+                                                          fval, fval, fval, wfval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        Assert.assertNotNull(tcf); // Avoid Findbugs "dead store" warning
+    }
+
+    /**
+     * Test for a plane.
+     * <p>
+     *  f(x, y, z) = 2 x - 3 y - 4 z + 5
+     * </p>
+     */
+    @Test
+    public void testPlane() throws FunctionEvaluationException {
+        double[] xval = new double[] {3, 4, 5, 6.5};
+        double[] yval = new double[] {-4, -3, -1, 2, 2.5};
+        double[] zval = new double[] {-12, -8, -5.5, -3, 0, 2.5};
+
+        // Function values
+        TrivariateRealFunction f = new TrivariateRealFunction() {
+                public double value(double x, double y, double z) {
+                    return 2 * x - 3 * y - 4 * z + 5;
+                }
+            };
+
+        double[][][] fval = new double[xval.length][yval.length][zval.length];
+
+        for (int i = 0; i < xval.length; i++) {
+            for (int j = 0; j < yval.length; j++) {
+                for (int k = 0; k < zval.length; k++) {
+                    fval[i][j][k] = f.value(xval[i], yval[j], zval[k]);
+                }
+            }
+        }
+        // Partial derivatives with respect to x
+        double[][][] dFdX = new double[xval.length][yval.length][zval.length];
+        for (int i = 0; i < xval.length; i++) {
+            for (int j = 0; j < yval.length; j++) {
+                for (int k = 0; k < zval.length; k++) {
+                    dFdX[i][j][k] = 2;
+                }
+            }
+        }
+        // Partial derivatives with respect to y
+        double[][][] dFdY = new double[xval.length][yval.length][zval.length];
+        for (int i = 0; i < xval.length; i++) {
+            for (int j = 0; j < yval.length; j++) {
+                for (int k = 0; k < zval.length; k++) {
+                    dFdY[i][j][k] = -3;
+                }
+            }
+        }
+
+        // Partial derivatives with respect to z
+        double[][][] dFdZ = new double[xval.length][yval.length][zval.length];
+        for (int i = 0; i < xval.length; i++) {
+            for (int j = 0; j < yval.length; j++) {
+                for (int k = 0; k < zval.length; k++) {
+                    dFdZ[i][j][k] = -4;
+                }
+            }
+        }
+        // Partial cross-derivatives
+        double[][][] d2FdXdY = new double[xval.length][yval.length][zval.length];
+        double[][][] d2FdXdZ = new double[xval.length][yval.length][zval.length];
+        double[][][] d2FdYdZ = new double[xval.length][yval.length][zval.length];
+        double[][][] d3FdXdYdZ = new double[xval.length][yval.length][zval.length];
+        for (int i = 0; i < xval.length; i++) {
+            for (int j = 0; j < yval.length; j++) {
+                for (int k = 0; k < zval.length; k++) {
+                    d2FdXdY[i][j][k] = 0;
+                    d2FdXdZ[i][j][k] = 0;
+                    d2FdYdZ[i][j][k] = 0;
+                    d3FdXdYdZ[i][j][k] = 0;
+                }
+            }
+        }
+
+        TrivariateRealFunction tcf = new TricubicSplineInterpolatingFunction(xval, yval, zval,
+                                                                             fval, dFdX, dFdY, dFdZ,
+                                                                             d2FdXdY, d2FdXdZ, d2FdYdZ,
+                                                                             d3FdXdYdZ);
+        double x, y, z;
+        double expected, result;
+
+        x = 4;
+        y = -3;
+        z = 0;
+        expected = f.value(x, y, z);
+        result = tcf.value(x, y, z);
+        Assert.assertEquals("On sample point",
+                            expected, result, 1e-15);
+
+        x = 4.5;
+        y = -1.5;
+        z = -4.25;
+        expected = f.value(x, y, z);
+        result = tcf.value(x, y, z);
+        Assert.assertEquals("Half-way between sample points (middle of the patch)",
+                            expected, result, 0.3);
+
+        x = 3.5;
+        y = -3.5;
+        z = -10;
+        expected = f.value(x, y, z);
+        result = tcf.value(x, y, z);
+        Assert.assertEquals("Half-way between sample points (border of the patch)",
+                            expected, result, 0.3);
+    }
+
+    /**
+     * Sine wave.
+     * <p>
+     *  f(x, y, z) = a cos [ω z - k<sub>y</sub> x - k<sub>y</sub> y]
+     * </p>
+     * with A = 0.2, ω = 0.5, k<sub>x</sub> = 2, k<sub>y</sub> = 1.
+     */
+    @Test
+    public void testWave() throws FunctionEvaluationException {
+        double[] xval = new double[] {3, 4, 5, 6.5};
+        double[] yval = new double[] {-4, -3, -1, 2, 2.5};
+        double[] zval = new double[] {-12, -8, -5.5, -3, 0, 4};
+        
+        final double a = 0.2;
+        final double omega = 0.5;
+        final double kx = 2;
+        final double ky = 1;
+        
+        // Function values
+        TrivariateRealFunction f = new TrivariateRealFunction() {
+                public double value(double x, double y, double z) {
+                    return a * FastMath.cos(omega * z - kx * x - ky * y);
+                }
+            };
+        
+        double[][][] fval = new double[xval.length][yval.length][zval.length];
+        for (int i = 0; i < xval.length; i++) {
+            for (int j = 0; j < yval.length; j++) {
+                for (int k = 0; k < zval.length; k++) {
+                    fval[i][j][k] = f.value(xval[i], yval[j], zval[k]);
+                }
+            }
+        }
+        
+        // Partial derivatives with respect to x
+        double[][][] dFdX = new double[xval.length][yval.length][zval.length];
+        TrivariateRealFunction dFdX_f = new TrivariateRealFunction() {
+                public double value(double x, double y, double z) {
+                    return a * FastMath.sin(omega * z - kx * x - ky * y) * kx;
+                }
+            };
+        for (int i = 0; i < xval.length; i++) {
+            for (int j = 0; j < yval.length; j++) {
+                for (int k = 0; k < zval.length; k++) {
+                    dFdX[i][j][k] = dFdX_f.value(xval[i], yval[j], zval[k]);
+                }
+            }
+        }
+            
+        // Partial derivatives with respect to y
+        double[][][] dFdY = new double[xval.length][yval.length][zval.length];
+        TrivariateRealFunction dFdY_f = new TrivariateRealFunction() {
+                public double value(double x, double y, double z) {
+                    return a * FastMath.sin(omega * z - kx * x - ky * y) * ky;
+                }
+            };
+        for (int i = 0; i < xval.length; i++) {
+            for (int j = 0; j < yval.length; j++) {
+                for (int k = 0; k < zval.length; k++) {
+                    dFdY[i][j][k] = dFdY_f.value(xval[i], yval[j], zval[k]);
+                }
+            }
+        }
+
+        // Partial derivatives with respect to z
+        double[][][] dFdZ = new double[xval.length][yval.length][zval.length];
+        TrivariateRealFunction dFdZ_f = new TrivariateRealFunction() {
+                public double value(double x, double y, double z) {
+                    return -a * FastMath.sin(omega * z - kx * x - ky * y) * omega;
+                }
+            };
+        for (int i = 0; i < xval.length; i++) {
+            for (int j = 0; j < yval.length; j++) {
+                for (int k = 0; k < zval.length; k++) {
+                    dFdZ[i][j][k] = dFdZ_f.value(xval[i], yval[j], zval[k]);
+                }
+            }
+        }
+
+        // Partial second derivatives w.r.t. (x, y)
+        double[][][] d2FdXdY = new double[xval.length][yval.length][zval.length];
+        TrivariateRealFunction d2FdXdY_f = new TrivariateRealFunction() {
+                public double value(double x, double y, double z) {
+                    return -a * FastMath.cos(omega * z - kx * x - ky * y) * kx * ky;
+                }
+            };
+        for (int i = 0; i < xval.length; i++) {
+            for (int j = 0; j < yval.length; j++) {
+                for (int k = 0; k < zval.length; k++) {
+                    d2FdXdY[i][j][k] = d2FdXdY_f.value(xval[i], yval[j], zval[k]);
+                }
+            }
+        }
+
+        // Partial second derivatives w.r.t. (x, z)
+        double[][][] d2FdXdZ = new double[xval.length][yval.length][zval.length];
+        TrivariateRealFunction d2FdXdZ_f = new TrivariateRealFunction() {
+                public double value(double x, double y, double z) {
+                    return a * FastMath.cos(omega * z - kx * x - ky * y) * kx * omega;
+                }
+            };
+        for (int i = 0; i < xval.length; i++) {
+            for (int j = 0; j < yval.length; j++) {
+                for (int k = 0; k < zval.length; k++) {
+                    d2FdXdZ[i][j][k] = d2FdXdZ_f.value(xval[i], yval[j], zval[k]);
+                }
+            }
+        }
+
+        // Partial second derivatives w.r.t. (y, z)
+        double[][][] d2FdYdZ = new double[xval.length][yval.length][zval.length];
+        TrivariateRealFunction d2FdYdZ_f = new TrivariateRealFunction() {
+                public double value(double x, double y, double z) {
+                    return a * FastMath.cos(omega * z - kx * x - ky * y) * ky * omega;
+                }
+            };
+        for (int i = 0; i < xval.length; i++) {
+            for (int j = 0; j < yval.length; j++) {
+                for (int k = 0; k < zval.length; k++) {
+                    d2FdYdZ[i][j][k] = d2FdYdZ_f.value(xval[i], yval[j], zval[k]);
+                }
+            }
+        }
+
+        // Partial third derivatives
+        double[][][] d3FdXdYdZ = new double[xval.length][yval.length][zval.length];
+        TrivariateRealFunction d3FdXdYdZ_f = new TrivariateRealFunction() {
+                public double value(double x, double y, double z) {
+                    return a * FastMath.sin(omega * z - kx * x - ky * y) * kx * ky * omega;
+                }
+            };
+        for (int i = 0; i < xval.length; i++) {
+            for (int j = 0; j < yval.length; j++) {
+                for (int k = 0; k < zval.length; k++) {
+                    d3FdXdYdZ[i][j][k] = d3FdXdYdZ_f.value(xval[i], yval[j], zval[k]);
+                }
+            }
+        }
+
+        TrivariateRealFunction tcf = new TricubicSplineInterpolatingFunction(xval, yval, zval,
+                                                                             fval, dFdX, dFdY, dFdZ,
+                                                                             d2FdXdY, d2FdXdZ, d2FdYdZ,
+                                                                             d3FdXdYdZ);
+        double x, y, z;
+        double expected, result;
+        
+        x = 4;
+        y = -3;
+        z = 0;
+        expected = f.value(x, y, z);
+        result = tcf.value(x, y, z);
+        Assert.assertEquals("On sample point",
+                            expected, result, 1e-14);
+
+        x = 4.5;
+        y = -1.5;
+        z = -4.25;
+        expected = f.value(x, y, z);
+        result = tcf.value(x, y, z);
+        Assert.assertEquals("Half-way between sample points (middle of the patch)",
+                            expected, result, 0.1);
+
+        x = 3.5;
+        y = -3.5;
+        z = -10;
+        expected = f.value(x, y, z);
+        result = tcf.value(x, y, z);
+        Assert.assertEquals("Half-way between sample points (border of the patch)",
+                            expected, result, 0.1);
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/analysis/interpolation/TricubicSplineInterpolatorTest.java b/src/test/java/org/apache/commons/math/analysis/interpolation/TricubicSplineInterpolatorTest.java
new file mode 100644
index 0000000..a678694
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/interpolation/TricubicSplineInterpolatorTest.java
@@ -0,0 +1,212 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.analysis.interpolation;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.exception.DimensionMismatchException;
+import org.apache.commons.math.util.FastMath;
+import org.apache.commons.math.analysis.TrivariateRealFunction;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Testcase for the tricubic interpolator.
+ * 
+ * @version $Revision$ $Date$ 
+ */
+public final class TricubicSplineInterpolatorTest {
+    /**
+     * Test preconditions.
+     */
+    @Test
+    public void testPreconditions() throws MathException {
+        double[] xval = new double[] {3, 4, 5, 6.5};
+        double[] yval = new double[] {-4, -3, -1, 2.5};
+        double[] zval = new double[] {-12, -8, -5.5, -3, 0, 2.5};
+        double[][][] fval = new double[xval.length][yval.length][zval.length];
+
+        TrivariateRealGridInterpolator interpolator = new TricubicSplineInterpolator();
+        
+        @SuppressWarnings("unused")
+        TrivariateRealFunction p = interpolator.interpolate(xval, yval, zval, fval);
+        
+        double[] wxval = new double[] {3, 2, 5, 6.5};
+        try {
+            p = interpolator.interpolate(wxval, yval, zval, fval);
+            Assert.fail("an exception should have been thrown");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        double[] wyval = new double[] {-4, -3, -1, -1};
+        try {
+            p = interpolator.interpolate(xval, wyval, zval, fval);
+            Assert.fail("an exception should have been thrown");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        double[] wzval = new double[] {-12, -8, -5.5, -3, -4, 2.5};
+        try {
+            p = interpolator.interpolate(xval, yval, wzval, fval);
+            Assert.fail("an exception should have been thrown");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+
+        double[][][] wfval = new double[xval.length][yval.length + 1][zval.length];
+        try {
+            p = interpolator.interpolate(xval, yval, zval, wfval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        wfval = new double[xval.length - 1][yval.length][zval.length];
+        try {
+            p = interpolator.interpolate(xval, yval, zval, wfval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+        wfval = new double[xval.length][yval.length][zval.length - 1];
+        try {
+            p = interpolator.interpolate(xval, yval, zval, wfval);
+            Assert.fail("an exception should have been thrown");
+        } catch (DimensionMismatchException e) {
+            // Expected
+        }
+    }
+
+    /**
+     * Test of interpolator for a plane.
+     * <p>
+     * f(x, y, z) = 2 x - 3 y - z + 5
+     */
+    @Test
+    public void testPlane() throws MathException {
+        TrivariateRealFunction f = new TrivariateRealFunction() {
+                public double value(double x, double y, double z) {
+                    return 2 * x - 3 * y - z + 5;
+                }
+            };
+
+        TrivariateRealGridInterpolator interpolator = new TricubicSplineInterpolator();
+
+        double[] xval = new double[] {3, 4, 5, 6.5};
+        double[] yval = new double[] {-4, -3, -1, 2, 2.5};
+        double[] zval = new double[] {-12, -8, -5.5, -3, 0, 2.5};
+        double[][][] fval = new double[xval.length][yval.length][zval.length];
+        for (int i = 0; i < xval.length; i++) {
+            for (int j = 0; j < yval.length; j++) {
+                for (int k = 0; k < zval.length; k++) {
+                    fval[i][j][k] = f.value(xval[i], yval[j], zval[k]);
+                }
+            }
+        }
+
+        TrivariateRealFunction p = interpolator.interpolate(xval, yval, zval, fval);
+        double x, y, z;
+        double expected, result;
+        
+        x = 4;
+        y = -3;
+        z = 0;
+        expected = f.value(x, y, z);
+        result = p.value(x, y, z);
+        Assert.assertEquals("On sample point", expected, result, 1e-15);
+
+        x = 4.5;
+        y = -1.5;
+        z = -4.25;
+        expected = f.value(x, y, z);
+        result = p.value(x, y, z);
+        Assert.assertEquals("half-way between sample points (middle of the patch)", expected, result, 0.3);
+
+        x = 3.5;
+        y = -3.5;
+        z = -10;
+        expected = f.value(x, y, z);
+        result = p.value(x, y, z);
+        Assert.assertEquals("half-way between sample points (border of the patch)", expected, result, 0.3);
+    }
+
+    /**
+     * Test of interpolator for a sine wave.
+     * <p>
+     * <p>
+     *  f(x, y, z) = a cos [ω z - k<sub>y</sub> x - k<sub>y</sub> y]
+     * </p>
+     * with A = 0.2, ω = 0.5, k<sub>x</sub> = 2, k<sub>y</sub> = 1.
+     */
+    @Test
+    public void testWave() throws MathException {
+        double[] xval = new double[] {3, 4, 5, 6.5};
+        double[] yval = new double[] {-4, -3, -1, 2, 2.5};
+        double[] zval = new double[] {-12, -8, -5.5, -3, 0, 4};
+
+        final double a = 0.2;
+        final double omega = 0.5;
+        final double kx = 2;
+        final double ky = 1;
+
+        // Function values
+        TrivariateRealFunction f = new TrivariateRealFunction() {
+                public double value(double x, double y, double z) {
+                    return a * FastMath.cos(omega * z - kx * x - ky * y);
+                }
+            };
+        
+        double[][][] fval = new double[xval.length][yval.length][zval.length];
+        for (int i = 0; i < xval.length; i++) {
+            for (int j = 0; j < yval.length; j++) {
+                for (int k = 0; k < zval.length; k++) {
+                    fval[i][j][k] = f.value(xval[i], yval[j], zval[k]);
+                }
+            }
+        }
+
+        TrivariateRealGridInterpolator interpolator = new TricubicSplineInterpolator();
+
+        TrivariateRealFunction p = interpolator.interpolate(xval, yval, zval, fval);
+        double x, y, z;
+        double expected, result;
+        
+        x = 4;
+        y = -3;
+        z = 0;
+        expected = f.value(x, y, z);
+        result = p.value(x, y, z);
+        Assert.assertEquals("On sample point",
+                            expected, result, 1e-12);
+
+        x = 4.5;
+        y = -1.5;
+        z = -4.25;
+        expected = f.value(x, y, z);
+        result = p.value(x, y, z);
+        Assert.assertEquals("Half-way between sample points (middle of the patch)",
+                            expected, result, 0.1);
+
+        x = 3.5;
+        y = -3.5;
+        z = -10;
+        expected = f.value(x, y, z);
+        result = p.value(x, y, z);
+        Assert.assertEquals("Half-way between sample points (border of the patch)",
+                            expected, result, 0.1);
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/analysis/polynomials/PolynomialFunctionLagrangeFormTest.java b/src/test/java/org/apache/commons/math/analysis/polynomials/PolynomialFunctionLagrangeFormTest.java
new file mode 100644
index 0000000..54cfb1e
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/polynomials/PolynomialFunctionLagrangeFormTest.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.analysis.polynomials;
+
+import org.apache.commons.math.FunctionEvaluationException;
+
+import junit.framework.TestCase;
+
+/**
+ * Testcase for Lagrange form of polynomial function.
+ * <p>
+ * We use n+1 points to interpolate a polynomial of degree n. This should
+ * give us the exact same polynomial as result. Thus we can use a very
+ * small tolerance to account only for round-off errors.
+ *
+ * @version $Revision: 1073498 $ $Date: 2011-02-22 21:57:26 +0100 (mar. 22 févr. 2011) $
+ */
+public final class PolynomialFunctionLagrangeFormTest extends TestCase {
+
+    /**
+     * Test of polynomial for the linear function.
+     */
+    public void testLinearFunction() throws FunctionEvaluationException {
+        PolynomialFunctionLagrangeForm p;
+        double c[], z, expected, result, tolerance = 1E-12;
+
+        // p(x) = 1.5x - 4
+        double x[] = { 0.0, 3.0 };
+        double y[] = { -4.0, 0.5 };
+        p = new PolynomialFunctionLagrangeForm(x, y);
+
+        z = 2.0; expected = -1.0; result = p.value(z);
+        assertEquals(expected, result, tolerance);
+
+        z = 4.5; expected = 2.75; result = p.value(z);
+        assertEquals(expected, result, tolerance);
+
+        z = 6.0; expected = 5.0; result = p.value(z);
+        assertEquals(expected, result, tolerance);
+
+        assertEquals(1, p.degree());
+
+        c = p.getCoefficients();
+        assertEquals(2, c.length);
+        assertEquals(-4.0, c[0], tolerance);
+        assertEquals(1.5, c[1], tolerance);
+    }
+
+    /**
+     * Test of polynomial for the quadratic function.
+     */
+    public void testQuadraticFunction() throws FunctionEvaluationException {
+        PolynomialFunctionLagrangeForm p;
+        double c[], z, expected, result, tolerance = 1E-12;
+
+        // p(x) = 2x^2 + 5x - 3 = (2x - 1)(x + 3)
+        double x[] = { 0.0, -1.0, 0.5 };
+        double y[] = { -3.0, -6.0, 0.0 };
+        p = new PolynomialFunctionLagrangeForm(x, y);
+
+        z = 1.0; expected = 4.0; result = p.value(z);
+        assertEquals(expected, result, tolerance);
+
+        z = 2.5; expected = 22.0; result = p.value(z);
+        assertEquals(expected, result, tolerance);
+
+        z = -2.0; expected = -5.0; result = p.value(z);
+        assertEquals(expected, result, tolerance);
+
+        assertEquals(2, p.degree());
+
+        c = p.getCoefficients();
+        assertEquals(3, c.length);
+        assertEquals(-3.0, c[0], tolerance);
+        assertEquals(5.0, c[1], tolerance);
+        assertEquals(2.0, c[2], tolerance);
+    }
+
+    /**
+     * Test of polynomial for the quintic function.
+     */
+    public void testQuinticFunction() throws FunctionEvaluationException {
+        PolynomialFunctionLagrangeForm p;
+        double c[], z, expected, result, tolerance = 1E-12;
+
+        // p(x) = x^5 - x^4 - 7x^3 + x^2 + 6x = x(x^2 - 1)(x + 2)(x - 3)
+        double x[] = { 1.0, -1.0, 2.0, 3.0, -3.0, 0.5 };
+        double y[] = { 0.0, 0.0, -24.0, 0.0, -144.0, 2.34375 };
+        p = new PolynomialFunctionLagrangeForm(x, y);
+
+        z = 0.0; expected = 0.0; result = p.value(z);
+        assertEquals(expected, result, tolerance);
+
+        z = -2.0; expected = 0.0; result = p.value(z);
+        assertEquals(expected, result, tolerance);
+
+        z = 4.0; expected = 360.0; result = p.value(z);
+        assertEquals(expected, result, tolerance);
+
+        assertEquals(5, p.degree());
+
+        c = p.getCoefficients();
+        assertEquals(6, c.length);
+        assertEquals(0.0, c[0], tolerance);
+        assertEquals(6.0, c[1], tolerance);
+        assertEquals(1.0, c[2], tolerance);
+        assertEquals(-7.0, c[3], tolerance);
+        assertEquals(-1.0, c[4], tolerance);
+        assertEquals(1.0, c[5], tolerance);
+    }
+
+    /**
+     * Test of parameters for the polynomial.
+     */
+    public void testParameters() throws Exception {
+
+        try {
+            // bad input array length
+            double x[] = { 1.0 };
+            double y[] = { 2.0 };
+            new PolynomialFunctionLagrangeForm(x, y);
+            fail("Expecting IllegalArgumentException - bad input array length");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            // mismatch input arrays
+            double x[] = { 1.0, 2.0, 3.0, 4.0 };
+            double y[] = { 0.0, -4.0, -24.0 };
+            new PolynomialFunctionLagrangeForm(x, y);
+            fail("Expecting IllegalArgumentException - mismatch input arrays");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/analysis/polynomials/PolynomialFunctionNewtonFormTest.java b/src/test/java/org/apache/commons/math/analysis/polynomials/PolynomialFunctionNewtonFormTest.java
new file mode 100644
index 0000000..d47c1b1
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/polynomials/PolynomialFunctionNewtonFormTest.java
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.FunctionEvaluationException;
+
+import junit.framework.TestCase;
+
+/**
+ * Testcase for Newton form of polynomial function.
+ * <p>
+ * The small tolerance number is used only to account for round-off errors.
+ *
+ * @version $Revision: 1073498 $ $Date: 2011-02-22 21:57:26 +0100 (mar. 22 févr. 2011) $
+ */
+public final class PolynomialFunctionNewtonFormTest extends TestCase {
+
+    /**
+     * Test of polynomial for the linear function.
+     */
+    public void testLinearFunction() throws FunctionEvaluationException {
+        PolynomialFunctionNewtonForm p;
+        double coefficients[], z, expected, result, tolerance = 1E-12;
+
+        // p(x) = 1.5x - 4 = 2 + 1.5(x-4)
+        double a[] = { 2.0, 1.5 };
+        double c[] = { 4.0 };
+        p = new PolynomialFunctionNewtonForm(a, c);
+
+        z = 2.0; expected = -1.0; result = p.value(z);
+        assertEquals(expected, result, tolerance);
+
+        z = 4.5; expected = 2.75; result = p.value(z);
+        assertEquals(expected, result, tolerance);
+
+        z = 6.0; expected = 5.0; result = p.value(z);
+        assertEquals(expected, result, tolerance);
+
+        assertEquals(1, p.degree());
+
+        coefficients = p.getCoefficients();
+        assertEquals(2, coefficients.length);
+        assertEquals(-4.0, coefficients[0], tolerance);
+        assertEquals(1.5, coefficients[1], tolerance);
+    }
+
+    /**
+     * Test of polynomial for the quadratic function.
+     */
+    public void testQuadraticFunction() throws FunctionEvaluationException {
+        PolynomialFunctionNewtonForm p;
+        double coefficients[], z, expected, result, tolerance = 1E-12;
+
+        // p(x) = 2x^2 + 5x - 3 = 4 + 3(x-1) + 2(x-1)(x+2)
+        double a[] = { 4.0, 3.0, 2.0 };
+        double c[] = { 1.0, -2.0 };
+        p = new PolynomialFunctionNewtonForm(a, c);
+
+        z = 1.0; expected = 4.0; result = p.value(z);
+        assertEquals(expected, result, tolerance);
+
+        z = 2.5; expected = 22.0; result = p.value(z);
+        assertEquals(expected, result, tolerance);
+
+        z = -2.0; expected = -5.0; result = p.value(z);
+        assertEquals(expected, result, tolerance);
+
+        assertEquals(2, p.degree());
+
+        coefficients = p.getCoefficients();
+        assertEquals(3, coefficients.length);
+        assertEquals(-3.0, coefficients[0], tolerance);
+        assertEquals(5.0, coefficients[1], tolerance);
+        assertEquals(2.0, coefficients[2], tolerance);
+    }
+
+    /**
+     * Test of polynomial for the quintic function.
+     */
+    public void testQuinticFunction() throws FunctionEvaluationException {
+        PolynomialFunctionNewtonForm p;
+        double coefficients[], z, expected, result, tolerance = 1E-12;
+
+        // p(x) = x^5 - x^4 - 7x^3 + x^2 + 6x
+        //      = 6x - 6x^2 -6x^2(x-1) + x^2(x-1)(x+1) + x^2(x-1)(x+1)(x-2)
+        double a[] = { 0.0, 6.0, -6.0, -6.0, 1.0, 1.0 };
+        double c[] = { 0.0, 0.0, 1.0, -1.0, 2.0 };
+        p = new PolynomialFunctionNewtonForm(a, c);
+
+        z = 0.0; expected = 0.0; result = p.value(z);
+        assertEquals(expected, result, tolerance);
+
+        z = -2.0; expected = 0.0; result = p.value(z);
+        assertEquals(expected, result, tolerance);
+
+        z = 4.0; expected = 360.0; result = p.value(z);
+        assertEquals(expected, result, tolerance);
+
+        assertEquals(5, p.degree());
+
+        coefficients = p.getCoefficients();
+        assertEquals(6, coefficients.length);
+        assertEquals(0.0, coefficients[0], tolerance);
+        assertEquals(6.0, coefficients[1], tolerance);
+        assertEquals(1.0, coefficients[2], tolerance);
+        assertEquals(-7.0, coefficients[3], tolerance);
+        assertEquals(-1.0, coefficients[4], tolerance);
+        assertEquals(1.0, coefficients[5], tolerance);
+    }
+
+    /**
+     * Test of parameters for the polynomial.
+     */
+    public void testParameters() throws Exception {
+
+        try {
+            // bad input array length
+            double a[] = { 1.0 };
+            double c[] = { 2.0 };
+            new PolynomialFunctionNewtonForm(a, c);
+            fail("Expecting IllegalArgumentException - bad input array length");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            // mismatch input arrays
+            double a[] = { 1.0, 2.0, 3.0, 4.0 };
+            double c[] = { 4.0, 3.0, 2.0, 1.0 };
+            new PolynomialFunctionNewtonForm(a, c);
+            fail("Expecting IllegalArgumentException - mismatch input arrays");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/analysis/polynomials/PolynomialFunctionTest.java b/src/test/java/org/apache/commons/math/analysis/polynomials/PolynomialFunctionTest.java
new file mode 100644
index 0000000..fb971b1
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/polynomials/PolynomialFunctionTest.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.analysis.polynomials;
+
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.util.FastMath;
+// junit
+import junit.framework.TestCase;
+
+/**
+ * Tests the PolynomialFunction implementation of a UnivariateRealFunction.
+ *
+ * @version $Revision: 1070725 $
+ * @author Matt Cliff <matt at mattcliff.com>
+ */
+public final class PolynomialFunctionTest extends TestCase {
+
+    /** Error tolerance for tests */
+    protected double tolerance = 1.0e-12;
+
+    /**
+     * tests the value of a constant polynomial.
+     *
+     * <p>value of this is 2.5 everywhere.</p>
+     */
+    public void testConstants() throws Exception {
+        double[] c = { 2.5 };
+        PolynomialFunction f = new PolynomialFunction( c );
+
+        // verify that we are equal to c[0] at several (nonsymmetric) places
+        assertEquals( f.value( 0.0), c[0], tolerance );
+        assertEquals( f.value( -1.0), c[0], tolerance );
+        assertEquals( f.value( -123.5), c[0], tolerance );
+        assertEquals( f.value( 3.0), c[0], tolerance );
+        assertEquals( f.value( 456.89), c[0], tolerance );
+
+        assertEquals(f.degree(), 0);
+        assertEquals(f.derivative().value(0), 0, tolerance);
+
+        assertEquals(f.polynomialDerivative().derivative().value(0), 0, tolerance);
+    }
+
+    /**
+     * tests the value of a linear polynomial.
+     *
+     * <p>This will test the function f(x) = 3*x - 1.5</p>
+     * <p>This will have the values
+     *  <tt>f(0.0) = -1.5, f(-1.0) = -4.5, f(-2.5) = -9.0,
+     *      f(0.5) = 0.0, f(1.5) = 3.0</tt> and <tt>f(3.0) = 7.5</tt>
+     * </p>
+     */
+    public void testLinear() throws Exception {
+        double[] c = { -1.5, 3.0 };
+        PolynomialFunction f = new PolynomialFunction( c );
+
+        // verify that we are equal to c[0] when x=0
+        assertEquals( f.value( 0.0), c[0], tolerance );
+
+        // now check a few other places
+        assertEquals( -4.5, f.value( -1.0), tolerance );
+        assertEquals( -9.0, f.value( -2.5), tolerance );
+        assertEquals( 0.0, f.value( 0.5), tolerance );
+        assertEquals( 3.0, f.value( 1.5), tolerance );
+        assertEquals( 7.5, f.value( 3.0), tolerance );
+
+        assertEquals(f.degree(), 1);
+
+        assertEquals(f.polynomialDerivative().derivative().value(0), 0, tolerance);
+
+    }
+
+
+    /**
+     * Tests a second order polynomial.
+     * <p> This will test the function f(x) = 2x^2 - 3x -2 = (2x+1)(x-2)</p>
+     *
+     */
+    public void testQuadratic() {
+        double[] c = { -2.0, -3.0, 2.0 };
+        PolynomialFunction f = new PolynomialFunction( c );
+
+        // verify that we are equal to c[0] when x=0
+        assertEquals( f.value( 0.0), c[0], tolerance );
+
+        // now check a few other places
+        assertEquals( 0.0, f.value( -0.5), tolerance );
+        assertEquals( 0.0, f.value( 2.0), tolerance );
+        assertEquals( -2.0, f.value( 1.5), tolerance );
+        assertEquals( 7.0, f.value( -1.5), tolerance );
+        assertEquals( 265.5312, f.value( 12.34), tolerance );
+
+    }
+
+
+    /**
+     * This will test the quintic function
+     *   f(x) = x^2(x-5)(x+3)(x-1) = x^5 - 3x^4 -13x^3 + 15x^2</p>
+     *
+     */
+    public void testQuintic() {
+        double[] c = { 0.0, 0.0, 15.0, -13.0, -3.0, 1.0 };
+        PolynomialFunction f = new PolynomialFunction( c );
+
+        // verify that we are equal to c[0] when x=0
+        assertEquals( f.value( 0.0), c[0], tolerance );
+
+        // now check a few other places
+        assertEquals( 0.0, f.value( 5.0), tolerance );
+        assertEquals( 0.0, f.value( 1.0), tolerance );
+        assertEquals( 0.0, f.value( -3.0), tolerance );
+        assertEquals( 54.84375, f.value( -1.5), tolerance );
+        assertEquals( -8.06637, f.value( 1.3), tolerance );
+
+        assertEquals(f.degree(), 5);
+
+    }
+
+
+    /**
+     * tests the firstDerivative function by comparison
+     *
+     * <p>This will test the functions
+     * <tt>f(x) = x^3 - 2x^2 + 6x + 3, g(x) = 3x^2 - 4x + 6</tt>
+     * and <tt>h(x) = 6x - 4</tt>
+     */
+    public void testfirstDerivativeComparison() throws Exception {
+        double[] f_coeff = { 3.0, 6.0, -2.0, 1.0 };
+        double[] g_coeff = { 6.0, -4.0, 3.0 };
+        double[] h_coeff = { -4.0, 6.0 };
+
+        PolynomialFunction f = new PolynomialFunction( f_coeff );
+        PolynomialFunction g = new PolynomialFunction( g_coeff );
+        PolynomialFunction h = new PolynomialFunction( h_coeff );
+
+        // compare f' = g
+        assertEquals( f.derivative().value(0.0), g.value(0.0), tolerance );
+        assertEquals( f.derivative().value(1.0), g.value(1.0), tolerance );
+        assertEquals( f.derivative().value(100.0), g.value(100.0), tolerance );
+        assertEquals( f.derivative().value(4.1), g.value(4.1), tolerance );
+        assertEquals( f.derivative().value(-3.25), g.value(-3.25), tolerance );
+
+        // compare g' = h
+        assertEquals( g.derivative().value(FastMath.PI), h.value(FastMath.PI), tolerance );
+        assertEquals( g.derivative().value(FastMath.E),  h.value(FastMath.E),  tolerance );
+
+    }
+
+    public void testString() {
+        PolynomialFunction p = new PolynomialFunction(new double[] { -5.0, 3.0, 1.0 });
+        checkPolynomial(p, "-5.0 + 3.0 x + x^2");
+        checkPolynomial(new PolynomialFunction(new double[] { 0.0, -2.0, 3.0 }),
+                        "-2.0 x + 3.0 x^2");
+        checkPolynomial(new PolynomialFunction(new double[] { 1.0, -2.0, 3.0 }),
+                      "1.0 - 2.0 x + 3.0 x^2");
+        checkPolynomial(new PolynomialFunction(new double[] { 0.0,  2.0, 3.0 }),
+                       "2.0 x + 3.0 x^2");
+        checkPolynomial(new PolynomialFunction(new double[] { 1.0,  2.0, 3.0 }),
+                     "1.0 + 2.0 x + 3.0 x^2");
+        checkPolynomial(new PolynomialFunction(new double[] { 1.0,  0.0, 3.0 }),
+                     "1.0 + 3.0 x^2");
+        checkPolynomial(new PolynomialFunction(new double[] { 0.0 }),
+                     "0");
+    }
+
+    public void testAddition() {
+
+        PolynomialFunction p1 = new PolynomialFunction(new double[] { -2.0, 1.0 });
+        PolynomialFunction p2 = new PolynomialFunction(new double[] { 2.0, -1.0, 0.0 });
+        checkNullPolynomial(p1.add(p2));
+
+        p2 = p1.add(p1);
+        checkPolynomial(p2, "-4.0 + 2.0 x");
+
+        p1 = new PolynomialFunction(new double[] { 1.0, -4.0, 2.0 });
+        p2 = new PolynomialFunction(new double[] { -1.0, 3.0, -2.0 });
+        p1 = p1.add(p2);
+        assertEquals(1, p1.degree());
+        checkPolynomial(p1, "-x");
+
+    }
+
+    public void testSubtraction() {
+
+        PolynomialFunction p1 = new PolynomialFunction(new double[] { -2.0, 1.0 });
+        checkNullPolynomial(p1.subtract(p1));
+
+        PolynomialFunction p2 = new PolynomialFunction(new double[] { -2.0, 6.0 });
+        p2 = p2.subtract(p1);
+        checkPolynomial(p2, "5.0 x");
+
+        p1 = new PolynomialFunction(new double[] { 1.0, -4.0, 2.0 });
+        p2 = new PolynomialFunction(new double[] { -1.0, 3.0, 2.0 });
+        p1 = p1.subtract(p2);
+        assertEquals(1, p1.degree());
+        checkPolynomial(p1, "2.0 - 7.0 x");
+
+    }
+
+    public void testMultiplication() {
+
+        PolynomialFunction p1 = new PolynomialFunction(new double[] { -3.0, 2.0 });
+        PolynomialFunction p2 = new PolynomialFunction(new double[] { 3.0, 2.0, 1.0 });
+        checkPolynomial(p1.multiply(p2), "-9.0 + x^2 + 2.0 x^3");
+
+        p1 = new PolynomialFunction(new double[] { 0.0, 1.0 });
+        p2 = p1;
+        for (int i = 2; i < 10; ++i) {
+            p2 = p2.multiply(p1);
+            checkPolynomial(p2, "x^" + i);
+        }
+
+    }
+
+    public void testSerial() {
+        PolynomialFunction p2 = new PolynomialFunction(new double[] { 3.0, 2.0, 1.0 });
+        assertEquals(p2, TestUtils.serializeAndRecover(p2));
+    }
+
+    /**
+     * tests the firstDerivative function by comparison
+     *
+     * <p>This will test the functions
+     * <tt>f(x) = x^3 - 2x^2 + 6x + 3, g(x) = 3x^2 - 4x + 6</tt>
+     * and <tt>h(x) = 6x - 4</tt>
+     */
+    public void testMath341() throws Exception {
+        double[] f_coeff = { 3.0, 6.0, -2.0, 1.0 };
+        double[] g_coeff = { 6.0, -4.0, 3.0 };
+        double[] h_coeff = { -4.0, 6.0 };
+
+        PolynomialFunction f = new PolynomialFunction( f_coeff );
+        PolynomialFunction g = new PolynomialFunction( g_coeff );
+        PolynomialFunction h = new PolynomialFunction( h_coeff );
+
+        // compare f' = g
+        assertEquals( f.derivative().value(0.0), g.value(0.0), tolerance );
+        assertEquals( f.derivative().value(1.0), g.value(1.0), tolerance );
+        assertEquals( f.derivative().value(100.0), g.value(100.0), tolerance );
+        assertEquals( f.derivative().value(4.1), g.value(4.1), tolerance );
+        assertEquals( f.derivative().value(-3.25), g.value(-3.25), tolerance );
+
+        // compare g' = h
+        assertEquals( g.derivative().value(FastMath.PI), h.value(FastMath.PI), tolerance );
+        assertEquals( g.derivative().value(FastMath.E),  h.value(FastMath.E),  tolerance );
+    }
+
+    public void checkPolynomial(PolynomialFunction p, String reference) {
+        assertEquals(reference, p.toString());
+    }
+
+    private void checkNullPolynomial(PolynomialFunction p) {
+        for (double coefficient : p.getCoefficients()) {
+            assertEquals(0.0, coefficient, 1.0e-15);
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/analysis/polynomials/PolynomialSplineFunctionTest.java b/src/test/java/org/apache/commons/math/analysis/polynomials/PolynomialSplineFunctionTest.java
new file mode 100644
index 0000000..1b2f2dc
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/polynomials/PolynomialSplineFunctionTest.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.analysis.polynomials;
+
+import java.util.Arrays;
+import junit.framework.TestCase;
+
+import org.apache.commons.math.ArgumentOutsideDomainException;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+
+/**
+ * Tests the PolynomialSplineFunction implementation.
+ *
+ * @version $Revision: 1042508 $
+ */
+public class PolynomialSplineFunctionTest extends TestCase {
+
+    /** Error tolerance for tests */
+    protected double tolerance = 1.0e-12;
+
+    /**
+     * Quadratic polynomials used in tests:
+     *
+     * x^2 + x            [-1, 0)
+     * x^2 + x + 2        [0, 1)
+     * x^2 + x + 4        [1, 2)
+     *
+     * Defined so that evaluation using PolynomialSplineFunction evaluation
+     * algorithm agrees at knot point boundaries.
+     */
+    protected PolynomialFunction[] polynomials = {
+        new PolynomialFunction(new double[] {0d, 1d, 1d}),
+        new PolynomialFunction(new double[] {2d, 1d, 1d}),
+        new PolynomialFunction(new double[] {4d, 1d, 1d})
+    };
+
+    /** Knot points  */
+    protected double[] knots = {-1, 0, 1, 2};
+
+    /** Derivative of test polynomials -- 2x + 1  */
+    protected PolynomialFunction dp =
+        new PolynomialFunction(new double[] {1d, 2d});
+
+
+    public void testConstructor() {
+        PolynomialSplineFunction spline =
+            new PolynomialSplineFunction(knots, polynomials);
+        assertTrue(Arrays.equals(knots, spline.getKnots()));
+        assertEquals(1d, spline.getPolynomials()[0].getCoefficients()[2], 0);
+        assertEquals(3, spline.getN());
+
+        try { // too few knots
+            new PolynomialSplineFunction(new double[] {0}, polynomials);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        try { // too many knots
+            new PolynomialSplineFunction(new double[] {0,1,2,3,4}, polynomials);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        try { // knots not increasing
+            new PolynomialSplineFunction(new double[] {0,1, 3, 2}, polynomials);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+
+    public void testValues() throws Exception {
+        PolynomialSplineFunction spline =
+            new PolynomialSplineFunction(knots, polynomials);
+        UnivariateRealFunction dSpline = spline.derivative();
+
+        /**
+         * interior points -- spline value at x should equal p(x - knot)
+         * where knot is the largest knot point less than or equal to x and p
+         * is the polynomial defined over the knot segment to which x belongs.
+         */
+        double x = -1;
+        int index = 0;
+        for (int i = 0; i < 10; i++) {
+           x+=0.25;
+           index = findKnot(knots, x);
+           assertEquals("spline function evaluation failed for x=" + x,
+                   polynomials[index].value(x - knots[index]), spline.value(x), tolerance);
+           assertEquals("spline derivative evaluation failed for x=" + x,
+                   dp.value(x - knots[index]), dSpline.value(x), tolerance);
+        }
+
+        // knot points -- centering should zero arguments
+        for (int i = 0; i < 3; i++) {
+            assertEquals("spline function evaluation failed for knot=" + knots[i],
+                    polynomials[i].value(0), spline.value(knots[i]), tolerance);
+            assertEquals("spline function evaluation failed for knot=" + knots[i],
+                    dp.value(0), dSpline.value(knots[i]), tolerance);
+        }
+
+        try { //outside of domain -- under min
+            x = spline.value(-1.5);
+            fail("Expecting ArgumentOutsideDomainException");
+        } catch (ArgumentOutsideDomainException ex) {
+            // expected
+        }
+
+        try { //outside of domain -- over max
+            x = spline.value(2.5);
+            fail("Expecting ArgumentOutsideDomainException");
+        } catch (ArgumentOutsideDomainException ex) {
+            // expected
+        }
+    }
+
+    /**
+     *  Do linear search to find largest knot point less than or equal to x.
+     *  Implementation does binary search.
+     */
+     protected int findKnot(double[] knots, double x) {
+         if (x < knots[0] || x >= knots[knots.length -1]) {
+             throw new IllegalArgumentException("x is out of range");
+         }
+         for (int i = 0; i < knots.length; i++) {
+             if (knots[i] > x) {
+                 return i -1;
+             }
+         }
+         throw new IllegalArgumentException("x is out of range");
+     }
+}
+
diff --git a/src/test/java/org/apache/commons/math/analysis/polynomials/PolynomialsUtilsTest.java b/src/test/java/org/apache/commons/math/analysis/polynomials/PolynomialsUtilsTest.java
new file mode 100644
index 0000000..f0d84e6
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/polynomials/PolynomialsUtilsTest.java
@@ -0,0 +1,218 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.analysis.polynomials;
+
+import org.apache.commons.math.util.FastMath;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests the PolynomialsUtils class.
+ *
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ */
+public class PolynomialsUtilsTest extends TestCase {
+
+    public void testFirstChebyshevPolynomials() {
+
+        checkPolynomial(PolynomialsUtils.createChebyshevPolynomial(3), "-3.0 x + 4.0 x^3");
+        checkPolynomial(PolynomialsUtils.createChebyshevPolynomial(2), "-1.0 + 2.0 x^2");
+        checkPolynomial(PolynomialsUtils.createChebyshevPolynomial(1), "x");
+        checkPolynomial(PolynomialsUtils.createChebyshevPolynomial(0), "1.0");
+
+        checkPolynomial(PolynomialsUtils.createChebyshevPolynomial(7), "-7.0 x + 56.0 x^3 - 112.0 x^5 + 64.0 x^7");
+        checkPolynomial(PolynomialsUtils.createChebyshevPolynomial(6), "-1.0 + 18.0 x^2 - 48.0 x^4 + 32.0 x^6");
+        checkPolynomial(PolynomialsUtils.createChebyshevPolynomial(5), "5.0 x - 20.0 x^3 + 16.0 x^5");
+        checkPolynomial(PolynomialsUtils.createChebyshevPolynomial(4), "1.0 - 8.0 x^2 + 8.0 x^4");
+
+    }
+
+    public void testChebyshevBounds() {
+        for (int k = 0; k < 12; ++k) {
+            PolynomialFunction Tk = PolynomialsUtils.createChebyshevPolynomial(k);
+            for (double x = -1.0; x <= 1.0; x += 0.02) {
+                assertTrue(k + " " + Tk.value(x), FastMath.abs(Tk.value(x)) < (1.0 + 1.0e-12));
+            }
+        }
+    }
+
+    public void testChebyshevDifferentials() {
+        for (int k = 0; k < 12; ++k) {
+
+            PolynomialFunction Tk0 = PolynomialsUtils.createChebyshevPolynomial(k);
+            PolynomialFunction Tk1 = Tk0.polynomialDerivative();
+            PolynomialFunction Tk2 = Tk1.polynomialDerivative();
+
+            PolynomialFunction g0 = new PolynomialFunction(new double[] { k * k });
+            PolynomialFunction g1 = new PolynomialFunction(new double[] { 0, -1});
+            PolynomialFunction g2 = new PolynomialFunction(new double[] { 1, 0, -1 });
+
+            PolynomialFunction Tk0g0 = Tk0.multiply(g0);
+            PolynomialFunction Tk1g1 = Tk1.multiply(g1);
+            PolynomialFunction Tk2g2 = Tk2.multiply(g2);
+
+            checkNullPolynomial(Tk0g0.add(Tk1g1.add(Tk2g2)));
+
+        }
+    }
+
+    public void testFirstHermitePolynomials() {
+
+        checkPolynomial(PolynomialsUtils.createHermitePolynomial(3), "-12.0 x + 8.0 x^3");
+        checkPolynomial(PolynomialsUtils.createHermitePolynomial(2), "-2.0 + 4.0 x^2");
+        checkPolynomial(PolynomialsUtils.createHermitePolynomial(1), "2.0 x");
+        checkPolynomial(PolynomialsUtils.createHermitePolynomial(0), "1.0");
+
+        checkPolynomial(PolynomialsUtils.createHermitePolynomial(7), "-1680.0 x + 3360.0 x^3 - 1344.0 x^5 + 128.0 x^7");
+        checkPolynomial(PolynomialsUtils.createHermitePolynomial(6), "-120.0 + 720.0 x^2 - 480.0 x^4 + 64.0 x^6");
+        checkPolynomial(PolynomialsUtils.createHermitePolynomial(5), "120.0 x - 160.0 x^3 + 32.0 x^5");
+        checkPolynomial(PolynomialsUtils.createHermitePolynomial(4), "12.0 - 48.0 x^2 + 16.0 x^4");
+
+    }
+
+    public void testHermiteDifferentials() {
+        for (int k = 0; k < 12; ++k) {
+
+            PolynomialFunction Hk0 = PolynomialsUtils.createHermitePolynomial(k);
+            PolynomialFunction Hk1 = Hk0.polynomialDerivative();
+            PolynomialFunction Hk2 = Hk1.polynomialDerivative();
+
+            PolynomialFunction g0 = new PolynomialFunction(new double[] { 2 * k });
+            PolynomialFunction g1 = new PolynomialFunction(new double[] { 0, -2 });
+            PolynomialFunction g2 = new PolynomialFunction(new double[] { 1 });
+
+            PolynomialFunction Hk0g0 = Hk0.multiply(g0);
+            PolynomialFunction Hk1g1 = Hk1.multiply(g1);
+            PolynomialFunction Hk2g2 = Hk2.multiply(g2);
+
+            checkNullPolynomial(Hk0g0.add(Hk1g1.add(Hk2g2)));
+
+        }
+    }
+
+    public void testFirstLaguerrePolynomials() {
+
+        checkPolynomial(PolynomialsUtils.createLaguerrePolynomial(3), 6l, "6.0 - 18.0 x + 9.0 x^2 - x^3");
+        checkPolynomial(PolynomialsUtils.createLaguerrePolynomial(2), 2l, "2.0 - 4.0 x + x^2");
+        checkPolynomial(PolynomialsUtils.createLaguerrePolynomial(1), 1l, "1.0 - x");
+        checkPolynomial(PolynomialsUtils.createLaguerrePolynomial(0), 1l, "1.0");
+
+        checkPolynomial(PolynomialsUtils.createLaguerrePolynomial(7), 5040l,
+                "5040.0 - 35280.0 x + 52920.0 x^2 - 29400.0 x^3"
+                + " + 7350.0 x^4 - 882.0 x^5 + 49.0 x^6 - x^7");
+        checkPolynomial(PolynomialsUtils.createLaguerrePolynomial(6),  720l,
+                "720.0 - 4320.0 x + 5400.0 x^2 - 2400.0 x^3 + 450.0 x^4"
+                + " - 36.0 x^5 + x^6");
+        checkPolynomial(PolynomialsUtils.createLaguerrePolynomial(5),  120l,
+        "120.0 - 600.0 x + 600.0 x^2 - 200.0 x^3 + 25.0 x^4 - x^5");
+        checkPolynomial(PolynomialsUtils.createLaguerrePolynomial(4),   24l,
+        "24.0 - 96.0 x + 72.0 x^2 - 16.0 x^3 + x^4");
+
+    }
+
+    public void testLaguerreDifferentials() {
+        for (int k = 0; k < 12; ++k) {
+
+            PolynomialFunction Lk0 = PolynomialsUtils.createLaguerrePolynomial(k);
+            PolynomialFunction Lk1 = Lk0.polynomialDerivative();
+            PolynomialFunction Lk2 = Lk1.polynomialDerivative();
+
+            PolynomialFunction g0 = new PolynomialFunction(new double[] { k });
+            PolynomialFunction g1 = new PolynomialFunction(new double[] { 1, -1 });
+            PolynomialFunction g2 = new PolynomialFunction(new double[] { 0, 1 });
+
+            PolynomialFunction Lk0g0 = Lk0.multiply(g0);
+            PolynomialFunction Lk1g1 = Lk1.multiply(g1);
+            PolynomialFunction Lk2g2 = Lk2.multiply(g2);
+
+            checkNullPolynomial(Lk0g0.add(Lk1g1.add(Lk2g2)));
+
+        }
+    }
+
+    public void testFirstLegendrePolynomials() {
+
+        checkPolynomial(PolynomialsUtils.createLegendrePolynomial(3),  2l, "-3.0 x + 5.0 x^3");
+        checkPolynomial(PolynomialsUtils.createLegendrePolynomial(2),  2l, "-1.0 + 3.0 x^2");
+        checkPolynomial(PolynomialsUtils.createLegendrePolynomial(1),  1l, "x");
+        checkPolynomial(PolynomialsUtils.createLegendrePolynomial(0),  1l, "1.0");
+
+        checkPolynomial(PolynomialsUtils.createLegendrePolynomial(7), 16l, "-35.0 x + 315.0 x^3 - 693.0 x^5 + 429.0 x^7");
+        checkPolynomial(PolynomialsUtils.createLegendrePolynomial(6), 16l, "-5.0 + 105.0 x^2 - 315.0 x^4 + 231.0 x^6");
+        checkPolynomial(PolynomialsUtils.createLegendrePolynomial(5),  8l, "15.0 x - 70.0 x^3 + 63.0 x^5");
+        checkPolynomial(PolynomialsUtils.createLegendrePolynomial(4),  8l, "3.0 - 30.0 x^2 + 35.0 x^4");
+
+    }
+
+    public void testLegendreDifferentials() {
+        for (int k = 0; k < 12; ++k) {
+
+            PolynomialFunction Pk0 = PolynomialsUtils.createLegendrePolynomial(k);
+            PolynomialFunction Pk1 = Pk0.polynomialDerivative();
+            PolynomialFunction Pk2 = Pk1.polynomialDerivative();
+
+            PolynomialFunction g0 = new PolynomialFunction(new double[] { k * (k + 1) });
+            PolynomialFunction g1 = new PolynomialFunction(new double[] { 0, -2 });
+            PolynomialFunction g2 = new PolynomialFunction(new double[] { 1, 0, -1 });
+
+            PolynomialFunction Pk0g0 = Pk0.multiply(g0);
+            PolynomialFunction Pk1g1 = Pk1.multiply(g1);
+            PolynomialFunction Pk2g2 = Pk2.multiply(g2);
+
+            checkNullPolynomial(Pk0g0.add(Pk1g1.add(Pk2g2)));
+
+        }
+    }
+
+    public void testHighDegreeLegendre() {
+        PolynomialsUtils.createLegendrePolynomial(40);
+        double[] l40 = PolynomialsUtils.createLegendrePolynomial(40).getCoefficients();
+        double denominator = 274877906944.0;
+        double[] numerators = new double[] {
+                          +34461632205.0,            -28258538408100.0,          +3847870979902950.0,        -207785032914759300.0,
+                  +5929294332103310025.0,     -103301483474866556880.0,    +1197358103913226000200.0,    -9763073770369381232400.0,
+              +58171647881784229843050.0,  -260061484647976556945400.0,  +888315281771246239250340.0, -2345767627188139419665400.0,
+            +4819022625419112503443050.0, -7710436200670580005508880.0, +9566652323054238154983240.0, -9104813935044723209570256.0,
+            +6516550296251767619752905.0, -3391858621221953912598660.0, +1211378079007840683070950.0,  -265365894974690562152100.0,
+              +26876802183334044115405.0
+        };
+        for (int i = 0; i < l40.length; ++i) {
+            if (i % 2 == 0) {
+                double ci = numerators[i / 2] / denominator;
+                assertEquals(ci, l40[i], FastMath.abs(ci) * 1.0e-15);
+            } else {
+                assertEquals(0.0, l40[i], 0.0);
+            }
+        }
+    }
+
+    private void checkPolynomial(PolynomialFunction p, long denominator, String reference) {
+        PolynomialFunction q = new PolynomialFunction(new double[] { denominator});
+        assertEquals(reference, p.multiply(q).toString());
+    }
+
+    private void checkPolynomial(PolynomialFunction p, String reference) {
+        assertEquals(reference, p.toString());
+    }
+
+    private void checkNullPolynomial(PolynomialFunction p) {
+        for (double coefficient : p.getCoefficients()) {
+            assertEquals(0.0, coefficient, 1.0e-13);
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/analysis/solvers/BisectionSolverTest.java b/src/test/java/org/apache/commons/math/analysis/solvers/BisectionSolverTest.java
new file mode 100644
index 0000000..a8ba41c
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/solvers/BisectionSolverTest.java
@@ -0,0 +1,194 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.analysis.solvers;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.analysis.QuinticFunction;
+import org.apache.commons.math.analysis.SinFunction;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.util.FastMath;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ */
+public final class BisectionSolverTest extends TestCase {
+
+    @Deprecated
+    public void testDeprecated() throws MathException {
+        UnivariateRealFunction f = new SinFunction();
+        double result;
+
+        UnivariateRealSolver solver = new BisectionSolver(f);
+        result = solver.solve(3, 4);
+        assertEquals(result, FastMath.PI, solver.getAbsoluteAccuracy());
+
+        result = solver.solve(1, 4);
+        assertEquals(result, FastMath.PI, solver.getAbsoluteAccuracy());
+    }
+
+    public void testSinZero() throws MathException {
+        UnivariateRealFunction f = new SinFunction();
+        double result;
+
+        UnivariateRealSolver solver = new BisectionSolver();
+        result = solver.solve(f, 3, 4);
+        assertEquals(result, FastMath.PI, solver.getAbsoluteAccuracy());
+
+        result = solver.solve(f, 1, 4);
+        assertEquals(result, FastMath.PI, solver.getAbsoluteAccuracy());
+    }
+
+   public void testQuinticZero() throws MathException {
+        UnivariateRealFunction f = new QuinticFunction();
+        double result;
+
+        UnivariateRealSolver solver = new BisectionSolver();
+        result = solver.solve(f, -0.2, 0.2);
+        assertEquals(result, 0, solver.getAbsoluteAccuracy());
+
+        result = solver.solve(f, -0.1, 0.3);
+        assertEquals(result, 0, solver.getAbsoluteAccuracy());
+
+        result = solver.solve(f, -0.3, 0.45);
+        assertEquals(result, 0, solver.getAbsoluteAccuracy());
+
+        result = solver.solve(f, 0.3, 0.7);
+        assertEquals(result, 0.5, solver.getAbsoluteAccuracy());
+
+        result = solver.solve(f, 0.2, 0.6);
+        assertEquals(result, 0.5, solver.getAbsoluteAccuracy());
+
+        result = solver.solve(f, 0.05, 0.95);
+        assertEquals(result, 0.5, solver.getAbsoluteAccuracy());
+
+        result = solver.solve(f, 0.85, 1.25);
+        assertEquals(result, 1.0, solver.getAbsoluteAccuracy());
+
+        result = solver.solve(f, 0.8, 1.2);
+        assertEquals(result, 1.0, solver.getAbsoluteAccuracy());
+
+        result = solver.solve(f, 0.85, 1.75);
+        assertEquals(result, 1.0, solver.getAbsoluteAccuracy());
+
+        result = solver.solve(f, 0.55, 1.45);
+        assertEquals(result, 1.0, solver.getAbsoluteAccuracy());
+
+        result = solver.solve(f, 0.85, 5);
+        assertEquals(result, 1.0, solver.getAbsoluteAccuracy());
+
+        assertEquals(result, solver.getResult(), 0);
+        assertTrue(solver.getIterationCount() > 0);
+    }
+
+    public void testMath369() throws Exception {
+        UnivariateRealFunction f = new SinFunction();
+        UnivariateRealSolver solver = new BisectionSolver();
+        assertEquals(FastMath.PI, solver.solve(f, 3.0, 3.2, 3.1), solver.getAbsoluteAccuracy());
+    }
+
+    /**
+     *
+     */
+    public void testSetFunctionValueAccuracy(){
+        double expected = 1.0e-2;
+        UnivariateRealSolver solver = new BisectionSolver();
+        solver.setFunctionValueAccuracy(expected);
+        assertEquals(expected, solver.getFunctionValueAccuracy(), 1.0e-2);
+    }
+
+    /**
+     *
+     */
+    public void testResetFunctionValueAccuracy(){
+        double newValue = 1.0e-2;
+        UnivariateRealSolver solver = new BisectionSolver();
+        double oldValue = solver.getFunctionValueAccuracy();
+        solver.setFunctionValueAccuracy(newValue);
+        solver.resetFunctionValueAccuracy();
+        assertEquals(oldValue, solver.getFunctionValueAccuracy(), 1.0e-2);
+    }
+
+    /**
+     *
+     */
+    public void testSetAbsoluteAccuracy(){
+        double expected = 1.0e-2;
+        UnivariateRealSolver solver = new BisectionSolver();
+        solver.setAbsoluteAccuracy(expected);
+        assertEquals(expected, solver.getAbsoluteAccuracy(), 1.0e-2);
+    }
+
+    /**
+     *
+     */
+    public void testResetAbsoluteAccuracy(){
+        double newValue = 1.0e-2;
+        UnivariateRealSolver solver = new BisectionSolver();
+        double oldValue = solver.getAbsoluteAccuracy();
+        solver.setAbsoluteAccuracy(newValue);
+        solver.resetAbsoluteAccuracy();
+        assertEquals(oldValue, solver.getAbsoluteAccuracy(), 1.0e-2);
+    }
+
+    /**
+     *
+     */
+    public void testSetMaximalIterationCount(){
+        int expected = 100;
+        UnivariateRealSolver solver = new BisectionSolver();
+        solver.setMaximalIterationCount(expected);
+        assertEquals(expected, solver.getMaximalIterationCount());
+    }
+
+    /**
+     *
+     */
+    public void testResetMaximalIterationCount(){
+        int newValue = 10000;
+        UnivariateRealSolver solver = new BisectionSolver();
+        int oldValue = solver.getMaximalIterationCount();
+        solver.setMaximalIterationCount(newValue);
+        solver.resetMaximalIterationCount();
+        assertEquals(oldValue, solver.getMaximalIterationCount());
+    }
+
+    /**
+     *
+     */
+    public void testSetRelativeAccuracy(){
+        double expected = 1.0e-2;
+        UnivariateRealSolver solver = new BisectionSolver();
+        solver.setRelativeAccuracy(expected);
+        assertEquals(expected, solver.getRelativeAccuracy(), 1.0e-2);
+    }
+
+    /**
+     *
+     */
+    public void testResetRelativeAccuracy(){
+        double newValue = 1.0e-2;
+        UnivariateRealSolver solver = new BisectionSolver();
+        double oldValue = solver.getRelativeAccuracy();
+        solver.setRelativeAccuracy(newValue);
+        solver.resetRelativeAccuracy();
+        assertEquals(oldValue, solver.getRelativeAccuracy(), 1.0e-2);
+    }
+
+
+}
diff --git a/src/test/java/org/apache/commons/math/analysis/solvers/BrentSolverTest.java b/src/test/java/org/apache/commons/math/analysis/solvers/BrentSolverTest.java
new file mode 100644
index 0000000..1690987
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/solvers/BrentSolverTest.java
@@ -0,0 +1,389 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 junit.framework.TestCase;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.analysis.MonitoredFunction;
+import org.apache.commons.math.analysis.QuinticFunction;
+import org.apache.commons.math.analysis.SinFunction;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Testcase for UnivariateRealSolver.
+ * Because Brent-Dekker is guaranteed to converge in less than the default
+ * maximum iteration count due to bisection fallback, it is quite hard to
+ * debug. I include measured iteration counts plus one in order to detect
+ * regressions. On average Brent-Dekker should use 4..5 iterations for the
+ * default absolute accuracy of 10E-8 for sinus and the quintic function around
+ * zero, and 5..10 iterations for the other zeros.
+ *
+ * @version $Revision:670469 $ $Date:2008-06-23 10:01:38 +0200 (lun., 23 juin 2008) $
+ */
+public final class BrentSolverTest extends TestCase {
+
+    public BrentSolverTest(String name) {
+        super(name);
+    }
+
+    @Deprecated
+    public void testDeprecated() throws MathException {
+        // The sinus function is behaved well around the root at #pi. The second
+        // order derivative is zero, which means linar approximating methods will
+        // still converge quadratically.
+        UnivariateRealFunction f = new SinFunction();
+        double result;
+        UnivariateRealSolver solver = new BrentSolver(f);
+        // Somewhat benign interval. The function is monotone.
+        result = solver.solve(3, 4);
+        //System.out.println(
+        //    "Root: " + result + " Iterations: " + solver.getIterationCount());
+        assertEquals(result, FastMath.PI, solver.getAbsoluteAccuracy());
+        // 4 iterations on i586 JDK 1.4.1.
+        assertTrue(solver.getIterationCount() <= 5);
+        // Larger and somewhat less benign interval. The function is grows first.
+        result = solver.solve(1, 4);
+        //System.out.println(
+        //    "Root: " + result + " Iterations: " + solver.getIterationCount());
+        assertEquals(result, FastMath.PI, solver.getAbsoluteAccuracy());
+        // 5 iterations on i586 JDK 1.4.1.
+        assertTrue(solver.getIterationCount() <= 6);
+        solver = new SecantSolver(f);
+        result = solver.solve(3, 4);
+        //System.out.println(
+        //    "Root: " + result + " Iterations: " + solver.getIterationCount());
+        assertEquals(result, FastMath.PI, solver.getAbsoluteAccuracy());
+        // 4 iterations on i586 JDK 1.4.1.
+        assertTrue(solver.getIterationCount() <= 5);
+        result = solver.solve(1, 4);
+        //System.out.println(
+        //    "Root: " + result + " Iterations: " + solver.getIterationCount());
+        assertEquals(result, FastMath.PI, solver.getAbsoluteAccuracy());
+        // 5 iterations on i586 JDK 1.4.1.
+        assertTrue(solver.getIterationCount() <= 6);
+        assertEquals(result, solver.getResult(), 0);
+    }
+
+    public void testSinZero() throws MathException {
+        // The sinus function is behaved well around the root at #pi. The second
+        // order derivative is zero, which means linar approximating methods will
+        // still converge quadratically.
+        UnivariateRealFunction f = new SinFunction();
+        double result;
+        UnivariateRealSolver solver = new BrentSolver();
+        // Somewhat benign interval. The function is monotone.
+        result = solver.solve(f, 3, 4);
+        //System.out.println(
+        //    "Root: " + result + " Iterations: " + solver.getIterationCount());
+        assertEquals(result, FastMath.PI, solver.getAbsoluteAccuracy());
+        // 4 iterations on i586 JDK 1.4.1.
+        assertTrue(solver.getIterationCount() <= 5);
+        // Larger and somewhat less benign interval. The function is grows first.
+        result = solver.solve(f, 1, 4);
+        //System.out.println(
+        //    "Root: " + result + " Iterations: " + solver.getIterationCount());
+        assertEquals(result, FastMath.PI, solver.getAbsoluteAccuracy());
+        // 5 iterations on i586 JDK 1.4.1.
+        assertTrue(solver.getIterationCount() <= 6);
+        solver = new SecantSolver();
+        result = solver.solve(f, 3, 4);
+        //System.out.println(
+        //    "Root: " + result + " Iterations: " + solver.getIterationCount());
+        assertEquals(result, FastMath.PI, solver.getAbsoluteAccuracy());
+        // 4 iterations on i586 JDK 1.4.1.
+        assertTrue(solver.getIterationCount() <= 5);
+        result = solver.solve(f, 1, 4);
+        //System.out.println(
+        //    "Root: " + result + " Iterations: " + solver.getIterationCount());
+        assertEquals(result, FastMath.PI, solver.getAbsoluteAccuracy());
+        // 5 iterations on i586 JDK 1.4.1.
+        assertTrue(solver.getIterationCount() <= 6);
+        assertEquals(result, solver.getResult(), 0);
+    }
+
+   public void testQuinticZero() throws MathException {
+        // The quintic function has zeros at 0, +-0.5 and +-1.
+        // Around the root of 0 the function is well behaved, with a second derivative
+        // of zero a 0.
+        // The other roots are less well to find, in particular the root at 1, because
+        // the function grows fast for x>1.
+        // The function has extrema (first derivative is zero) at 0.27195613 and 0.82221643,
+        // intervals containing these values are harder for the solvers.
+        UnivariateRealFunction f = new QuinticFunction();
+        double result;
+        // Brent-Dekker solver.
+        UnivariateRealSolver solver = new BrentSolver();
+        // Symmetric bracket around 0. Test whether solvers can handle hitting
+        // the root in the first iteration.
+        result = solver.solve(f, -0.2, 0.2);
+        //System.out.println(
+        //    "Root: " + result + " Iterations: " + solver.getIterationCount());
+        assertEquals(result, 0, solver.getAbsoluteAccuracy());
+        assertTrue(solver.getIterationCount() <= 2);
+        // 1 iterations on i586 JDK 1.4.1.
+        // Asymmetric bracket around 0, just for fun. Contains extremum.
+        result = solver.solve(f, -0.1, 0.3);
+        //System.out.println(
+        //    "Root: " + result + " Iterations: " + solver.getIterationCount());
+        assertEquals(result, 0, solver.getAbsoluteAccuracy());
+        // 5 iterations on i586 JDK 1.4.1.
+        assertTrue(solver.getIterationCount() <= 6);
+        // Large bracket around 0. Contains two extrema.
+        result = solver.solve(f, -0.3, 0.45);
+        //System.out.println(
+        //    "Root: " + result + " Iterations: " + solver.getIterationCount());
+        assertEquals(result, 0, solver.getAbsoluteAccuracy());
+        // 6 iterations on i586 JDK 1.4.1.
+        assertTrue(solver.getIterationCount() <= 7);
+        // Benign bracket around 0.5, function is monotonous.
+        result = solver.solve(f, 0.3, 0.7);
+        //System.out.println(
+        //    "Root: " + result + " Iterations: " + solver.getIterationCount());
+        assertEquals(result, 0.5, solver.getAbsoluteAccuracy());
+        // 6 iterations on i586 JDK 1.4.1.
+        assertTrue(solver.getIterationCount() <= 7);
+        // Less benign bracket around 0.5, contains one extremum.
+        result = solver.solve(f, 0.2, 0.6);
+        //System.out.println(
+        //    "Root: " + result + " Iterations: " + solver.getIterationCount());
+        assertEquals(result, 0.5, solver.getAbsoluteAccuracy());
+        // 6 iterations on i586 JDK 1.4.1.
+        assertTrue(solver.getIterationCount() <= 7);
+        // Large, less benign bracket around 0.5, contains both extrema.
+        result = solver.solve(f, 0.05, 0.95);
+        //System.out.println(
+        //    "Root: " + result + " Iterations: " + solver.getIterationCount());
+        assertEquals(result, 0.5, solver.getAbsoluteAccuracy());
+        // 8 iterations on i586 JDK 1.4.1.
+        assertTrue(solver.getIterationCount() <= 9);
+        // Relatively benign bracket around 1, function is monotonous. Fast growth for x>1
+        // is still a problem.
+        result = solver.solve(f, 0.85, 1.25);
+        //System.out.println(
+        //    "Root: " + result + " Iterations: " + solver.getIterationCount());
+        assertEquals(result, 1.0, solver.getAbsoluteAccuracy());
+        // 8 iterations on i586 JDK 1.4.1.
+        assertTrue(solver.getIterationCount() <= 9);
+        // Less benign bracket around 1 with extremum.
+        result = solver.solve(f, 0.8, 1.2);
+        //System.out.println(
+        //    "Root: " + result + " Iterations: " + solver.getIterationCount());
+        assertEquals(result, 1.0, solver.getAbsoluteAccuracy());
+        // 8 iterations on i586 JDK 1.4.1.
+        assertTrue(solver.getIterationCount() <= 9);
+        // Large bracket around 1. Monotonous.
+        result = solver.solve(f, 0.85, 1.75);
+        //System.out.println(
+        //    "Root: " + result + " Iterations: " + solver.getIterationCount());
+        assertEquals(result, 1.0, solver.getAbsoluteAccuracy());
+        // 10 iterations on i586 JDK 1.4.1.
+        assertTrue(solver.getIterationCount() <= 11);
+        // Large bracket around 1. Interval contains extremum.
+        result = solver.solve(f, 0.55, 1.45);
+        //System.out.println(
+        //    "Root: " + result + " Iterations: " + solver.getIterationCount());
+        assertEquals(result, 1.0, solver.getAbsoluteAccuracy());
+        // 7 iterations on i586 JDK 1.4.1.
+        assertTrue(solver.getIterationCount() <= 8);
+        // Very large bracket around 1 for testing fast growth behaviour.
+        result = solver.solve(f, 0.85, 5);
+        //System.out.println(
+       //     "Root: " + result + " Iterations: " + solver.getIterationCount());
+        assertEquals(result, 1.0, solver.getAbsoluteAccuracy());
+        // 12 iterations on i586 JDK 1.4.1.
+        assertTrue(solver.getIterationCount() <= 13);
+        // Secant solver.
+        solver = new SecantSolver();
+        result = solver.solve(f, -0.2, 0.2);
+        //System.out.println(
+        //    "Root: " + result + " Iterations: " + solver.getIterationCount());
+        assertEquals(result, 0, solver.getAbsoluteAccuracy());
+        // 1 iterations on i586 JDK 1.4.1.
+        assertTrue(solver.getIterationCount() <= 2);
+        result = solver.solve(f, -0.1, 0.3);
+        //System.out.println(
+        //    "Root: " + result + " Iterations: " + solver.getIterationCount());
+        assertEquals(result, 0, solver.getAbsoluteAccuracy());
+        // 5 iterations on i586 JDK 1.4.1.
+        assertTrue(solver.getIterationCount() <= 6);
+        result = solver.solve(f, -0.3, 0.45);
+        //System.out.println(
+        //    "Root: " + result + " Iterations: " + solver.getIterationCount());
+        assertEquals(result, 0, solver.getAbsoluteAccuracy());
+        // 6 iterations on i586 JDK 1.4.1.
+        assertTrue(solver.getIterationCount() <= 7);
+        result = solver.solve(f, 0.3, 0.7);
+        //System.out.println(
+        //    "Root: " + result + " Iterations: " + solver.getIterationCount());
+        assertEquals(result, 0.5, solver.getAbsoluteAccuracy());
+        // 7 iterations on i586 JDK 1.4.1.
+        assertTrue(solver.getIterationCount() <= 8);
+        result = solver.solve(f, 0.2, 0.6);
+        //System.out.println(
+        //    "Root: " + result + " Iterations: " + solver.getIterationCount());
+        assertEquals(result, 0.5, solver.getAbsoluteAccuracy());
+        // 6 iterations on i586 JDK 1.4.1.
+        assertTrue(solver.getIterationCount() <= 7);
+        result = solver.solve(f, 0.05, 0.95);
+        //System.out.println(
+        //    "Root: " + result + " Iterations: " + solver.getIterationCount());
+        assertEquals(result, 0.5, solver.getAbsoluteAccuracy());
+        // 8 iterations on i586 JDK 1.4.1.
+        assertTrue(solver.getIterationCount() <= 9);
+        result = solver.solve(f, 0.85, 1.25);
+        //System.out.println(
+        //    "Root: " + result + " Iterations: " + solver.getIterationCount());
+        assertEquals(result, 1.0, solver.getAbsoluteAccuracy());
+        // 10 iterations on i586 JDK 1.4.1.
+        assertTrue(solver.getIterationCount() <= 11);
+        result = solver.solve(f, 0.8, 1.2);
+        //System.out.println(
+        //    "Root: " + result + " Iterations: " + solver.getIterationCount());
+        assertEquals(result, 1.0, solver.getAbsoluteAccuracy());
+        // 8 iterations on i586 JDK 1.4.1.
+        assertTrue(solver.getIterationCount() <= 9);
+        result = solver.solve(f, 0.85, 1.75);
+        //System.out.println(
+        //    "Root: " + result + " Iterations: " + solver.getIterationCount());
+        assertEquals(result, 1.0, solver.getAbsoluteAccuracy());
+        // 14 iterations on i586 JDK 1.4.1.
+        assertTrue(solver.getIterationCount() <= 15);
+        // The followig is especially slow because the solver first has to reduce
+        // the bracket to exclude the extremum. After that, convergence is rapide.
+        result = solver.solve(f, 0.55, 1.45);
+        //System.out.println(
+        //    "Root: " + result + " Iterations: " + solver.getIterationCount());
+        assertEquals(result, 1.0, solver.getAbsoluteAccuracy());
+        // 7 iterations on i586 JDK 1.4.1.
+        assertTrue(solver.getIterationCount() <= 8);
+        result = solver.solve(f, 0.85, 5);
+        //System.out.println(
+        //    "Root: " + result + " Iterations: " + solver.getIterationCount());
+        assertEquals(result, 1.0, solver.getAbsoluteAccuracy());
+        // 14 iterations on i586 JDK 1.4.1.
+        assertTrue(solver.getIterationCount() <= 15);
+        // Static solve method
+        result = UnivariateRealSolverUtils.solve(f, -0.2, 0.2);
+        assertEquals(result, 0, solver.getAbsoluteAccuracy());
+        result = UnivariateRealSolverUtils.solve(f, -0.1, 0.3);
+        assertEquals(result, 0, 1E-8);
+        result = UnivariateRealSolverUtils.solve(f, -0.3, 0.45);
+        assertEquals(result, 0, 1E-6);
+        result = UnivariateRealSolverUtils.solve(f, 0.3, 0.7);
+        assertEquals(result, 0.5, 1E-6);
+        result = UnivariateRealSolverUtils.solve(f, 0.2, 0.6);
+        assertEquals(result, 0.5, 1E-6);
+        result = UnivariateRealSolverUtils.solve(f, 0.05, 0.95);
+        assertEquals(result, 0.5, 1E-6);
+        result = UnivariateRealSolverUtils.solve(f, 0.85, 1.25);
+        assertEquals(result, 1.0, 1E-6);
+        result = UnivariateRealSolverUtils.solve(f, 0.8, 1.2);
+        assertEquals(result, 1.0, 1E-6);
+        result = UnivariateRealSolverUtils.solve(f, 0.85, 1.75);
+        assertEquals(result, 1.0, 1E-6);
+        result = UnivariateRealSolverUtils.solve(f, 0.55, 1.45);
+        assertEquals(result, 1.0, 1E-6);
+        result = UnivariateRealSolverUtils.solve(f, 0.85, 5);
+        assertEquals(result, 1.0, 1E-6);
+    }
+
+    public void testRootEndpoints() throws Exception {
+        UnivariateRealFunction f = new SinFunction();
+        UnivariateRealSolver solver = new BrentSolver();
+
+        // endpoint is root
+        double result = solver.solve(f, FastMath.PI, 4);
+        assertEquals(FastMath.PI, result, solver.getAbsoluteAccuracy());
+
+        result = solver.solve(f, 3, FastMath.PI);
+        assertEquals(FastMath.PI, result, solver.getAbsoluteAccuracy());
+
+        result = solver.solve(f, FastMath.PI, 4, 3.5);
+        assertEquals(FastMath.PI, result, solver.getAbsoluteAccuracy());
+
+        result = solver.solve(f, 3, FastMath.PI, 3.07);
+        assertEquals(FastMath.PI, result, solver.getAbsoluteAccuracy());
+
+    }
+
+    public void testBadEndpoints() throws Exception {
+        UnivariateRealFunction f = new SinFunction();
+        UnivariateRealSolver solver = new BrentSolver();
+        try {  // bad interval
+            solver.solve(f, 1, -1);
+            fail("Expecting IllegalArgumentException - bad interval");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {  // no bracket
+            solver.solve(f, 1, 1.5);
+            fail("Expecting IllegalArgumentException - non-bracketing");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {  // no bracket
+            solver.solve(f, 1, 1.5, 1.2);
+            fail("Expecting IllegalArgumentException - non-bracketing");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+
+    public void testInitialGuess() throws MathException {
+
+        MonitoredFunction f = new MonitoredFunction(new QuinticFunction());
+        UnivariateRealSolver solver = new BrentSolver();
+        double result;
+
+        // no guess
+        result = solver.solve(f, 0.6, 7.0);
+        assertEquals(result, 1.0, solver.getAbsoluteAccuracy());
+        int referenceCallsCount = f.getCallsCount();
+        assertTrue(referenceCallsCount >= 13);
+
+        // invalid guess (it *is* a root, but outside of the range)
+        try {
+          result = solver.solve(f, 0.6, 7.0, 0.0);
+          fail("an IllegalArgumentException was expected");
+        } catch (IllegalArgumentException iae) {
+            // expected behaviour
+        }
+
+        // bad guess
+        f.setCallsCount(0);
+        result = solver.solve(f, 0.6, 7.0, 0.61);
+        assertEquals(result, 1.0, solver.getAbsoluteAccuracy());
+        assertTrue(f.getCallsCount() > referenceCallsCount);
+
+        // good guess
+        f.setCallsCount(0);
+        result = solver.solve(f, 0.6, 7.0, 0.999999);
+        assertEquals(result, 1.0, solver.getAbsoluteAccuracy());
+        assertTrue(f.getCallsCount() < referenceCallsCount);
+
+        // perfect guess
+        f.setCallsCount(0);
+        result = solver.solve(f, 0.6, 7.0, 1.0);
+        assertEquals(result, 1.0, solver.getAbsoluteAccuracy());
+        assertEquals(0, solver.getIterationCount());
+        assertEquals(1, f.getCallsCount());
+
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/analysis/solvers/LaguerreSolverTest.java b/src/test/java/org/apache/commons/math/analysis/solvers/LaguerreSolverTest.java
new file mode 100644
index 0000000..53584b5
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/solvers/LaguerreSolverTest.java
@@ -0,0 +1,199 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.analysis.solvers;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.analysis.SinFunction;
+import org.apache.commons.math.analysis.polynomials.PolynomialFunction;
+import org.apache.commons.math.complex.Complex;
+import org.apache.commons.math.util.FastMath;
+
+import junit.framework.TestCase;
+
+/**
+ * Testcase for Laguerre solver.
+ * <p>
+ * Laguerre's method is very efficient in solving polynomials. Test runs
+ * show that for a default absolute accuracy of 1E-6, it generally takes
+ * less than 5 iterations to find one root, provided solveAll() is not
+ * invoked, and 15 to 20 iterations to find all roots for quintic function.
+ *
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ */
+public final class LaguerreSolverTest extends TestCase {
+
+    /**
+     * Test deprecated APIs.
+     */
+    @Deprecated
+    public void testDeprecated() throws MathException {
+        double min, max, expected, result, tolerance;
+
+        // p(x) = 4x - 1
+        double coefficients[] = { -1.0, 4.0 };
+        PolynomialFunction f = new PolynomialFunction(coefficients);
+        UnivariateRealSolver solver = new LaguerreSolver(f);
+
+        min = 0.0; max = 1.0; expected = 0.25;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve(min, max);
+        assertEquals(expected, result, tolerance);
+    }
+
+    /**
+     * Test of solver for the linear function.
+     */
+    public void testLinearFunction() throws MathException {
+        double min, max, expected, result, tolerance;
+
+        // p(x) = 4x - 1
+        double coefficients[] = { -1.0, 4.0 };
+        PolynomialFunction f = new PolynomialFunction(coefficients);
+        UnivariateRealSolver solver = new LaguerreSolver();
+
+        min = 0.0; max = 1.0; expected = 0.25;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve(f, min, max);
+        assertEquals(expected, result, tolerance);
+    }
+
+    /**
+     * Test of solver for the quadratic function.
+     */
+    public void testQuadraticFunction() throws MathException {
+        double min, max, expected, result, tolerance;
+
+        // p(x) = 2x^2 + 5x - 3 = (x+3)(2x-1)
+        double coefficients[] = { -3.0, 5.0, 2.0 };
+        PolynomialFunction f = new PolynomialFunction(coefficients);
+        UnivariateRealSolver solver = new LaguerreSolver();
+
+        min = 0.0; max = 2.0; expected = 0.5;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve(f, min, max);
+        assertEquals(expected, result, tolerance);
+
+        min = -4.0; max = -1.0; expected = -3.0;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve(f, min, max);
+        assertEquals(expected, result, tolerance);
+    }
+
+    /**
+     * Test of solver for the quintic function.
+     */
+    public void testQuinticFunction() throws MathException {
+        double min, max, expected, result, tolerance;
+
+        // p(x) = x^5 - x^4 - 12x^3 + x^2 - x - 12 = (x+1)(x+3)(x-4)(x^2-x+1)
+        double coefficients[] = { -12.0, -1.0, 1.0, -12.0, -1.0, 1.0 };
+        PolynomialFunction f = new PolynomialFunction(coefficients);
+        UnivariateRealSolver solver = new LaguerreSolver();
+
+        min = -2.0; max = 2.0; expected = -1.0;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve(f, min, max);
+        assertEquals(expected, result, tolerance);
+
+        min = -5.0; max = -2.5; expected = -3.0;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve(f, min, max);
+        assertEquals(expected, result, tolerance);
+
+        min = 3.0; max = 6.0; expected = 4.0;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve(f, min, max);
+        assertEquals(expected, result, tolerance);
+    }
+
+    /**
+     * Test of solver for the quintic function using solveAll().
+     */
+    public void testQuinticFunction2() throws MathException {
+        double initial = 0.0, tolerance;
+        Complex expected, result[];
+
+        // p(x) = x^5 + 4x^3 + x^2 + 4 = (x+1)(x^2-x+1)(x^2+4)
+        double coefficients[] = { 4.0, 0.0, 1.0, 4.0, 0.0, 1.0 };
+        LaguerreSolver solver = new LaguerreSolver();
+        result = solver.solveAll(coefficients, initial);
+
+        expected = new Complex(0.0, -2.0);
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected.abs() * solver.getRelativeAccuracy()));
+        TestUtils.assertContains(result, expected, tolerance);
+
+        expected = new Complex(0.0, 2.0);
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected.abs() * solver.getRelativeAccuracy()));
+        TestUtils.assertContains(result, expected, tolerance);
+
+        expected = new Complex(0.5, 0.5 * FastMath.sqrt(3.0));
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected.abs() * solver.getRelativeAccuracy()));
+        TestUtils.assertContains(result, expected, tolerance);
+
+        expected = new Complex(-1.0, 0.0);
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected.abs() * solver.getRelativeAccuracy()));
+        TestUtils.assertContains(result, expected, tolerance);
+
+        expected = new Complex(0.5, -0.5 * FastMath.sqrt(3.0));
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected.abs() * solver.getRelativeAccuracy()));
+        TestUtils.assertContains(result, expected, tolerance);
+    }
+
+    /**
+     * Test of parameters for the solver.
+     */
+    public void testParameters() throws Exception {
+        double coefficients[] = { -3.0, 5.0, 2.0 };
+        PolynomialFunction f = new PolynomialFunction(coefficients);
+        UnivariateRealSolver solver = new LaguerreSolver();
+
+        try {
+            // bad interval
+            solver.solve(f, 1, -1);
+            fail("Expecting IllegalArgumentException - bad interval");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            // no bracketing
+            solver.solve(f, 2, 3);
+            fail("Expecting IllegalArgumentException - no bracketing");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            // bad function
+            solver.solve(new SinFunction(), -1, 1);
+            fail("Expecting IllegalArgumentException - bad function");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/analysis/solvers/MullerSolverTest.java b/src/test/java/org/apache/commons/math/analysis/solvers/MullerSolverTest.java
new file mode 100644
index 0000000..b8ae755
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/solvers/MullerSolverTest.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.analysis.solvers;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.analysis.Expm1Function;
+import org.apache.commons.math.analysis.QuinticFunction;
+import org.apache.commons.math.analysis.SinFunction;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.util.FastMath;
+
+import junit.framework.TestCase;
+
+/**
+ * Testcase for Muller solver.
+ * <p>
+ * Muller's method converges almost quadratically near roots, but it can
+ * be very slow in regions far away from zeros. Test runs show that for
+ * reasonably good initial values, for a default absolute accuracy of 1E-6,
+ * it generally takes 5 to 10 iterations for the solver to converge.
+ * <p>
+ * Tests for the exponential function illustrate the situations where
+ * Muller solver performs poorly.
+ *
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ */
+public final class MullerSolverTest extends TestCase {
+
+    /**
+     * Test deprecated APIs.
+     */
+    @Deprecated
+    public void testDeprecated() throws MathException {
+        UnivariateRealFunction f = new SinFunction();
+        UnivariateRealSolver solver = new MullerSolver(f);
+        double min, max, expected, result, tolerance;
+
+        min = 3.0; max = 4.0; expected = FastMath.PI;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve(min, max);
+        assertEquals(expected, result, tolerance);
+
+        min = -1.0; max = 1.5; expected = 0.0;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve(min, max);
+        assertEquals(expected, result, tolerance);
+    }
+
+    /**
+     * Test deprecated APIs.
+     */
+    @Deprecated
+    public void testDeprecated2() throws MathException {
+        UnivariateRealFunction f = new QuinticFunction();
+        MullerSolver solver = new MullerSolver(f);
+        double min, max, expected, result, tolerance;
+
+        min = -0.4; max = 0.2; expected = 0.0;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve2(min, max);
+        assertEquals(expected, result, tolerance);
+
+        min = 0.75; max = 1.5; expected = 1.0;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve2(min, max);
+        assertEquals(expected, result, tolerance);
+
+        min = -0.9; max = -0.2; expected = -0.5;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve2(min, max);
+        assertEquals(expected, result, tolerance);
+    }
+
+    /**
+     * Test of solver for the sine function.
+     */
+    public void testSinFunction() throws MathException {
+        UnivariateRealFunction f = new SinFunction();
+        UnivariateRealSolver solver = new MullerSolver();
+        double min, max, expected, result, tolerance;
+
+        min = 3.0; max = 4.0; expected = FastMath.PI;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve(f, min, max);
+        assertEquals(expected, result, tolerance);
+
+        min = -1.0; max = 1.5; expected = 0.0;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve(f, min, max);
+        assertEquals(expected, result, tolerance);
+    }
+
+    /**
+     * Test of solver for the sine function using solve2().
+     */
+    public void testSinFunction2() throws MathException {
+        UnivariateRealFunction f = new SinFunction();
+        MullerSolver solver = new MullerSolver();
+        double min, max, expected, result, tolerance;
+
+        min = 3.0; max = 4.0; expected = FastMath.PI;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve2(f, min, max);
+        assertEquals(expected, result, tolerance);
+
+        min = -1.0; max = 1.5; expected = 0.0;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve2(f, min, max);
+        assertEquals(expected, result, tolerance);
+    }
+
+    /**
+     * Test of solver for the quintic function.
+     */
+    public void testQuinticFunction() throws MathException {
+        UnivariateRealFunction f = new QuinticFunction();
+        UnivariateRealSolver solver = new MullerSolver();
+        double min, max, expected, result, tolerance;
+
+        min = -0.4; max = 0.2; expected = 0.0;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve(f, min, max);
+        assertEquals(expected, result, tolerance);
+
+        min = 0.75; max = 1.5; expected = 1.0;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve(f, min, max);
+        assertEquals(expected, result, tolerance);
+
+        min = -0.9; max = -0.2; expected = -0.5;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve(f, min, max);
+        assertEquals(expected, result, tolerance);
+    }
+
+    /**
+     * Test of solver for the quintic function using solve2().
+     */
+    public void testQuinticFunction2() throws MathException {
+        UnivariateRealFunction f = new QuinticFunction();
+        MullerSolver solver = new MullerSolver();
+        double min, max, expected, result, tolerance;
+
+        min = -0.4; max = 0.2; expected = 0.0;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve2(f, min, max);
+        assertEquals(expected, result, tolerance);
+
+        min = 0.75; max = 1.5; expected = 1.0;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve2(f, min, max);
+        assertEquals(expected, result, tolerance);
+
+        min = -0.9; max = -0.2; expected = -0.5;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve2(f, min, max);
+        assertEquals(expected, result, tolerance);
+    }
+
+    /**
+     * Test of solver for the exponential function.
+     * <p>
+     * It takes 10 to 15 iterations for the last two tests to converge.
+     * In fact, if not for the bisection alternative, the solver would
+     * exceed the default maximal iteration of 100.
+     */
+    public void testExpm1Function() throws MathException {
+        UnivariateRealFunction f = new Expm1Function();
+        UnivariateRealSolver solver = new MullerSolver();
+        double min, max, expected, result, tolerance;
+
+        min = -1.0; max = 2.0; expected = 0.0;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve(f, min, max);
+        assertEquals(expected, result, tolerance);
+
+        min = -20.0; max = 10.0; expected = 0.0;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve(f, min, max);
+        assertEquals(expected, result, tolerance);
+
+        min = -50.0; max = 100.0; expected = 0.0;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve(f, min, max);
+        assertEquals(expected, result, tolerance);
+    }
+
+    /**
+     * Test of solver for the exponential function using solve2().
+     * <p>
+     * It takes 25 to 50 iterations for the last two tests to converge.
+     */
+    public void testExpm1Function2() throws MathException {
+        UnivariateRealFunction f = new Expm1Function();
+        MullerSolver solver = new MullerSolver();
+        double min, max, expected, result, tolerance;
+
+        min = -1.0; max = 2.0; expected = 0.0;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve2(f, min, max);
+        assertEquals(expected, result, tolerance);
+
+        min = -20.0; max = 10.0; expected = 0.0;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve2(f, min, max);
+        assertEquals(expected, result, tolerance);
+
+        min = -50.0; max = 100.0; expected = 0.0;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve2(f, min, max);
+        assertEquals(expected, result, tolerance);
+    }
+
+    /**
+     * Test of parameters for the solver.
+     */
+    public void testParameters() throws Exception {
+        UnivariateRealFunction f = new SinFunction();
+        UnivariateRealSolver solver = new MullerSolver();
+
+        try {
+            // bad interval
+            solver.solve(f, 1, -1);
+            fail("Expecting IllegalArgumentException - bad interval");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            // no bracketing
+            solver.solve(f, 2, 3);
+            fail("Expecting IllegalArgumentException - no bracketing");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/analysis/solvers/NewtonSolverTest.java b/src/test/java/org/apache/commons/math/analysis/solvers/NewtonSolverTest.java
new file mode 100644
index 0000000..e7966fe
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/solvers/NewtonSolverTest.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.analysis.solvers;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.analysis.DifferentiableUnivariateRealFunction;
+import org.apache.commons.math.analysis.QuinticFunction;
+import org.apache.commons.math.analysis.SinFunction;
+import org.apache.commons.math.util.FastMath;
+
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ */
+public final class NewtonSolverTest extends TestCase {
+
+    @Deprecated
+    public void testDeprecated() throws MathException {
+        DifferentiableUnivariateRealFunction f = new SinFunction();
+        double result;
+
+        UnivariateRealSolver solver = new NewtonSolver(f);
+        result = solver.solve(3, 4);
+        assertEquals(result, FastMath.PI, solver.getAbsoluteAccuracy());
+
+        result = solver.solve(1, 4);
+        assertEquals(result, FastMath.PI, solver.getAbsoluteAccuracy());
+
+        assertEquals(result, solver.getResult(), 0);
+        assertTrue(solver.getIterationCount() > 0);
+    }
+
+    /**
+    *
+    */
+   public void testSinZero() throws MathException {
+       DifferentiableUnivariateRealFunction f = new SinFunction();
+       double result;
+
+       UnivariateRealSolver solver = new NewtonSolver();
+       result = solver.solve(f, 3, 4);
+       assertEquals(result, FastMath.PI, solver.getAbsoluteAccuracy());
+
+       result = solver.solve(f, 1, 4);
+       assertEquals(result, FastMath.PI, solver.getAbsoluteAccuracy());
+
+       assertEquals(result, solver.getResult(), 0);
+       assertTrue(solver.getIterationCount() > 0);
+   }
+
+   /**
+     *
+     */
+    public void testQuinticZero() throws MathException {
+        DifferentiableUnivariateRealFunction f = new QuinticFunction();
+        double result;
+
+        UnivariateRealSolver solver = new NewtonSolver();
+        result = solver.solve(f, -0.2, 0.2);
+        assertEquals(result, 0, solver.getAbsoluteAccuracy());
+
+        result = solver.solve(f, -0.1, 0.3);
+        assertEquals(result, 0, solver.getAbsoluteAccuracy());
+
+        result = solver.solve(f, -0.3, 0.45);
+        assertEquals(result, 0, solver.getAbsoluteAccuracy());
+
+        result = solver.solve(f, 0.3, 0.7);
+        assertEquals(result, 0.5, solver.getAbsoluteAccuracy());
+
+        result = solver.solve(f, 0.2, 0.6);
+        assertEquals(result, 0.5, solver.getAbsoluteAccuracy());
+
+        result = solver.solve(f, 0.05, 0.95);
+        assertEquals(result, 0.5, solver.getAbsoluteAccuracy());
+
+        result = solver.solve(f, 0.85, 1.25);
+        assertEquals(result, 1.0, solver.getAbsoluteAccuracy());
+
+        result = solver.solve(f, 0.8, 1.2);
+        assertEquals(result, 1.0, solver.getAbsoluteAccuracy());
+
+        result = solver.solve(f, 0.85, 1.75);
+        assertEquals(result, 1.0, solver.getAbsoluteAccuracy());
+
+        result = solver.solve(f, 0.55, 1.45);
+        assertEquals(result, 1.0, solver.getAbsoluteAccuracy());
+
+        result = solver.solve(f, 0.85, 5);
+        assertEquals(result, 1.0, solver.getAbsoluteAccuracy());
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/analysis/solvers/RiddersSolverTest.java b/src/test/java/org/apache/commons/math/analysis/solvers/RiddersSolverTest.java
new file mode 100644
index 0000000..c6d17ef
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/solvers/RiddersSolverTest.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.analysis.solvers;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.analysis.Expm1Function;
+import org.apache.commons.math.analysis.QuinticFunction;
+import org.apache.commons.math.analysis.SinFunction;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.util.FastMath;
+
+import junit.framework.TestCase;
+
+/**
+ * Testcase for Ridders solver.
+ * <p>
+ * Ridders' method converges superlinearly, more specific, its rate of
+ * convergence is sqrt(2). Test runs show that for a default absolute
+ * accuracy of 1E-6, it generally takes less than 5 iterations for close
+ * initial bracket and 5 to 10 iterations for distant initial bracket
+ * to converge.
+ *
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ */
+public final class RiddersSolverTest extends TestCase {
+
+    /**
+     * Test the deprecated APIs.
+     */
+    @Deprecated
+    public void testDeprecated() throws MathException {
+        UnivariateRealFunction f = new SinFunction();
+        UnivariateRealSolver solver = new RiddersSolver(f);
+        double min, max, expected, result, tolerance;
+
+        min = 3.0; max = 4.0; expected = FastMath.PI;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve(min, max);
+        assertEquals(expected, result, tolerance);
+
+        min = -1.0; max = 1.5; expected = 0.0;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve(min, max);
+        assertEquals(expected, result, tolerance);
+    }
+
+    /**
+     * Test of solver for the sine function.
+     */
+    public void testSinFunction() throws MathException {
+        UnivariateRealFunction f = new SinFunction();
+        UnivariateRealSolver solver = new RiddersSolver();
+        double min, max, expected, result, tolerance;
+
+        min = 3.0; max = 4.0; expected = FastMath.PI;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve(f, min, max);
+        assertEquals(expected, result, tolerance);
+
+        min = -1.0; max = 1.5; expected = 0.0;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve(f, min, max);
+        assertEquals(expected, result, tolerance);
+    }
+
+    /**
+     * Test of solver for the quintic function.
+     */
+    public void testQuinticFunction() throws MathException {
+        UnivariateRealFunction f = new QuinticFunction();
+        UnivariateRealSolver solver = new RiddersSolver();
+        double min, max, expected, result, tolerance;
+
+        min = -0.4; max = 0.2; expected = 0.0;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve(f, min, max);
+        assertEquals(expected, result, tolerance);
+
+        min = 0.75; max = 1.5; expected = 1.0;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve(f, min, max);
+        assertEquals(expected, result, tolerance);
+
+        min = -0.9; max = -0.2; expected = -0.5;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve(f, min, max);
+        assertEquals(expected, result, tolerance);
+    }
+
+    /**
+     * Test of solver for the exponential function.
+     */
+    public void testExpm1Function() throws MathException {
+        UnivariateRealFunction f = new Expm1Function();
+        UnivariateRealSolver solver = new RiddersSolver();
+        double min, max, expected, result, tolerance;
+
+        min = -1.0; max = 2.0; expected = 0.0;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve(f, min, max);
+        assertEquals(expected, result, tolerance);
+
+        min = -20.0; max = 10.0; expected = 0.0;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve(f, min, max);
+        assertEquals(expected, result, tolerance);
+
+        min = -50.0; max = 100.0; expected = 0.0;
+        tolerance = FastMath.max(solver.getAbsoluteAccuracy(),
+                    FastMath.abs(expected * solver.getRelativeAccuracy()));
+        result = solver.solve(f, min, max);
+        assertEquals(expected, result, tolerance);
+    }
+
+    /**
+     * Test of parameters for the solver.
+     */
+    public void testParameters() throws Exception {
+        UnivariateRealFunction f = new SinFunction();
+        UnivariateRealSolver solver = new RiddersSolver();
+
+        try {
+            // bad interval
+            solver.solve(f, 1, -1);
+            fail("Expecting IllegalArgumentException - bad interval");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            // no bracketing
+            solver.solve(f, 2, 3);
+            fail("Expecting IllegalArgumentException - no bracketing");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/analysis/solvers/UnivariateRealSolverFactoryImplTest.java b/src/test/java/org/apache/commons/math/analysis/solvers/UnivariateRealSolverFactoryImplTest.java
new file mode 100644
index 0000000..ad786d1
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/solvers/UnivariateRealSolverFactoryImplTest.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.solvers;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+public class UnivariateRealSolverFactoryImplTest extends TestCase {
+
+    /** solver factory */
+    private UnivariateRealSolverFactory factory;
+
+    /**
+     * @throws java.lang.Exception
+     * @see junit.framework.TestCase#tearDown()
+     */
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        factory = new UnivariateRealSolverFactoryImpl();
+    }
+
+    /**
+     * @throws java.lang.Exception
+     * @see junit.framework.TestCase#tearDown()
+     */
+    @Override
+    protected void tearDown() throws Exception {
+        factory = null;
+        super.tearDown();
+    }
+
+    public void testNewBisectionSolverValid() {
+        UnivariateRealSolver solver = factory.newBisectionSolver();
+        assertNotNull(solver);
+        assertTrue(solver instanceof BisectionSolver);
+    }
+
+    public void testNewNewtonSolverValid() {
+        UnivariateRealSolver solver = factory.newNewtonSolver();
+        assertNotNull(solver);
+        assertTrue(solver instanceof NewtonSolver);
+    }
+
+    public void testNewBrentSolverValid() {
+        UnivariateRealSolver solver = factory.newBrentSolver();
+        assertNotNull(solver);
+        assertTrue(solver instanceof BrentSolver);
+    }
+
+    public void testNewSecantSolverValid() {
+        UnivariateRealSolver solver = factory.newSecantSolver();
+        assertNotNull(solver);
+        assertTrue(solver instanceof SecantSolver);
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/analysis/solvers/UnivariateRealSolverUtilsTest.java b/src/test/java/org/apache/commons/math/analysis/solvers/UnivariateRealSolverUtilsTest.java
new file mode 100644
index 0000000..7bab7cf
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/analysis/solvers/UnivariateRealSolverUtilsTest.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.analysis.solvers;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.analysis.SinFunction;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * @version $Revision: 1003901 $ $Date: 2010-10-03 00:11:04 +0200 (dim. 03 oct. 2010) $
+ */
+public class UnivariateRealSolverUtilsTest extends TestCase {
+
+    protected UnivariateRealFunction sin = new SinFunction();
+
+    public void testSolveNull() throws MathException {
+        try {
+            UnivariateRealSolverUtils.solve(null, 0.0, 4.0);
+            fail();
+        } catch(IllegalArgumentException ex){
+            // success
+        }
+    }
+
+    public void testSolveBadEndpoints() throws MathException {
+        try { // bad endpoints
+            UnivariateRealSolverUtils.solve(sin, -0.1, 4.0, 4.0);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+
+    public void testSolveBadAccuracy() throws MathException {
+        try { // bad accuracy
+            UnivariateRealSolverUtils.solve(sin, 0.0, 4.0, 0.0);
+//             fail("Expecting IllegalArgumentException"); // TODO needs rework since convergence behaviour was changed
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+
+    public void testSolveSin() throws MathException {
+        double x = UnivariateRealSolverUtils.solve(sin, 1.0, 4.0);
+        assertEquals(FastMath.PI, x, 1.0e-4);
+    }
+
+    public void testSolveAccuracyNull()  throws MathException {
+        try {
+            double accuracy = 1.0e-6;
+            UnivariateRealSolverUtils.solve(null, 0.0, 4.0, accuracy);
+            fail();
+        } catch(IllegalArgumentException ex){
+            // success
+        }
+    }
+
+    public void testSolveAccuracySin() throws MathException {
+        double accuracy = 1.0e-6;
+        double x = UnivariateRealSolverUtils.solve(sin, 1.0,
+                4.0, accuracy);
+        assertEquals(FastMath.PI, x, accuracy);
+    }
+
+    public void testSolveNoRoot() throws MathException {
+        try {
+            UnivariateRealSolverUtils.solve(sin, 1.0, 1.5);
+            fail("Expecting IllegalArgumentException ");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+
+    public void testBracketSin() throws MathException {
+        double[] result = UnivariateRealSolverUtils.bracket(sin,
+                0.0, -2.0, 2.0);
+        assertTrue(sin.value(result[0]) < 0);
+        assertTrue(sin.value(result[1]) > 0);
+    }
+
+    public void testBracketEndpointRoot() throws MathException {
+        double[] result = UnivariateRealSolverUtils.bracket(sin, 1.5, 0, 2.0);
+        assertEquals(0.0, sin.value(result[0]), 1.0e-15);
+        assertTrue(sin.value(result[1]) > 0);
+    }
+
+    public void testNullFunction() throws MathException {
+        try { // null function
+            UnivariateRealSolverUtils.bracket(null, 1.5, 0, 2.0);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+    
+    public void testBadInitial() throws MathException {
+        try { // initial not between endpoints
+            UnivariateRealSolverUtils.bracket(sin, 2.5, 0, 2.0);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+    
+    public void testBadEndpoints() throws MathException {
+        try { // endpoints not valid
+            UnivariateRealSolverUtils.bracket(sin, 1.5, 2.0, 1.0);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+    
+    public void testBadMaximumIterations() throws MathException {
+        try { // bad maximum iterations
+            UnivariateRealSolverUtils.bracket(sin, 1.5, 0, 2.0, 0);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/complex/ComplexFieldTest.java b/src/test/java/org/apache/commons/math/complex/ComplexFieldTest.java
new file mode 100644
index 0000000..dc6c0f3
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/complex/ComplexFieldTest.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.complex;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.commons.math.TestUtils;
+import org.junit.Test;
+
+public class ComplexFieldTest {
+
+    @Test
+    public void testZero() {
+        assertEquals(Complex.ZERO, ComplexField.getInstance().getZero());
+    }
+
+    @Test
+    public void testOne() {
+        assertEquals(Complex.ONE, ComplexField.getInstance().getOne());
+    }
+
+    @Test
+    public void testSerial() {
+        // deserializing the singleton should give the singleton itself back
+        ComplexField field = ComplexField.getInstance();
+        assertTrue(field == TestUtils.serializeAndRecover(field));
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/complex/ComplexFormatAbstractTest.java b/src/test/java/org/apache/commons/math/complex/ComplexFormatAbstractTest.java
new file mode 100644
index 0000000..b09a045
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/complex/ComplexFormatAbstractTest.java
@@ -0,0 +1,362 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.complex;
+
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.util.Locale;
+
+import org.apache.commons.math.util.CompositeFormat;
+import org.apache.commons.math.util.FastMath;
+
+import junit.framework.TestCase;
+
+public abstract class ComplexFormatAbstractTest extends TestCase {
+
+    CompositeFormat complexFormat = null;
+    ComplexFormat complexFormatJ = null;
+
+    protected abstract Locale getLocale();
+
+    protected abstract char getDecimalCharacter();
+
+    @Override
+    protected void setUp() throws Exception {
+        complexFormat = ComplexFormat.getInstance(getLocale());
+        complexFormatJ = ComplexFormat.getInstance(getLocale());
+        complexFormatJ.setImaginaryCharacter("j");
+    }
+
+    public void testSimpleNoDecimals() {
+        Complex c = new Complex(1, 1);
+        String expected = "1 + 1i";
+        String actual = complexFormat.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void testSimpleWithDecimals() {
+        Complex c = new Complex(1.23, 1.43);
+        String expected = "1" + getDecimalCharacter() + "23 + 1" + getDecimalCharacter() + "43i";
+        String actual = complexFormat.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void testSimpleWithDecimalsTrunc() {
+        Complex c = new Complex(1.2323, 1.4343);
+        String expected = "1" + getDecimalCharacter() + "23 + 1" + getDecimalCharacter() + "43i";
+        String actual = complexFormat.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void testNegativeReal() {
+        Complex c = new Complex(-1.2323, 1.4343);
+        String expected = "-1" + getDecimalCharacter() + "23 + 1" + getDecimalCharacter() + "43i";
+        String actual = complexFormat.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void testNegativeImaginary() {
+        Complex c = new Complex(1.2323, -1.4343);
+        String expected = "1" + getDecimalCharacter() + "23 - 1" + getDecimalCharacter() + "43i";
+        String actual = complexFormat.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void testNegativeBoth() {
+        Complex c = new Complex(-1.2323, -1.4343);
+        String expected = "-1" + getDecimalCharacter() + "23 - 1" + getDecimalCharacter() + "43i";
+        String actual = complexFormat.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void testZeroReal() {
+        Complex c = new Complex(0.0, -1.4343);
+        String expected = "0 - 1" + getDecimalCharacter() + "43i";
+        String actual = complexFormat.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void testZeroImaginary() {
+        Complex c = new Complex(30.233, 0);
+        String expected = "30" + getDecimalCharacter() + "23";
+        String actual = complexFormat.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void testDifferentImaginaryChar() {
+        Complex c = new Complex(1, 1);
+        String expected = "1 + 1j";
+        String actual = complexFormatJ.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void testStaticFormatComplex() {
+        Locale defaultLocal = Locale.getDefault();
+        Locale.setDefault(getLocale());
+
+        Complex c = new Complex(232.222, -342.33);
+        String expected = "232" + getDecimalCharacter() + "22 - 342" + getDecimalCharacter() + "33i";
+        String actual = ComplexFormat.formatComplex(c);
+        assertEquals(expected, actual);
+
+        Locale.setDefault(defaultLocal);
+    }
+
+    public void testNan() {
+        Complex c = new Complex(Double.NaN, Double.NaN);
+        String expected = "(NaN) + (NaN)i";
+        String actual = complexFormat.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void testPositiveInfinity() {
+        Complex c = new Complex(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
+        String expected = "(Infinity) + (Infinity)i";
+        String actual = complexFormat.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void testNegativeInfinity() {
+        Complex c = new Complex(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
+        String expected = "(-Infinity) - (Infinity)i";
+        String actual = complexFormat.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void testParseSimpleNoDecimals() {
+        String source = "1 + 1i";
+        Complex expected = new Complex(1, 1);
+        try {
+            Complex actual = (Complex)complexFormat.parseObject(source);
+            assertEquals(expected, actual);
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParseSimpleWithDecimals() {
+        String source = "1" + getDecimalCharacter() + "23 + 1" + getDecimalCharacter() + "43i";
+        Complex expected = new Complex(1.23, 1.43);
+        try {
+            Complex actual = (Complex)complexFormat.parseObject(source);
+            assertEquals(expected, actual);
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParseSimpleWithDecimalsTrunc() {
+        String source = "1" + getDecimalCharacter() + "2323 + 1" + getDecimalCharacter() + "4343i";
+        Complex expected = new Complex(1.2323, 1.4343);
+        try {
+            Complex actual = (Complex)complexFormat.parseObject(source);
+            assertEquals(expected, actual);
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParseNegativeReal() {
+        String source = "-1" + getDecimalCharacter() + "2323 + 1" + getDecimalCharacter() + "4343i";
+        Complex expected = new Complex(-1.2323, 1.4343);
+        try {
+            Complex actual = (Complex)complexFormat.parseObject(source);
+            assertEquals(expected, actual);
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParseNegativeImaginary() {
+        String source = "1" + getDecimalCharacter() + "2323 - 1" + getDecimalCharacter() + "4343i";
+        Complex expected = new Complex(1.2323, -1.4343);
+        try {
+            Complex actual = (Complex)complexFormat.parseObject(source);
+            assertEquals(expected, actual);
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParseNegativeBoth() {
+        String source = "-1" + getDecimalCharacter() + "2323 - 1" + getDecimalCharacter() + "4343i";
+        Complex expected = new Complex(-1.2323, -1.4343);
+        try {
+            Complex actual = (Complex)complexFormat.parseObject(source);
+            assertEquals(expected, actual);
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParseZeroReal() {
+        String source = "0" + getDecimalCharacter() + "0 - 1" + getDecimalCharacter() + "4343i";
+        Complex expected = new Complex(0.0, -1.4343);
+        try {
+            Complex actual = (Complex)complexFormat.parseObject(source);
+            assertEquals(expected, actual);
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParseZeroImaginary() {
+        String source = "-1" + getDecimalCharacter() + "2323";
+        Complex expected = new Complex(-1.2323, 0);
+        try {
+            Complex actual = (Complex)complexFormat.parseObject(source);
+            assertEquals(expected, actual);
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParseDifferentImaginaryChar() {
+        String source = "-1" + getDecimalCharacter() + "2323 - 1" + getDecimalCharacter() + "4343j";
+        Complex expected = new Complex(-1.2323, -1.4343);
+        try {
+            Complex actual = (Complex)complexFormatJ.parseObject(source);
+            assertEquals(expected, actual);
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParseNan() {
+        String source = "(NaN) + (NaN)i";
+        Complex expected = new Complex(Double.NaN, Double.NaN);
+        try {
+            Complex actual = (Complex)complexFormat.parseObject(source);
+            assertEquals(expected, actual);
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParsePositiveInfinity() {
+        String source = "(Infinity) + (Infinity)i";
+        Complex expected = new Complex(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
+        try {
+            Complex actual = (Complex)complexFormat.parseObject(source);
+            assertEquals(expected, actual);
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testPaseNegativeInfinity() {
+        String source = "(-Infinity) - (Infinity)i";
+        Complex expected = new Complex(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
+        try {
+            Complex actual = (Complex)complexFormat.parseObject(source);
+            assertEquals(expected, actual);
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testConstructorSingleFormat() {
+        NumberFormat nf = NumberFormat.getInstance();
+        ComplexFormat cf = new ComplexFormat(nf);
+        assertNotNull(cf);
+        assertEquals(nf, cf.getRealFormat());
+    }
+
+    public void testGetImaginaryFormat() {
+        NumberFormat nf = NumberFormat.getInstance();
+        ComplexFormat cf = new ComplexFormat();
+
+        assertNotSame(nf, cf.getImaginaryFormat());
+        cf.setImaginaryFormat(nf);
+        assertSame(nf, cf.getImaginaryFormat());
+    }
+
+    public void testSetImaginaryFormatNull() {
+        try {
+            ComplexFormat cf = new ComplexFormat();
+            cf.setImaginaryFormat(null);
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // success
+        }
+    }
+
+    public void testSetRealFormatNull() {
+        try {
+            ComplexFormat cf = new ComplexFormat();
+            cf.setRealFormat(null);
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // success
+        }
+    }
+
+    public void testGetRealFormat() {
+        NumberFormat nf = NumberFormat.getInstance();
+        ComplexFormat cf = new ComplexFormat();
+
+        assertNotSame(nf, cf.getRealFormat());
+        cf.setRealFormat(nf);
+        assertSame(nf, cf.getRealFormat());
+    }
+
+    public void testSetImaginaryCharacterNull() {
+        try {
+            ComplexFormat cf = new ComplexFormat();
+            cf.setImaginaryCharacter(null);
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // success
+        }
+    }
+
+    public void testSetImaginaryCharacterEmpty() {
+        try {
+            ComplexFormat cf = new ComplexFormat();
+            cf.setImaginaryCharacter("");
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // success
+        }
+    }
+
+    public void testFormatNumber() {
+        CompositeFormat cf = ComplexFormat.getInstance(getLocale());
+        Double pi = Double.valueOf(FastMath.PI);
+        String text = cf.format(pi);
+        assertEquals("3" + getDecimalCharacter() + "14", text);
+    }
+
+    public void testFormatObject() {
+        try {
+            CompositeFormat cf = new ComplexFormat();
+            Object object = new Object();
+            cf.format(object);
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // success
+        }
+    }
+
+    public void testForgottenImaginaryCharacter() {
+        ParsePosition pos = new ParsePosition(0);
+        assertNull(new ComplexFormat().parse("1 + 1", pos));
+        assertEquals(5, pos.getErrorIndex());
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/complex/ComplexFormatTest.java b/src/test/java/org/apache/commons/math/complex/ComplexFormatTest.java
new file mode 100644
index 0000000..5ae9299
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/complex/ComplexFormatTest.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.complex;
+
+import java.util.Locale;
+
+
+public class ComplexFormatTest extends ComplexFormatAbstractTest {
+    @Override
+    protected char getDecimalCharacter() {
+        return '.';
+    }
+
+    @Override
+    protected Locale getLocale() {
+        return Locale.US;
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/complex/ComplexTest.java b/src/test/java/org/apache/commons/math/complex/ComplexTest.java
new file mode 100644
index 0000000..2564195
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/complex/ComplexTest.java
@@ -0,0 +1,996 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.TestUtils;
+import org.apache.commons.math.util.FastMath;
+
+import java.util.List;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ */
+public class ComplexTest extends TestCase {
+
+
+    private double inf = Double.POSITIVE_INFINITY;
+    private double neginf = Double.NEGATIVE_INFINITY;
+    private double nan = Double.NaN;
+    private double pi = FastMath.PI;
+    private Complex oneInf = new Complex(1, inf);
+    private Complex oneNegInf = new Complex(1, neginf);
+    private Complex infOne = new Complex(inf, 1);
+    private Complex infZero = new Complex(inf, 0);
+    private Complex infNaN = new Complex(inf, nan);
+    private Complex infNegInf = new Complex(inf, neginf);
+    private Complex infInf = new Complex(inf, inf);
+    private Complex negInfInf = new Complex(neginf, inf);
+    private Complex negInfZero = new Complex(neginf, 0);
+    private Complex negInfOne = new Complex(neginf, 1);
+    private Complex negInfNaN = new Complex(neginf, nan);
+    private Complex negInfNegInf = new Complex(neginf, neginf);
+    private Complex oneNaN = new Complex(1, nan);
+    private Complex zeroInf = new Complex(0, inf);
+    private Complex zeroNaN = new Complex(0, nan);
+    private Complex nanInf = new Complex(nan, inf);
+    private Complex nanNegInf = new Complex(nan, neginf);
+    private Complex nanZero = new Complex(nan, 0);
+
+    public void testConstructor() {
+        Complex z = new Complex(3.0, 4.0);
+        assertEquals(3.0, z.getReal(), 1.0e-5);
+        assertEquals(4.0, z.getImaginary(), 1.0e-5);
+    }
+
+    public void testConstructorNaN() {
+        Complex z = new Complex(3.0, Double.NaN);
+        assertTrue(z.isNaN());
+
+        z = new Complex(nan, 4.0);
+        assertTrue(z.isNaN());
+
+        z = new Complex(3.0, 4.0);
+        assertFalse(z.isNaN());
+    }
+
+    public void testAbs() {
+        Complex z = new Complex(3.0, 4.0);
+        assertEquals(5.0, z.abs(), 1.0e-5);
+    }
+
+    public void testAbsNaN() {
+        assertTrue(Double.isNaN(Complex.NaN.abs()));
+        Complex z = new Complex(inf, nan);
+        assertTrue(Double.isNaN(z.abs()));
+    }
+
+    public void testAbsInfinite() {
+        Complex z = new Complex(inf, 0);
+        assertEquals(inf, z.abs(), 0);
+        z = new Complex(0, neginf);
+        assertEquals(inf, z.abs(), 0);
+        z = new Complex(inf, neginf);
+        assertEquals(inf, z.abs(), 0);
+    }
+
+    public void testAdd() {
+        Complex x = new Complex(3.0, 4.0);
+        Complex y = new Complex(5.0, 6.0);
+        Complex z = x.add(y);
+        assertEquals(8.0, z.getReal(), 1.0e-5);
+        assertEquals(10.0, z.getImaginary(), 1.0e-5);
+    }
+
+    public void testAddNaN() {
+        Complex x = new Complex(3.0, 4.0);
+        Complex z = x.add(Complex.NaN);
+        assertTrue(z.isNaN());
+        z = new Complex(1, nan);
+        Complex w = x.add(z);
+        assertEquals(w.getReal(), 4.0, 0);
+        assertTrue(Double.isNaN(w.getImaginary()));
+    }
+
+    public void testAddInfinite() {
+        Complex x = new Complex(1, 1);
+        Complex z = new Complex(inf, 0);
+        Complex w = x.add(z);
+        assertEquals(w.getImaginary(), 1, 0);
+        assertEquals(inf, w.getReal(), 0);
+
+        x = new Complex(neginf, 0);
+        assertTrue(Double.isNaN(x.add(z).getReal()));
+    }
+
+    public void testConjugate() {
+        Complex x = new Complex(3.0, 4.0);
+        Complex z = x.conjugate();
+        assertEquals(3.0, z.getReal(), 1.0e-5);
+        assertEquals(-4.0, z.getImaginary(), 1.0e-5);
+    }
+
+    public void testConjugateNaN() {
+        Complex z = Complex.NaN.conjugate();
+        assertTrue(z.isNaN());
+    }
+
+    public void testConjugateInfiinite() {
+        Complex z = new Complex(0, inf);
+        assertEquals(neginf, z.conjugate().getImaginary(), 0);
+        z = new Complex(0, neginf);
+        assertEquals(inf, z.conjugate().getImaginary(), 0);
+    }
+
+    public void testDivide() {
+        Complex x = new Complex(3.0, 4.0);
+        Complex y = new Complex(5.0, 6.0);
+        Complex z = x.divide(y);
+        assertEquals(39.0 / 61.0, z.getReal(), 1.0e-5);
+        assertEquals(2.0 / 61.0, z.getImaginary(), 1.0e-5);
+    }
+
+    public void testDivideReal() {
+        Complex x = new Complex(2d, 3d);
+        Complex y = new Complex(2d, 0d);
+        assertEquals(new Complex(1d, 1.5), x.divide(y));
+
+    }
+
+    public void testDivideImaginary() {
+        Complex x = new Complex(2d, 3d);
+        Complex y = new Complex(0d, 2d);
+        assertEquals(new Complex(1.5d, -1d), x.divide(y));
+    }
+
+    public void testDivideInfinite() {
+        Complex x = new Complex(3, 4);
+        Complex w = new Complex(neginf, inf);
+        assertTrue(x.divide(w).equals(Complex.ZERO));
+
+        Complex z = w.divide(x);
+        assertTrue(Double.isNaN(z.getReal()));
+        assertEquals(inf, z.getImaginary(), 0);
+
+        w = new Complex(inf, inf);
+        z = w.divide(x);
+        assertTrue(Double.isNaN(z.getImaginary()));
+        assertEquals(inf, z.getReal(), 0);
+
+        w = new Complex(1, inf);
+        z = w.divide(w);
+        assertTrue(Double.isNaN(z.getReal()));
+        assertTrue(Double.isNaN(z.getImaginary()));
+    }
+
+    public void testDivideZero() {
+        Complex x = new Complex(3.0, 4.0);
+        Complex z = x.divide(Complex.ZERO);
+        assertEquals(z, Complex.NaN);
+    }
+
+    public void testDivideNaN() {
+        Complex x = new Complex(3.0, 4.0);
+        Complex z = x.divide(Complex.NaN);
+        assertTrue(z.isNaN());
+    }
+
+    public void testDivideNaNInf() {
+       Complex z = oneInf.divide(Complex.ONE);
+       assertTrue(Double.isNaN(z.getReal()));
+       assertEquals(inf, z.getImaginary(), 0);
+
+       z = negInfNegInf.divide(oneNaN);
+       assertTrue(Double.isNaN(z.getReal()));
+       assertTrue(Double.isNaN(z.getImaginary()));
+
+       z = negInfInf.divide(Complex.ONE);
+       assertTrue(Double.isNaN(z.getReal()));
+       assertTrue(Double.isNaN(z.getImaginary()));
+    }
+
+    public void testMultiply() {
+        Complex x = new Complex(3.0, 4.0);
+        Complex y = new Complex(5.0, 6.0);
+        Complex z = x.multiply(y);
+        assertEquals(-9.0, z.getReal(), 1.0e-5);
+        assertEquals(38.0, z.getImaginary(), 1.0e-5);
+    }
+
+    public void testMultiplyNaN() {
+        Complex x = new Complex(3.0, 4.0);
+        Complex z = x.multiply(Complex.NaN);
+        assertTrue(z.isNaN());
+    }
+
+    public void testMultiplyNaNInf() {
+        Complex z = new Complex(1,1);
+        Complex w = z.multiply(infOne);
+        assertEquals(w.getReal(), inf, 0);
+        assertEquals(w.getImaginary(), inf, 0);
+
+        // [MATH-164]
+        assertTrue(new Complex( 1,0).multiply(infInf).equals(Complex.INF));
+        assertTrue(new Complex(-1,0).multiply(infInf).equals(Complex.INF));
+        assertTrue(new Complex( 1,0).multiply(negInfZero).equals(Complex.INF));
+
+        w = oneInf.multiply(oneNegInf);
+        assertEquals(w.getReal(), inf, 0);
+        assertEquals(w.getImaginary(), inf, 0);
+
+        w = negInfNegInf.multiply(oneNaN);
+        assertTrue(Double.isNaN(w.getReal()));
+        assertTrue(Double.isNaN(w.getImaginary()));
+    }
+
+    public void testScalarMultiply() {
+        Complex x = new Complex(3.0, 4.0);
+        double y = 2.0;
+        Complex z = x.multiply(y);
+        assertEquals(6.0, z.getReal(), 1.0e-5);
+        assertEquals(8.0, z.getImaginary(), 1.0e-5);
+    }
+
+    public void testScalarMultiplyNaN() {
+        Complex x = new Complex(3.0, 4.0);
+        Complex z = x.multiply(Double.NaN);
+        assertTrue(z.isNaN());
+    }
+
+    public void testScalarMultiplyInf() {
+        Complex z = new Complex(1,1);
+        Complex w = z.multiply(Double.POSITIVE_INFINITY);
+        assertEquals(w.getReal(), inf, 0);
+        assertEquals(w.getImaginary(), inf, 0);
+
+        w = z.multiply(Double.NEGATIVE_INFINITY);
+        assertEquals(w.getReal(), inf, 0);
+        assertEquals(w.getImaginary(), inf, 0);
+    }
+
+    public void testNegate() {
+        Complex x = new Complex(3.0, 4.0);
+        Complex z = x.negate();
+        assertEquals(-3.0, z.getReal(), 1.0e-5);
+        assertEquals(-4.0, z.getImaginary(), 1.0e-5);
+    }
+
+    public void testNegateNaN() {
+        Complex z = Complex.NaN.negate();
+        assertTrue(z.isNaN());
+    }
+
+    public void testSubtract() {
+        Complex x = new Complex(3.0, 4.0);
+        Complex y = new Complex(5.0, 6.0);
+        Complex z = x.subtract(y);
+        assertEquals(-2.0, z.getReal(), 1.0e-5);
+        assertEquals(-2.0, z.getImaginary(), 1.0e-5);
+    }
+
+    public void testSubtractNaN() {
+        Complex x = new Complex(3.0, 4.0);
+        Complex z = x.subtract(Complex.NaN);
+        assertTrue(z.isNaN());
+    }
+
+    public void testEqualsNull() {
+        Complex x = new Complex(3.0, 4.0);
+        assertFalse(x.equals(null));
+    }
+
+    public void testEqualsClass() {
+        Complex x = new Complex(3.0, 4.0);
+        assertFalse(x.equals(this));
+    }
+
+    public void testEqualsSame() {
+        Complex x = new Complex(3.0, 4.0);
+        assertTrue(x.equals(x));
+    }
+
+    public void testEqualsTrue() {
+        Complex x = new Complex(3.0, 4.0);
+        Complex y = new Complex(3.0, 4.0);
+        assertTrue(x.equals(y));
+    }
+
+    public void testEqualsRealDifference() {
+        Complex x = new Complex(0.0, 0.0);
+        Complex y = new Complex(0.0 + Double.MIN_VALUE, 0.0);
+        assertFalse(x.equals(y));
+    }
+
+    public void testEqualsImaginaryDifference() {
+        Complex x = new Complex(0.0, 0.0);
+        Complex y = new Complex(0.0, 0.0 + Double.MIN_VALUE);
+        assertFalse(x.equals(y));
+    }
+
+    public void testEqualsNaN() {
+        Complex realNaN = new Complex(Double.NaN, 0.0);
+        Complex imaginaryNaN = new Complex(0.0, Double.NaN);
+        Complex complexNaN = Complex.NaN;
+        assertTrue(realNaN.equals(imaginaryNaN));
+        assertTrue(imaginaryNaN.equals(complexNaN));
+        assertTrue(realNaN.equals(complexNaN));
+    }
+
+    public void testHashCode() {
+        Complex x = new Complex(0.0, 0.0);
+        Complex y = new Complex(0.0, 0.0 + Double.MIN_VALUE);
+        assertFalse(x.hashCode()==y.hashCode());
+        y = new Complex(0.0 + Double.MIN_VALUE, 0.0);
+        assertFalse(x.hashCode()==y.hashCode());
+        Complex realNaN = new Complex(Double.NaN, 0.0);
+        Complex imaginaryNaN = new Complex(0.0, Double.NaN);
+        assertEquals(realNaN.hashCode(), imaginaryNaN.hashCode());
+        assertEquals(imaginaryNaN.hashCode(), Complex.NaN.hashCode());
+    }
+
+    public void testAcos() {
+        Complex z = new Complex(3, 4);
+        Complex expected = new Complex(0.936812, -2.30551);
+        TestUtils.assertEquals(expected, z.acos(), 1.0e-5);
+        TestUtils.assertEquals(new Complex(FastMath.acos(0), 0),
+                Complex.ZERO.acos(), 1.0e-12);
+    }
+
+    public void testAcosInf() {
+        TestUtils.assertSame(Complex.NaN, oneInf.acos());
+        TestUtils.assertSame(Complex.NaN, oneNegInf.acos());
+        TestUtils.assertSame(Complex.NaN, infOne.acos());
+        TestUtils.assertSame(Complex.NaN, negInfOne.acos());
+        TestUtils.assertSame(Complex.NaN, infInf.acos());
+        TestUtils.assertSame(Complex.NaN, infNegInf.acos());
+        TestUtils.assertSame(Complex.NaN, negInfInf.acos());
+        TestUtils.assertSame(Complex.NaN, negInfNegInf.acos());
+    }
+
+    public void testAcosNaN() {
+        assertTrue(Complex.NaN.acos().isNaN());
+    }
+
+    public void testAsin() {
+        Complex z = new Complex(3, 4);
+        Complex expected = new Complex(0.633984, 2.30551);
+        TestUtils.assertEquals(expected, z.asin(), 1.0e-5);
+    }
+
+    public void testAsinNaN() {
+        assertTrue(Complex.NaN.asin().isNaN());
+    }
+
+    public void testAsinInf() {
+        TestUtils.assertSame(Complex.NaN, oneInf.asin());
+        TestUtils.assertSame(Complex.NaN, oneNegInf.asin());
+        TestUtils.assertSame(Complex.NaN, infOne.asin());
+        TestUtils.assertSame(Complex.NaN, negInfOne.asin());
+        TestUtils.assertSame(Complex.NaN, infInf.asin());
+        TestUtils.assertSame(Complex.NaN, infNegInf.asin());
+        TestUtils.assertSame(Complex.NaN, negInfInf.asin());
+        TestUtils.assertSame(Complex.NaN, negInfNegInf.asin());
+    }
+
+
+    public void testAtan() {
+        Complex z = new Complex(3, 4);
+        Complex expected = new Complex(1.44831, 0.158997);
+        TestUtils.assertEquals(expected, z.atan(), 1.0e-5);
+    }
+
+    public void testAtanInf() {
+        TestUtils.assertSame(Complex.NaN, oneInf.atan());
+        TestUtils.assertSame(Complex.NaN, oneNegInf.atan());
+        TestUtils.assertSame(Complex.NaN, infOne.atan());
+        TestUtils.assertSame(Complex.NaN, negInfOne.atan());
+        TestUtils.assertSame(Complex.NaN, infInf.atan());
+        TestUtils.assertSame(Complex.NaN, infNegInf.atan());
+        TestUtils.assertSame(Complex.NaN, negInfInf.atan());
+        TestUtils.assertSame(Complex.NaN, negInfNegInf.atan());
+    }
+
+    public void testAtanNaN() {
+        assertTrue(Complex.NaN.atan().isNaN());
+        assertTrue(Complex.I.atan().isNaN());
+    }
+
+    public void testCos() {
+        Complex z = new Complex(3, 4);
+        Complex expected = new Complex(-27.03495, -3.851153);
+        TestUtils.assertEquals(expected, z.cos(), 1.0e-5);
+    }
+
+    public void testCosNaN() {
+        assertTrue(Complex.NaN.cos().isNaN());
+    }
+
+    public void testCosInf() {
+        TestUtils.assertSame(infNegInf, oneInf.cos());
+        TestUtils.assertSame(infInf, oneNegInf.cos());
+        TestUtils.assertSame(Complex.NaN, infOne.cos());
+        TestUtils.assertSame(Complex.NaN, negInfOne.cos());
+        TestUtils.assertSame(Complex.NaN, infInf.cos());
+        TestUtils.assertSame(Complex.NaN, infNegInf.cos());
+        TestUtils.assertSame(Complex.NaN, negInfInf.cos());
+        TestUtils.assertSame(Complex.NaN, negInfNegInf.cos());
+    }
+
+    public void testCosh() {
+        Complex z = new Complex(3, 4);
+        Complex expected = new Complex(-6.58066, -7.58155);
+        TestUtils.assertEquals(expected, z.cosh(), 1.0e-5);
+    }
+
+    public void testCoshNaN() {
+        assertTrue(Complex.NaN.cosh().isNaN());
+    }
+
+    public void testCoshInf() {
+        TestUtils.assertSame(Complex.NaN, oneInf.cosh());
+        TestUtils.assertSame(Complex.NaN, oneNegInf.cosh());
+        TestUtils.assertSame(infInf, infOne.cosh());
+        TestUtils.assertSame(infNegInf, negInfOne.cosh());
+        TestUtils.assertSame(Complex.NaN, infInf.cosh());
+        TestUtils.assertSame(Complex.NaN, infNegInf.cosh());
+        TestUtils.assertSame(Complex.NaN, negInfInf.cosh());
+        TestUtils.assertSame(Complex.NaN, negInfNegInf.cosh());
+    }
+
+    public void testExp() {
+        Complex z = new Complex(3, 4);
+        Complex expected = new Complex(-13.12878, -15.20078);
+        TestUtils.assertEquals(expected, z.exp(), 1.0e-5);
+        TestUtils.assertEquals(Complex.ONE,
+                Complex.ZERO.exp(), 10e-12);
+        Complex iPi = Complex.I.multiply(new Complex(pi,0));
+        TestUtils.assertEquals(Complex.ONE.negate(),
+                iPi.exp(), 10e-12);
+    }
+
+    public void testExpNaN() {
+        assertTrue(Complex.NaN.exp().isNaN());
+    }
+
+    public void testExpInf() {
+        TestUtils.assertSame(Complex.NaN, oneInf.exp());
+        TestUtils.assertSame(Complex.NaN, oneNegInf.exp());
+        TestUtils.assertSame(infInf, infOne.exp());
+        TestUtils.assertSame(Complex.ZERO, negInfOne.exp());
+        TestUtils.assertSame(Complex.NaN, infInf.exp());
+        TestUtils.assertSame(Complex.NaN, infNegInf.exp());
+        TestUtils.assertSame(Complex.NaN, negInfInf.exp());
+        TestUtils.assertSame(Complex.NaN, negInfNegInf.exp());
+    }
+
+    public void testLog() {
+        Complex z = new Complex(3, 4);
+        Complex expected = new Complex(1.60944, 0.927295);
+        TestUtils.assertEquals(expected, z.log(), 1.0e-5);
+    }
+
+    public void testLogNaN() {
+        assertTrue(Complex.NaN.log().isNaN());
+    }
+
+    public void testLogInf() {
+        TestUtils.assertEquals(new Complex(inf, pi / 2),
+                oneInf.log(), 10e-12);
+        TestUtils.assertEquals(new Complex(inf, -pi / 2),
+                oneNegInf.log(), 10e-12);
+        TestUtils.assertEquals(infZero, infOne.log(), 10e-12);
+        TestUtils.assertEquals(new Complex(inf, pi),
+                negInfOne.log(), 10e-12);
+        TestUtils.assertEquals(new Complex(inf, pi / 4),
+                infInf.log(), 10e-12);
+        TestUtils.assertEquals(new Complex(inf, -pi / 4),
+                infNegInf.log(), 10e-12);
+        TestUtils.assertEquals(new Complex(inf, 3d * pi / 4),
+                negInfInf.log(), 10e-12);
+        TestUtils.assertEquals(new Complex(inf, - 3d * pi / 4),
+                negInfNegInf.log(), 10e-12);
+    }
+
+    public void testLogZero() {
+        TestUtils.assertSame(negInfZero, Complex.ZERO.log());
+    }
+
+    public void testPow() {
+        Complex x = new Complex(3, 4);
+        Complex y = new Complex(5, 6);
+        Complex expected = new Complex(-1.860893, 11.83677);
+        TestUtils.assertEquals(expected, x.pow(y), 1.0e-5);
+    }
+
+    public void testPowNaNBase() {
+        Complex x = new Complex(3, 4);
+        assertTrue(Complex.NaN.pow(x).isNaN());
+    }
+
+    public void testPowNaNExponent() {
+        Complex x = new Complex(3, 4);
+        assertTrue(x.pow(Complex.NaN).isNaN());
+    }
+
+   public void testPowInf() {
+       TestUtils.assertSame(Complex.NaN,Complex.ONE.pow(oneInf));
+       TestUtils.assertSame(Complex.NaN,Complex.ONE.pow(oneNegInf));
+       TestUtils.assertSame(Complex.NaN,Complex.ONE.pow(infOne));
+       TestUtils.assertSame(Complex.NaN,Complex.ONE.pow(infInf));
+       TestUtils.assertSame(Complex.NaN,Complex.ONE.pow(infNegInf));
+       TestUtils.assertSame(Complex.NaN,Complex.ONE.pow(negInfInf));
+       TestUtils.assertSame(Complex.NaN,Complex.ONE.pow(negInfNegInf));
+       TestUtils.assertSame(Complex.NaN,infOne.pow(Complex.ONE));
+       TestUtils.assertSame(Complex.NaN,negInfOne.pow(Complex.ONE));
+       TestUtils.assertSame(Complex.NaN,infInf.pow(Complex.ONE));
+       TestUtils.assertSame(Complex.NaN,infNegInf.pow(Complex.ONE));
+       TestUtils.assertSame(Complex.NaN,negInfInf.pow(Complex.ONE));
+       TestUtils.assertSame(Complex.NaN,negInfNegInf.pow(Complex.ONE));
+       TestUtils.assertSame(Complex.NaN,negInfNegInf.pow(infNegInf));
+       TestUtils.assertSame(Complex.NaN,negInfNegInf.pow(negInfNegInf));
+       TestUtils.assertSame(Complex.NaN,negInfNegInf.pow(infInf));
+       TestUtils.assertSame(Complex.NaN,infInf.pow(infNegInf));
+       TestUtils.assertSame(Complex.NaN,infInf.pow(negInfNegInf));
+       TestUtils.assertSame(Complex.NaN,infInf.pow(infInf));
+       TestUtils.assertSame(Complex.NaN,infNegInf.pow(infNegInf));
+       TestUtils.assertSame(Complex.NaN,infNegInf.pow(negInfNegInf));
+       TestUtils.assertSame(Complex.NaN,infNegInf.pow(infInf));
+   }
+
+   public void testPowZero() {
+       TestUtils.assertSame(Complex.NaN,
+               Complex.ZERO.pow(Complex.ONE));
+       TestUtils.assertSame(Complex.NaN,
+               Complex.ZERO.pow(Complex.ZERO));
+       TestUtils.assertSame(Complex.NaN,
+               Complex.ZERO.pow(Complex.I));
+       TestUtils.assertEquals(Complex.ONE,
+               Complex.ONE.pow(Complex.ZERO), 10e-12);
+       TestUtils.assertEquals(Complex.ONE,
+               Complex.I.pow(Complex.ZERO), 10e-12);
+       TestUtils.assertEquals(Complex.ONE,
+               new Complex(-1, 3).pow(Complex.ZERO), 10e-12);
+   }
+
+    public void testpowNull() {
+        try {
+            Complex.ONE.pow(null);
+            fail("Expecting NullPointerException");
+        } catch (NullPointerException ex) {
+            // expected
+        }
+    }
+
+    public void testSin() {
+        Complex z = new Complex(3, 4);
+        Complex expected = new Complex(3.853738, -27.01681);
+        TestUtils.assertEquals(expected, z.sin(), 1.0e-5);
+    }
+
+    public void testSinInf() {
+        TestUtils.assertSame(infInf, oneInf.sin());
+        TestUtils.assertSame(infNegInf, oneNegInf.sin());
+        TestUtils.assertSame(Complex.NaN, infOne.sin());
+        TestUtils.assertSame(Complex.NaN, negInfOne.sin());
+        TestUtils.assertSame(Complex.NaN, infInf.sin());
+        TestUtils.assertSame(Complex.NaN, infNegInf.sin());
+        TestUtils.assertSame(Complex.NaN, negInfInf.sin());
+        TestUtils.assertSame(Complex.NaN, negInfNegInf.sin());
+    }
+
+    public void testSinNaN() {
+        assertTrue(Complex.NaN.sin().isNaN());
+    }
+
+    public void testSinh() {
+        Complex z = new Complex(3, 4);
+        Complex expected = new Complex(-6.54812, -7.61923);
+        TestUtils.assertEquals(expected, z.sinh(), 1.0e-5);
+    }
+
+    public void testSinhNaN() {
+        assertTrue(Complex.NaN.sinh().isNaN());
+    }
+
+    public void testSinhInf() {
+        TestUtils.assertSame(Complex.NaN, oneInf.sinh());
+        TestUtils.assertSame(Complex.NaN, oneNegInf.sinh());
+        TestUtils.assertSame(infInf, infOne.sinh());
+        TestUtils.assertSame(negInfInf, negInfOne.sinh());
+        TestUtils.assertSame(Complex.NaN, infInf.sinh());
+        TestUtils.assertSame(Complex.NaN, infNegInf.sinh());
+        TestUtils.assertSame(Complex.NaN, negInfInf.sinh());
+        TestUtils.assertSame(Complex.NaN, negInfNegInf.sinh());
+    }
+
+    public void testSqrtRealPositive() {
+        Complex z = new Complex(3, 4);
+        Complex expected = new Complex(2, 1);
+        TestUtils.assertEquals(expected, z.sqrt(), 1.0e-5);
+    }
+
+    public void testSqrtRealZero() {
+        Complex z = new Complex(0.0, 4);
+        Complex expected = new Complex(1.41421, 1.41421);
+        TestUtils.assertEquals(expected, z.sqrt(), 1.0e-5);
+    }
+
+    public void testSqrtRealNegative() {
+        Complex z = new Complex(-3.0, 4);
+        Complex expected = new Complex(1, 2);
+        TestUtils.assertEquals(expected, z.sqrt(), 1.0e-5);
+    }
+
+    public void testSqrtImaginaryZero() {
+        Complex z = new Complex(-3.0, 0.0);
+        Complex expected = new Complex(0.0, 1.73205);
+        TestUtils.assertEquals(expected, z.sqrt(), 1.0e-5);
+    }
+
+    public void testSqrtImaginaryNegative() {
+        Complex z = new Complex(-3.0, -4.0);
+        Complex expected = new Complex(1.0, -2.0);
+        TestUtils.assertEquals(expected, z.sqrt(), 1.0e-5);
+    }
+
+    public void testSqrtPolar() {
+        double r = 1;
+        for (int i = 0; i < 5; i++) {
+            r += i;
+            double theta = 0;
+            for (int j =0; j < 11; j++) {
+                theta += pi /12;
+                Complex z = ComplexUtils.polar2Complex(r, theta);
+                Complex sqrtz = ComplexUtils.polar2Complex(FastMath.sqrt(r), theta / 2);
+                TestUtils.assertEquals(sqrtz, z.sqrt(), 10e-12);
+            }
+        }
+    }
+
+    public void testSqrtNaN() {
+        assertTrue(Complex.NaN.sqrt().isNaN());
+    }
+
+    public void testSqrtInf() {
+        TestUtils.assertSame(infNaN, oneInf.sqrt());
+        TestUtils.assertSame(infNaN, oneNegInf.sqrt());
+        TestUtils.assertSame(infZero, infOne.sqrt());
+        TestUtils.assertSame(zeroInf, negInfOne.sqrt());
+        TestUtils.assertSame(infNaN, infInf.sqrt());
+        TestUtils.assertSame(infNaN, infNegInf.sqrt());
+        TestUtils.assertSame(nanInf, negInfInf.sqrt());
+        TestUtils.assertSame(nanNegInf, negInfNegInf.sqrt());
+    }
+
+    public void testSqrt1z() {
+        Complex z = new Complex(3, 4);
+        Complex expected = new Complex(4.08033, -2.94094);
+        TestUtils.assertEquals(expected, z.sqrt1z(), 1.0e-5);
+    }
+
+    public void testSqrt1zNaN() {
+        assertTrue(Complex.NaN.sqrt1z().isNaN());
+    }
+
+    public void testTan() {
+        Complex z = new Complex(3, 4);
+        Complex expected = new Complex(-0.000187346, 0.999356);
+        TestUtils.assertEquals(expected, z.tan(), 1.0e-5);
+    }
+
+    public void testTanNaN() {
+        assertTrue(Complex.NaN.tan().isNaN());
+    }
+
+    public void testTanInf() {
+        TestUtils.assertSame(zeroNaN, oneInf.tan());
+        TestUtils.assertSame(zeroNaN, oneNegInf.tan());
+        TestUtils.assertSame(Complex.NaN, infOne.tan());
+        TestUtils.assertSame(Complex.NaN, negInfOne.tan());
+        TestUtils.assertSame(Complex.NaN, infInf.tan());
+        TestUtils.assertSame(Complex.NaN, infNegInf.tan());
+        TestUtils.assertSame(Complex.NaN, negInfInf.tan());
+        TestUtils.assertSame(Complex.NaN, negInfNegInf.tan());
+    }
+
+   public void testTanCritical() {
+        TestUtils.assertSame(infNaN, new Complex(pi/2, 0).tan());
+        TestUtils.assertSame(negInfNaN, new Complex(-pi/2, 0).tan());
+    }
+
+    public void testTanh() {
+        Complex z = new Complex(3, 4);
+        Complex expected = new Complex(1.00071, 0.00490826);
+        TestUtils.assertEquals(expected, z.tanh(), 1.0e-5);
+    }
+
+    public void testTanhNaN() {
+        assertTrue(Complex.NaN.tanh().isNaN());
+    }
+
+    public void testTanhInf() {
+        TestUtils.assertSame(Complex.NaN, oneInf.tanh());
+        TestUtils.assertSame(Complex.NaN, oneNegInf.tanh());
+        TestUtils.assertSame(nanZero, infOne.tanh());
+        TestUtils.assertSame(nanZero, negInfOne.tanh());
+        TestUtils.assertSame(Complex.NaN, infInf.tanh());
+        TestUtils.assertSame(Complex.NaN, infNegInf.tanh());
+        TestUtils.assertSame(Complex.NaN, negInfInf.tanh());
+        TestUtils.assertSame(Complex.NaN, negInfNegInf.tanh());
+    }
+
+    public void testTanhCritical() {
+        TestUtils.assertSame(nanInf, new Complex(0, pi/2).tanh());
+    }
+
+    /** test issue MATH-221 */
+    public void testMath221() {
+        assertEquals(new Complex(0,-1), new Complex(0,1).multiply(new Complex(-1,0)));
+    }
+
+    /**
+     * Test: computing <b>third roots</b> of z.
+     * <pre>
+     * <code>
+     * <b>z = -2 + 2 * i</b>
+     *   => z_0 =  1      +          i
+     *   => z_1 = -1.3660 + 0.3660 * i
+     *   => z_2 =  0.3660 - 1.3660 * i
+     * </code>
+     * </pre>
+     */
+    public void testNthRoot_normal_thirdRoot() {
+        // The complex number we want to compute all third-roots for.
+        Complex z = new Complex(-2,2);
+        // The List holding all third roots
+        Complex[] thirdRootsOfZ = z.nthRoot(3).toArray(new Complex[0]);
+        // Returned Collection must not be empty!
+        assertEquals(3, thirdRootsOfZ.length);
+        // test z_0
+        assertEquals(1.0,                  thirdRootsOfZ[0].getReal(),      1.0e-5);
+        assertEquals(1.0,                  thirdRootsOfZ[0].getImaginary(), 1.0e-5);
+        // test z_1
+        assertEquals(-1.3660254037844386,  thirdRootsOfZ[1].getReal(),      1.0e-5);
+        assertEquals(0.36602540378443843,  thirdRootsOfZ[1].getImaginary(), 1.0e-5);
+        // test z_2
+        assertEquals(0.366025403784439,    thirdRootsOfZ[2].getReal(),      1.0e-5);
+        assertEquals(-1.3660254037844384,  thirdRootsOfZ[2].getImaginary(), 1.0e-5);
+    }
+
+
+    /**
+     * Test: computing <b>fourth roots</b> of z.
+     * <pre>
+     * <code>
+     * <b>z = 5 - 2 * i</b>
+     *   => z_0 =  1.5164 - 0.1446 * i
+     *   => z_1 =  0.1446 + 1.5164 * i
+     *   => z_2 = -1.5164 + 0.1446 * i
+     *   => z_3 = -1.5164 - 0.1446 * i
+     * </code>
+     * </pre>
+     */
+    public void testNthRoot_normal_fourthRoot() {
+        // The complex number we want to compute all third-roots for.
+        Complex z = new Complex(5,-2);
+        // The List holding all fourth roots
+        Complex[] fourthRootsOfZ = z.nthRoot(4).toArray(new Complex[0]);
+        // Returned Collection must not be empty!
+        assertEquals(4, fourthRootsOfZ.length);
+        // test z_0
+        assertEquals(1.5164629308487783,     fourthRootsOfZ[0].getReal(),      1.0e-5);
+        assertEquals(-0.14469266210702247,   fourthRootsOfZ[0].getImaginary(), 1.0e-5);
+        // test z_1
+        assertEquals(0.14469266210702256,    fourthRootsOfZ[1].getReal(),      1.0e-5);
+        assertEquals(1.5164629308487783,     fourthRootsOfZ[1].getImaginary(), 1.0e-5);
+        // test z_2
+        assertEquals(-1.5164629308487783,    fourthRootsOfZ[2].getReal(),      1.0e-5);
+        assertEquals(0.14469266210702267,    fourthRootsOfZ[2].getImaginary(), 1.0e-5);
+        // test z_3
+        assertEquals(-0.14469266210702275,   fourthRootsOfZ[3].getReal(),      1.0e-5);
+        assertEquals(-1.5164629308487783,    fourthRootsOfZ[3].getImaginary(), 1.0e-5);
+    }
+
+    /**
+     * Test: computing <b>third roots</b> of z.
+     * <pre>
+     * <code>
+     * <b>z = 8</b>
+     *   => z_0 =  2
+     *   => z_1 = -1 + 1.73205 * i
+     *   => z_2 = -1 - 1.73205 * i
+     * </code>
+     * </pre>
+     */
+    public void testNthRoot_cornercase_thirdRoot_imaginaryPartEmpty() {
+        // The number 8 has three third roots. One we all already know is the number 2.
+        // But there are two more complex roots.
+        Complex z = new Complex(8,0);
+        // The List holding all third roots
+        Complex[] thirdRootsOfZ = z.nthRoot(3).toArray(new Complex[0]);
+        // Returned Collection must not be empty!
+        assertEquals(3, thirdRootsOfZ.length);
+        // test z_0
+        assertEquals(2.0,                thirdRootsOfZ[0].getReal(),      1.0e-5);
+        assertEquals(0.0,                thirdRootsOfZ[0].getImaginary(), 1.0e-5);
+        // test z_1
+        assertEquals(-1.0,               thirdRootsOfZ[1].getReal(),      1.0e-5);
+        assertEquals(1.7320508075688774, thirdRootsOfZ[1].getImaginary(), 1.0e-5);
+        // test z_2
+        assertEquals(-1.0,               thirdRootsOfZ[2].getReal(),      1.0e-5);
+        assertEquals(-1.732050807568877, thirdRootsOfZ[2].getImaginary(), 1.0e-5);
+    }
+
+
+    /**
+     * Test: computing <b>third roots</b> of z with real part 0.
+     * <pre>
+     * <code>
+     * <b>z = 2 * i</b>
+     *   => z_0 =  1.0911 + 0.6299 * i
+     *   => z_1 = -1.0911 + 0.6299 * i
+     *   => z_2 = -2.3144 - 1.2599 * i
+     * </code>
+     * </pre>
+     */
+    public void testNthRoot_cornercase_thirdRoot_realPartZero() {
+        // complex number with only imaginary part
+        Complex z = new Complex(0,2);
+        // The List holding all third roots
+        Complex[] thirdRootsOfZ = z.nthRoot(3).toArray(new Complex[0]);
+        // Returned Collection must not be empty!
+        assertEquals(3, thirdRootsOfZ.length);
+        // test z_0
+        assertEquals(1.0911236359717216,      thirdRootsOfZ[0].getReal(),      1.0e-5);
+        assertEquals(0.6299605249474365,      thirdRootsOfZ[0].getImaginary(), 1.0e-5);
+        // test z_1
+        assertEquals(-1.0911236359717216,     thirdRootsOfZ[1].getReal(),      1.0e-5);
+        assertEquals(0.6299605249474365,      thirdRootsOfZ[1].getImaginary(), 1.0e-5);
+        // test z_2
+        assertEquals(-2.3144374213981936E-16, thirdRootsOfZ[2].getReal(),      1.0e-5);
+        assertEquals(-1.2599210498948732,     thirdRootsOfZ[2].getImaginary(), 1.0e-5);
+    }
+
+    /**
+     * Test cornercases with NaN and Infinity.
+     */
+    public void testNthRoot_cornercase_NAN_Inf() {
+        // NaN + finite -> NaN
+        List<Complex> roots = oneNaN.nthRoot(3);
+        assertEquals(1,roots.size());
+        assertEquals(Complex.NaN, roots.get(0));
+
+        roots = nanZero.nthRoot(3);
+        assertEquals(1,roots.size());
+        assertEquals(Complex.NaN, roots.get(0));
+
+        // NaN + infinite -> NaN
+        roots = nanInf.nthRoot(3);
+        assertEquals(1,roots.size());
+        assertEquals(Complex.NaN, roots.get(0));
+
+        // finite + infinite -> Inf
+        roots = oneInf.nthRoot(3);
+        assertEquals(1,roots.size());
+        assertEquals(Complex.INF, roots.get(0));
+
+        // infinite + infinite -> Inf
+        roots = negInfInf.nthRoot(3);
+        assertEquals(1,roots.size());
+        assertEquals(Complex.INF, roots.get(0));
+    }
+
+    /**
+     * Test standard values
+     */
+    public void testGetArgument() {
+        Complex z = new Complex(1, 0);
+        assertEquals(0.0, z.getArgument(), 1.0e-12);
+
+        z = new Complex(1, 1);
+        assertEquals(FastMath.PI/4, z.getArgument(), 1.0e-12);
+
+        z = new Complex(0, 1);
+        assertEquals(FastMath.PI/2, z.getArgument(), 1.0e-12);
+
+        z = new Complex(-1, 1);
+        assertEquals(3 * FastMath.PI/4, z.getArgument(), 1.0e-12);
+
+        z = new Complex(-1, 0);
+        assertEquals(FastMath.PI, z.getArgument(), 1.0e-12);
+
+        z = new Complex(-1, -1);
+        assertEquals(-3 * FastMath.PI/4, z.getArgument(), 1.0e-12);
+
+        z = new Complex(0, -1);
+        assertEquals(-FastMath.PI/2, z.getArgument(), 1.0e-12);
+
+        z = new Complex(1, -1);
+        assertEquals(-FastMath.PI/4, z.getArgument(), 1.0e-12);
+
+    }
+
+    /**
+     * Verify atan2-style handling of infinite parts
+     */
+    public void testGetArgumentInf() {
+        assertEquals(FastMath.PI/4, infInf.getArgument(), 1.0e-12);
+        assertEquals(FastMath.PI/2, oneInf.getArgument(), 1.0e-12);
+        assertEquals(0.0, infOne.getArgument(), 1.0e-12);
+        assertEquals(FastMath.PI/2, zeroInf.getArgument(), 1.0e-12);
+        assertEquals(0.0, infZero.getArgument(), 1.0e-12);
+        assertEquals(FastMath.PI, negInfOne.getArgument(), 1.0e-12);
+        assertEquals(-3.0*FastMath.PI/4, negInfNegInf.getArgument(), 1.0e-12);
+        assertEquals(-FastMath.PI/2, oneNegInf.getArgument(), 1.0e-12);
+    }
+
+    /**
+     * Verify that either part NaN results in NaN
+     */
+    public void testGetArgumentNaN() {
+        assertEquals(nan, nanZero.getArgument());
+        assertEquals(nan, zeroNaN.getArgument());
+        assertEquals(nan, Complex.NaN.getArgument());
+    }
+
+    public void testSerial() {
+        Complex z = new Complex(3.0, 4.0);
+        assertEquals(z, TestUtils.serializeAndRecover(z));
+        Complex ncmplx = (Complex)TestUtils.serializeAndRecover(oneNaN);
+        assertEquals(nanZero, ncmplx);
+        assertTrue(ncmplx.isNaN());
+        Complex infcmplx = (Complex)TestUtils.serializeAndRecover(infInf);
+        assertEquals(infInf, infcmplx);
+        assertTrue(infcmplx.isInfinite());
+        TestComplex tz = new TestComplex(3.0, 4.0);
+        assertEquals(tz, TestUtils.serializeAndRecover(tz));
+        TestComplex ntcmplx = (TestComplex)TestUtils.serializeAndRecover(new TestComplex(oneNaN));
+        assertEquals(nanZero, ntcmplx);
+        assertTrue(ntcmplx.isNaN());
+        TestComplex inftcmplx = (TestComplex)TestUtils.serializeAndRecover(new TestComplex(infInf));
+        assertEquals(infInf, inftcmplx);
+        assertTrue(inftcmplx.isInfinite());
+    }
+
+    /**
+     * Class to test extending Complex
+     */
+    public static class TestComplex extends Complex {
+
+        /**
+         * Serialization identifier.
+         */
+        private static final long serialVersionUID = 3268726724160389237L;
+
+        public TestComplex(double real, double imaginary) {
+            super(real, imaginary);
+        }
+
+        public TestComplex(Complex other){
+            this(other.getReal(), other.getImaginary());
+        }
+
+        @Override
+        protected TestComplex createComplex(double real, double imaginary){
+            return new TestComplex(real, imaginary);
+        }
+
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/complex/ComplexUtilsTest.java b/src/test/java/org/apache/commons/math/complex/ComplexUtilsTest.java
new file mode 100644
index 0000000..e19f59f
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/complex/ComplexUtilsTest.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.complex;
+
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.util.FastMath;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ */
+public class ComplexUtilsTest extends TestCase {
+
+    private double inf = Double.POSITIVE_INFINITY;
+    private double negInf = Double.NEGATIVE_INFINITY;
+    private double nan = Double.NaN;
+    private double pi = FastMath.PI;
+
+    private Complex negInfInf = new Complex(negInf, inf);
+    private Complex infNegInf = new Complex(inf, negInf);
+    private Complex infInf = new Complex(inf, inf);
+    private Complex negInfNegInf = new Complex(negInf, negInf);
+    private Complex infNaN = new Complex(inf, nan);
+
+    public void testPolar2Complex() {
+        TestUtils.assertEquals(Complex.ONE,
+                ComplexUtils.polar2Complex(1, 0), 10e-12);
+        TestUtils.assertEquals(Complex.ZERO,
+                ComplexUtils.polar2Complex(0, 1), 10e-12);
+        TestUtils.assertEquals(Complex.ZERO,
+                ComplexUtils.polar2Complex(0, -1), 10e-12);
+        TestUtils.assertEquals(Complex.I,
+                ComplexUtils.polar2Complex(1, pi/2), 10e-12);
+        TestUtils.assertEquals(Complex.I.negate(),
+                ComplexUtils.polar2Complex(1, -pi/2), 10e-12);
+        double r = 0;
+        for (int i = 0; i < 5; i++) {
+          r += i;
+          double theta = 0;
+          for (int j =0; j < 20; j++) {
+              theta += pi / 6;
+              TestUtils.assertEquals(altPolar(r, theta),
+                      ComplexUtils.polar2Complex(r, theta), 10e-12);
+          }
+          theta = -2 * pi;
+          for (int j =0; j < 20; j++) {
+              theta -= pi / 6;
+              TestUtils.assertEquals(altPolar(r, theta),
+                      ComplexUtils.polar2Complex(r, theta), 10e-12);
+          }
+        }
+    }
+
+    protected Complex altPolar(double r, double theta) {
+        return Complex.I.multiply(new Complex(theta, 0)).exp().multiply(new Complex(r, 0));
+    }
+
+    public void testPolar2ComplexIllegalModulus() {
+        try {
+            ComplexUtils.polar2Complex(-1, 0);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+
+    public void testPolar2ComplexNaN() {
+        TestUtils.assertSame(Complex.NaN, ComplexUtils.polar2Complex(nan, 1));
+        TestUtils.assertSame(Complex.NaN, ComplexUtils.polar2Complex(1, nan));
+        TestUtils.assertSame(Complex.NaN,
+                ComplexUtils.polar2Complex(nan, nan));
+    }
+
+    public void testPolar2ComplexInf() {
+        TestUtils.assertSame(Complex.NaN, ComplexUtils.polar2Complex(1, inf));
+        TestUtils.assertSame(Complex.NaN,
+                ComplexUtils.polar2Complex(1, negInf));
+        TestUtils.assertSame(Complex.NaN, ComplexUtils.polar2Complex(inf, inf));
+        TestUtils.assertSame(Complex.NaN,
+                ComplexUtils.polar2Complex(inf, negInf));
+        TestUtils.assertSame(infInf, ComplexUtils.polar2Complex(inf, pi/4));
+        TestUtils.assertSame(infNaN, ComplexUtils.polar2Complex(inf, 0));
+        TestUtils.assertSame(infNegInf, ComplexUtils.polar2Complex(inf, -pi/4));
+        TestUtils.assertSame(negInfInf, ComplexUtils.polar2Complex(inf, 3*pi/4));
+        TestUtils.assertSame(negInfNegInf, ComplexUtils.polar2Complex(inf, 5*pi/4));
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/complex/FrenchComplexFormatTest.java b/src/test/java/org/apache/commons/math/complex/FrenchComplexFormatTest.java
new file mode 100644
index 0000000..9fff723
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/complex/FrenchComplexFormatTest.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.complex;
+
+import java.util.Locale;
+
+
+public class FrenchComplexFormatTest extends ComplexFormatAbstractTest {
+
+    @Override
+    protected char getDecimalCharacter() {
+        return ',';
+    }
+
+    @Override
+    protected Locale getLocale() {
+        return Locale.FRENCH;
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/dfp/Decimal10.java b/src/test/java/org/apache/commons/math/dfp/Decimal10.java
new file mode 100644
index 0000000..f86c059
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/dfp/Decimal10.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.dfp;
+
+public class Decimal10 extends DfpDec {
+
+    Decimal10(final DfpField factory) {
+        super(factory);
+    }
+
+    Decimal10(final DfpField factory, final byte x) {
+        super(factory, x);
+    }
+
+    Decimal10(final DfpField factory, final int x) {
+        super(factory, x);
+    }
+
+    Decimal10(final DfpField factory, final long x) {
+        super(factory, x);
+    }
+
+    Decimal10(final DfpField factory, final double x) {
+        super(factory, x);
+    }
+
+    public Decimal10(final Dfp d) {
+        super(d);
+    }
+
+    public Decimal10(final DfpField factory, final String s) {
+        super(factory, s);
+    }
+
+    protected Decimal10(final DfpField factory, final byte sign, final byte nans) {
+        super(factory, sign, nans);
+    }
+
+    @Override
+    public Dfp newInstance() {
+        return new Decimal10(getField());
+    }
+
+    @Override
+    public Dfp newInstance(final byte x) {
+        return new Decimal10(getField(), x);
+    }
+
+    @Override
+    public Dfp newInstance(final int x) {
+        return new Decimal10(getField(), x);
+    }
+
+    @Override
+    public Dfp newInstance(final long x) {
+        return new Decimal10(getField(), x);
+    }
+
+    @Override
+    public Dfp newInstance(final double x) {
+        return new Decimal10(getField(), x);
+    }
+
+    @Override
+    public Dfp newInstance(final Dfp d) {
+        return new Decimal10(d);
+    }
+
+    @Override
+    public Dfp newInstance(final String s) {
+        return new Decimal10(getField(), s);
+    }
+
+    @Override
+    public Dfp newInstance(final byte sign, final byte nans) {
+        return new Decimal10(getField(), sign, nans);
+    }
+
+    @Override
+    protected int getDecimalDigits() {
+        return 10;
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/dfp/DfpDecTest.java b/src/test/java/org/apache/commons/math/dfp/DfpDecTest.java
new file mode 100644
index 0000000..0cf6b22
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/dfp/DfpDecTest.java
@@ -0,0 +1,565 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.dfp;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class DfpDecTest {
+
+    private DfpField field;
+    private Dfp pinf;
+    private Dfp ninf;
+
+    @Before
+    public void setUp() {
+        // Some basic setup.  Define some constants and clear the status flags
+        field = new DfpField(20);
+        pinf = new DfpDec(field, 1).divide(new DfpDec(field, 0));
+        ninf = new DfpDec(field, -1).divide(new DfpDec(field, 0));
+        ninf.getField().clearIEEEFlags();
+    }
+
+    @After
+    public void tearDown() {
+        field = null;
+        pinf    = null;
+        ninf    = null;
+    }
+
+    // Generic test function.  Takes params x and y and tests them for 
+    // equality.  Then checks the status flags against the flags argument.
+    // If the test fail, it prints the desc string
+    private void test(Dfp x, Dfp y, int flags, String desc) {
+        boolean b = x.equals(y);
+
+        if (!x.equals(y) && !x.unequal(y))  // NaNs involved 
+            b = (x.toString().equals(y.toString()));
+
+        if (x.equals(new DfpDec(field, 0)))  // distinguish +/- zero
+            b = (b && (x.toString().equals(y.toString())));
+
+        b = (b && x.getField().getIEEEFlags() == flags);
+
+        if (!b)
+            Assert.assertTrue("assersion failed "+desc+" x = "+x.toString()+" flags = "+x.getField().getIEEEFlags(), b);
+
+        x.getField().clearIEEEFlags();
+    }
+
+    @Test
+    public void testRound()
+    {
+        field.setRoundingMode(DfpField.RoundingMode.ROUND_HALF_EVEN);
+
+        test(new DfpDec(field, "12345678901234567890"),
+             new DfpDec(field, "12345678901234568000"),
+             DfpField.FLAG_INEXACT, "Round #1");
+
+        test(new DfpDec(field, "0.12345678901234567890"),
+             new DfpDec(field, "0.12345678901234568"),
+             DfpField.FLAG_INEXACT, "Round #2");
+
+        test(new DfpDec(field, "0.12345678901234567500"),
+             new DfpDec(field, "0.12345678901234568"),
+             DfpField.FLAG_INEXACT, "Round #3");
+
+        test(new DfpDec(field, "0.12345678901234568500"),
+             new DfpDec(field, "0.12345678901234568"),
+             DfpField.FLAG_INEXACT, "Round #4");
+
+        test(new DfpDec(field, "0.12345678901234568501"),
+             new DfpDec(field, "0.12345678901234569"),
+             DfpField.FLAG_INEXACT, "Round #5");
+
+        test(new DfpDec(field, "0.12345678901234568499"),
+             new DfpDec(field, "0.12345678901234568"),
+             DfpField.FLAG_INEXACT, "Round #6");
+
+        test(new DfpDec(field, "1.2345678901234567890"),
+             new DfpDec(field, "1.2345678901234568"),
+             DfpField.FLAG_INEXACT, "Round #7");
+
+        test(new DfpDec(field, "1.2345678901234567500"),
+             new DfpDec(field, "1.2345678901234568"),
+             DfpField.FLAG_INEXACT, "Round #8");
+
+        test(new DfpDec(field, "1.2345678901234568500"),
+             new DfpDec(field, "1.2345678901234568"),
+             DfpField.FLAG_INEXACT, "Round #9");
+
+        test(new DfpDec(field, "1.2345678901234568000").add(new DfpDec(field, ".0000000000000000501")),
+             new DfpDec(field, "1.2345678901234569"),
+             DfpField.FLAG_INEXACT, "Round #10");
+
+        test(new DfpDec(field, "1.2345678901234568499"),
+             new DfpDec(field, "1.2345678901234568"),
+             DfpField.FLAG_INEXACT, "Round #11");
+
+        test(new DfpDec(field, "12.345678901234567890"),
+             new DfpDec(field, "12.345678901234568"),
+             DfpField.FLAG_INEXACT, "Round #12");
+
+        test(new DfpDec(field, "12.345678901234567500"),
+             new DfpDec(field, "12.345678901234568"),
+             DfpField.FLAG_INEXACT, "Round #13");
+
+        test(new DfpDec(field, "12.345678901234568500"),
+             new DfpDec(field, "12.345678901234568"),
+             DfpField.FLAG_INEXACT, "Round #14");
+
+        test(new DfpDec(field, "12.345678901234568").add(new DfpDec(field, ".000000000000000501")),
+             new DfpDec(field, "12.345678901234569"),
+             DfpField.FLAG_INEXACT, "Round #15");
+
+        test(new DfpDec(field, "12.345678901234568499"),
+             new DfpDec(field, "12.345678901234568"),
+             DfpField.FLAG_INEXACT, "Round #16");
+
+        test(new DfpDec(field, "123.45678901234567890"),
+             new DfpDec(field, "123.45678901234568"),
+             DfpField.FLAG_INEXACT, "Round #17");
+
+        test(new DfpDec(field, "123.45678901234567500"),
+             new DfpDec(field, "123.45678901234568"),
+             DfpField.FLAG_INEXACT, "Round #18");
+
+        test(new DfpDec(field, "123.45678901234568500"),
+             new DfpDec(field, "123.45678901234568"),
+             DfpField.FLAG_INEXACT, "Round #19");
+
+        test(new DfpDec(field, "123.456789012345685").add(new DfpDec(field, ".00000000000000501")),
+             new DfpDec(field, "123.45678901234569"),
+             DfpField.FLAG_INEXACT, "Round #20");
+
+        test(new DfpDec(field, "123.45678901234568499"),
+             new DfpDec(field, "123.45678901234568"),
+             DfpField.FLAG_INEXACT, "Round #21");
+
+        field.setRoundingMode(DfpField.RoundingMode.ROUND_DOWN);
+
+        // Round down
+        test(new DfpDec(field, "12345678901234567").add(new DfpDec(field, "0.9")),
+             new DfpDec(field, "12345678901234567"),
+             DfpField.FLAG_INEXACT, "Round #22");
+
+        test(new DfpDec(field, "12345678901234567").add(new DfpDec(field, "0.99999999")),
+             new DfpDec(field, "12345678901234567"),
+             DfpField.FLAG_INEXACT, "Round #23");
+
+        test(new DfpDec(field, "-12345678901234567").add(new DfpDec(field, "-0.99999999")),
+             new DfpDec(field, "-12345678901234567"),
+             DfpField.FLAG_INEXACT, "Round #24");
+
+        field.setRoundingMode(DfpField.RoundingMode.ROUND_UP);
+
+        // Round up
+        test(new DfpDec(field, "12345678901234567").add(new DfpDec(field, "0.1")),
+             new DfpDec(field, "12345678901234568"),
+             DfpField.FLAG_INEXACT, "Round #25");
+
+        test(new DfpDec(field, "12345678901234567").add(new DfpDec(field, "0.0001")),
+             new DfpDec(field, "12345678901234568"),
+             DfpField.FLAG_INEXACT, "Round #26");
+
+        test(new DfpDec(field, "-12345678901234567").add(new DfpDec(field, "-0.1")),
+             new DfpDec(field, "-12345678901234568"),
+             DfpField.FLAG_INEXACT, "Round #27");
+
+        test(new DfpDec(field, "-12345678901234567").add(new DfpDec(field, "-0.0001")),
+             new DfpDec(field, "-12345678901234568"),
+             DfpField.FLAG_INEXACT, "Round #28");
+
+        test(new DfpDec(field, "-12345678901234567").add(new DfpDec(field, "0")),
+             new DfpDec(field, "-12345678901234567"),
+             0, "Round #28.5");
+
+        field.setRoundingMode(DfpField.RoundingMode.ROUND_HALF_UP);
+
+        // Round half up
+        test(new DfpDec(field, "12345678901234567").add(new DfpDec(field, "0.499999999999")),
+             new DfpDec(field, "12345678901234567"),
+             DfpField.FLAG_INEXACT, "Round #29");
+
+        test(new DfpDec(field, "12345678901234567").add(new DfpDec(field, "0.50000001")),
+             new DfpDec(field, "12345678901234568"),
+             DfpField.FLAG_INEXACT, "Round #30");
+
+        test(new DfpDec(field, "12345678901234567").add(new DfpDec(field, "0.5")),
+             new DfpDec(field, "12345678901234568"),
+             DfpField.FLAG_INEXACT, "Round #30.5");
+
+        test(new DfpDec(field, "-12345678901234567").add(new DfpDec(field, "-0.499999999999")),
+             new DfpDec(field, "-12345678901234567"),
+             DfpField.FLAG_INEXACT, "Round #31");
+
+        test(new DfpDec(field, "-12345678901234567").add(new DfpDec(field, "-0.50000001")),
+             new DfpDec(field, "-12345678901234568"),
+             DfpField.FLAG_INEXACT, "Round #32");
+
+        field.setRoundingMode(DfpField.RoundingMode.ROUND_HALF_DOWN);
+
+        // Round half down
+        test(new DfpDec(field, "12345678901234567").add(new DfpDec(field, "0.5001")),
+             new DfpDec(field, "12345678901234568"),
+             DfpField.FLAG_INEXACT, "Round #33");
+
+        test(new DfpDec(field, "12345678901234567").add(new DfpDec(field, "0.5000")),
+             new DfpDec(field, "12345678901234567"),
+             DfpField.FLAG_INEXACT, "Round #34");
+
+        test(new DfpDec(field, "-12345678901234567").add(new DfpDec(field, "-0.5001")),
+             new DfpDec(field, "-12345678901234568"),
+             DfpField.FLAG_INEXACT, "Round #35");
+
+        test(new DfpDec(field, "-12345678901234567").add(new DfpDec(field, "-0.6")),
+             new DfpDec(field, "-12345678901234568"),
+             DfpField.FLAG_INEXACT, "Round #35.5");
+
+        test(new DfpDec(field, "-12345678901234567").add(new DfpDec(field, "-0.5000")),
+             new DfpDec(field, "-12345678901234567"),
+             DfpField.FLAG_INEXACT, "Round #36");
+
+        field.setRoundingMode(DfpField.RoundingMode.ROUND_HALF_ODD);
+
+        // Round half odd
+        test(new DfpDec(field, "12345678901234568").add(new DfpDec(field, "0.5000")),
+             new DfpDec(field, "12345678901234569"),
+             DfpField.FLAG_INEXACT, "Round #37");
+
+        test(new DfpDec(field, "12345678901234567").add(new DfpDec(field, "0.5000")),
+             new DfpDec(field, "12345678901234567"),
+             DfpField.FLAG_INEXACT, "Round #38");
+
+        test(new DfpDec(field, "-12345678901234568").add(new DfpDec(field, "-0.5000")),
+             new DfpDec(field, "-12345678901234569"),
+             DfpField.FLAG_INEXACT, "Round #39");
+
+        test(new DfpDec(field, "-12345678901234567").add(new DfpDec(field, "-0.5000")),
+             new DfpDec(field, "-12345678901234567"),
+             DfpField.FLAG_INEXACT, "Round #40");
+
+        field.setRoundingMode(DfpField.RoundingMode.ROUND_CEIL);
+
+        // Round ceil
+        test(new DfpDec(field, "12345678901234567").add(new DfpDec(field, "0.0001")),
+             new DfpDec(field, "12345678901234568"),
+             DfpField.FLAG_INEXACT, "Round #41");
+
+        test(new DfpDec(field, "-12345678901234567").add(new DfpDec(field, "-0.9999")),
+             new DfpDec(field, "-12345678901234567"),
+             DfpField.FLAG_INEXACT, "Round #42");
+
+        field.setRoundingMode(DfpField.RoundingMode.ROUND_FLOOR);
+
+        // Round floor
+        test(new DfpDec(field, "12345678901234567").add(new DfpDec(field, "0.9999")),
+             new DfpDec(field, "12345678901234567"),
+             DfpField.FLAG_INEXACT, "Round #43");
+
+        test(new DfpDec(field, "-12345678901234567").add(new DfpDec(field, "-0.0001")),
+             new DfpDec(field, "-12345678901234568"),
+             DfpField.FLAG_INEXACT, "Round #44");
+
+        field.setRoundingMode(DfpField.RoundingMode.ROUND_HALF_EVEN);  // reset
+    }
+
+    @Test
+    public void testRoundDecimal10()
+    {
+        field.setRoundingMode(DfpField.RoundingMode.ROUND_HALF_EVEN);
+
+        test(new Decimal10(field, "1234567891234567890"),
+             new Decimal10(field, "1234567891000000000"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #1");
+
+        test(new Decimal10(field, "0.1234567891634567890"),
+             new Decimal10(field, "0.1234567892"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #2");
+
+        test(new Decimal10(field, "0.1234567891500000000"),
+             new Decimal10(field, "0.1234567892"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #3");
+
+        test(new Decimal10(field, "0.1234567890500"),
+             new Decimal10(field, "0.1234567890"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #4");
+
+        test(new Decimal10(field, "0.1234567890501"),
+             new Decimal10(field, "0.1234567891"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #5");
+
+        test(new Decimal10(field, "0.1234567890499"),
+             new Decimal10(field, "0.1234567890"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #6");
+
+        test(new Decimal10(field, "1.234567890890"),
+             new Decimal10(field, "1.234567891"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #7");
+
+        test(new Decimal10(field, "1.234567891500"),
+             new Decimal10(field, "1.234567892"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #8");
+
+        test(new Decimal10(field, "1.234567890500"),
+             new Decimal10(field, "1.234567890"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #9");
+
+        test(new Decimal10(field, "1.234567890000").add(new Decimal10(field, ".000000000501")),
+             new Decimal10(field, "1.234567891"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #10");
+
+        test(new Decimal10(field, "1.234567890499"),
+             new Decimal10(field, "1.234567890"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #11");
+
+        test(new Decimal10(field, "12.34567890890"),
+             new Decimal10(field, "12.34567891"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #12");
+
+        test(new Decimal10(field, "12.34567891500"),
+             new Decimal10(field, "12.34567892"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #13");
+
+        test(new Decimal10(field, "12.34567890500"),
+             new Decimal10(field, "12.34567890"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #14");
+
+        test(new Decimal10(field, "12.34567890").add(new Decimal10(field, ".00000000501")),
+             new Decimal10(field, "12.34567891"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #15");
+
+        test(new Decimal10(field, "12.34567890499"),
+             new Decimal10(field, "12.34567890"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #16");
+
+        test(new Decimal10(field, "123.4567890890"),
+             new Decimal10(field, "123.4567891"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #17");
+
+        test(new Decimal10(field, "123.4567891500"),
+             new Decimal10(field, "123.4567892"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #18");
+
+        test(new Decimal10(field, "123.4567890500"),
+             new Decimal10(field, "123.4567890"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #19");
+
+        test(new Decimal10(field, "123.4567890").add(new Decimal10(field, ".0000000501")),
+             new Decimal10(field, "123.4567891"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #20");
+
+        test(new Decimal10(field, "123.4567890499"),
+             new Decimal10(field, "123.4567890"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #21");
+
+        field.setRoundingMode(DfpField.RoundingMode.ROUND_DOWN);
+
+        // RoundDecimal10 down
+        test(new Decimal10(field, "1234567890").add(new Decimal10(field, "0.9")),
+             new Decimal10(field, "1234567890"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #22");
+
+        test(new Decimal10(field, "1234567890").add(new Decimal10(field, "0.99999999")),
+             new Decimal10(field, "1234567890"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #23");
+
+        test(new Decimal10(field, "-1234567890").add(new Decimal10(field, "-0.99999999")),
+             new Decimal10(field, "-1234567890"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #24");
+
+        field.setRoundingMode(DfpField.RoundingMode.ROUND_UP);
+
+        // RoundDecimal10 up
+        test(new Decimal10(field, 1234567890).add(new Decimal10(field, "0.1")),
+             new Decimal10(field, 1234567891l),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #25");
+
+        test(new Decimal10(field, "1234567890").add(new Decimal10(field, "0.0001")),
+             new Decimal10(field, "1234567891"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #26");
+
+        test(new Decimal10(field, "-1234567890").add(new Decimal10(field, "-0.1")),
+             new Decimal10(field, "-1234567891"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #27");
+
+        test(new Decimal10(field, "-1234567890").add(new Decimal10(field, "-0.0001")),
+             new Decimal10(field, "-1234567891"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #28");
+
+        test(new Decimal10(field, "-1234567890").add(new Decimal10(field, "0")),
+             new Decimal10(field, "-1234567890"),
+             0, "RoundDecimal10 #28.5");
+
+        field.setRoundingMode(DfpField.RoundingMode.ROUND_HALF_UP);
+
+        // RoundDecimal10 half up
+        test(new Decimal10(field, "1234567890").add(new Decimal10(field, "0.4999999999")),
+             new Decimal10(field, "1234567890"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #29");
+
+        test(new Decimal10(field, "1234567890").add(new Decimal10(field, "0.50000001")),
+             new Decimal10(field, "1234567891"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #30");
+
+        test(new Decimal10(field, "1234567890").add(new Decimal10(field, "0.5")),
+             new Decimal10(field, "1234567891"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #30.5");
+
+        test(new Decimal10(field, "-1234567890").add(new Decimal10(field, "-0.4999999999")),
+             new Decimal10(field, "-1234567890"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #31");
+
+        test(new Decimal10(field, "-1234567890").add(new Decimal10(field, "-0.50000001")),
+             new Decimal10(field, "-1234567891"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #32");
+
+        field.setRoundingMode(DfpField.RoundingMode.ROUND_HALF_DOWN);
+
+        // RoundDecimal10 half down
+        test(new Decimal10(field, "1234567890").add(new Decimal10(field, "0.5001")),
+             new Decimal10(field, "1234567890"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #33");
+
+        test(new Decimal10(field, "1234567890").add(new Decimal10(field, "0.5000")),
+             new Decimal10(field, "1234567890"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #34");
+
+        test(new Decimal10(field, "-1234567890").add(new Decimal10(field, "-0.5001")),
+             new Decimal10(field, "-1234567890"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #35");
+
+        test(new Decimal10(field, "-1234567890").add(new Decimal10(field, "-0.6")),
+             new Decimal10(field, "-1234567891"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #35.5");
+
+        test(new Decimal10(field, "-1234567890").add(new Decimal10(field, "-0.5000")),
+             new Decimal10(field, "-1234567890"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #36");
+
+        field.setRoundingMode(DfpField.RoundingMode.ROUND_HALF_ODD);
+
+        // RoundDecimal10 half odd
+        test(new Decimal10(field, "1234567890").add(new Decimal10(field, "0.5000")),
+             new Decimal10(field, "1234567891"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #37");
+
+        test(new Decimal10(field, "1234567891").add(new Decimal10(field, "0.5000")),
+             new Decimal10(field, "1234567891"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #38");
+
+        test(new Decimal10(field, "-1234567890").add(new Decimal10(field, "-0.5000")),
+             new Decimal10(field, "-1234567891"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #39");
+
+        test(new Decimal10(field, "-1234567891").add(new Decimal10(field, "-0.5000")),
+             new Decimal10(field, "-1234567891"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #40");
+
+        field.setRoundingMode(DfpField.RoundingMode.ROUND_CEIL);
+
+        // RoundDecimal10 ceil
+        test(new Decimal10(field, "1234567890").add(new Decimal10(field, "0.0001")),
+             new Decimal10(field, "1234567891"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #41");
+
+        test(new Decimal10(field, "-1234567890").add(new Decimal10(field, "-0.9999")),
+             new Decimal10(field, "-1234567890"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #42");
+
+        field.setRoundingMode(DfpField.RoundingMode.ROUND_FLOOR);
+
+        // RoundDecimal10 floor
+        test(new Decimal10(field, "1234567890").add(new Decimal10(field, "0.9999")),
+             new Decimal10(field, "1234567890"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #43");
+
+        test(new Decimal10(field, "-1234567890").add(new Decimal10(field, "-0.0001")),
+             new Decimal10(field, "-1234567891"),
+             DfpField.FLAG_INEXACT, "RoundDecimal10 #44");
+
+        field.setRoundingMode(DfpField.RoundingMode.ROUND_HALF_EVEN);  // reset
+    }
+
+    @Test
+    public void testNextAfter()
+    {
+        test(new DfpDec(field, 1).nextAfter(pinf),
+             new DfpDec(field, "1.0000000000000001"),
+             0, "NextAfter #1");
+
+        test(new DfpDec(field, "1.0000000000000001").nextAfter(ninf),
+             new DfpDec(field, 1),
+             0, "NextAfter #1.5");
+
+        test(new DfpDec(field, 1).nextAfter(ninf),
+             new DfpDec(field, "0.99999999999999999"),
+             0, "NextAfter #2");
+
+        test(new DfpDec(field, "0.99999999999999999").nextAfter(new DfpDec(field, 2)),
+             new DfpDec(field, 1),
+             0, "NextAfter #3");
+
+        test(new DfpDec(field, -1).nextAfter(ninf),
+             new DfpDec(field, "-1.0000000000000001"),
+             0, "NextAfter #4");
+
+        test(new DfpDec(field, -1).nextAfter(pinf),
+             new DfpDec(field, "-0.99999999999999999"),
+             0, "NextAfter #5");
+
+        test(new DfpDec(field, "-0.99999999999999999").nextAfter(new DfpDec(field, -2)),
+             new DfpDec(field, (byte) -1),
+             0, "NextAfter #6");
+
+        test(new DfpDec(field, (byte) 2).nextAfter(new DfpDec(field, 2)),
+             new DfpDec(field, 2l),
+             0, "NextAfter #7");
+
+        test(new DfpDec(field, 0).nextAfter(new DfpDec(field, 0)),
+             new DfpDec(field, 0),
+             0, "NextAfter #8");
+
+        test(new DfpDec(field, -2).nextAfter(new DfpDec(field, -2)),
+             new DfpDec(field, -2),
+             0, "NextAfter #9");
+
+        test(new DfpDec(field, 0).nextAfter(new DfpDec(field, 1)),
+             new DfpDec(field, "1e-131092"),
+             DfpField.FLAG_UNDERFLOW, "NextAfter #10");
+
+        test(new DfpDec(field, 0).nextAfter(new DfpDec(field, -1)),
+             new DfpDec(field, "-1e-131092"),
+             DfpField.FLAG_UNDERFLOW, "NextAfter #11");
+
+        test(new DfpDec(field, "-1e-131092").nextAfter(pinf),
+             new DfpDec(field, "-0"),
+             DfpField.FLAG_UNDERFLOW|DfpField.FLAG_INEXACT, "Next After #12");
+
+        test(new DfpDec(field, "1e-131092").nextAfter(ninf), 
+             new DfpDec(field, "0"),
+             DfpField.FLAG_UNDERFLOW|DfpField.FLAG_INEXACT, "Next After #13");
+
+        test(new DfpDec(field, "9.9999999999999999e131078").nextAfter(pinf),
+             pinf,
+             DfpField.FLAG_OVERFLOW|DfpField.FLAG_INEXACT, "Next After #14");
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/dfp/DfpMathTest.java b/src/test/java/org/apache/commons/math/dfp/DfpMathTest.java
new file mode 100644
index 0000000..aba6fd5
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/dfp/DfpMathTest.java
@@ -0,0 +1,587 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.dfp;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class DfpMathTest {
+
+    private DfpField factory;
+    private Dfp pinf;
+    private Dfp ninf;
+    private Dfp nan;
+    private Dfp qnan;
+
+    @Before
+    public void setUp() {
+        // Some basic setup.  Define some constants and clear the status flags
+        factory = new DfpField(20);
+        pinf = factory.newDfp("1").divide(factory.newDfp("0"));
+        ninf = factory.newDfp("-1").divide(factory.newDfp("0"));
+        nan = factory.newDfp("0").divide(factory.newDfp("0"));
+        qnan = factory.newDfp((byte)1, Dfp.QNAN);
+        ninf.getField().clearIEEEFlags();
+
+        // force loading of dfpmath
+        Dfp pi = factory.getPi();
+        pi.getField().clearIEEEFlags();
+    }
+
+    @After
+    public void tearDown() {
+        pinf = null;
+        ninf = null;
+        nan  = null;
+        qnan = null;
+    }
+
+    // Generic test function.  Takes params x and y and tests them for 
+    // equality.  Then checks the status flags against the flags argument.
+    // If the test fail, it prints the desc string
+    private void test(Dfp x, Dfp y, int flags, String desc)
+    {
+        boolean b = x.equals(y);
+
+        if (!x.equals(y) && !x.unequal(y))  // NaNs involved 
+            b = (x.toString().equals(y.toString()));
+
+        if (x.equals(factory.newDfp("0")))  // distinguish +/- zero
+            b = (b && (x.toString().equals(y.toString())));
+
+        b = (b && x.getField().getIEEEFlags() == flags);
+
+        if (!b)
+            Assert.assertTrue("assersion failed "+desc+" x = "+x.toString()+" flags = "+x.getField().getIEEEFlags(), b);
+
+        x.getField().clearIEEEFlags();
+    }
+
+    @Test
+    public void testPow()  
+    {
+        // Test special cases  exponent of zero
+        test(DfpMath.pow(factory.newDfp("0"), factory.newDfp("0")),      
+             factory.newDfp("1"), 
+             0, "pow #1");
+
+        test(DfpMath.pow(factory.newDfp("0"), factory.newDfp("-0")),      
+             factory.newDfp("1"), 
+             0, "pow #2");
+
+        test(DfpMath.pow(factory.newDfp("2"), factory.newDfp("0")),      
+             factory.newDfp("1"), 
+             0, "pow #3");
+
+        test(DfpMath.pow(factory.newDfp("-2"), factory.newDfp("-0")),      
+             factory.newDfp("1"), 
+             0, "pow #4");
+
+        test(DfpMath.pow(pinf, factory.newDfp("-0")),      
+             factory.newDfp("1"), 
+             0, "pow #5");
+
+        test(DfpMath.pow(pinf, factory.newDfp("0")),
+             factory.newDfp("1"), 
+             0, "pow #6");
+
+        test(DfpMath.pow(ninf, factory.newDfp("-0")),      
+             factory.newDfp("1"), 
+             0, "pow #7");
+
+        test(DfpMath.pow(ninf, factory.newDfp("0")),
+             factory.newDfp("1"), 
+             0, "pow #8");
+
+        test(DfpMath.pow(qnan, factory.newDfp("0")),
+             factory.newDfp("1"), 
+             0, "pow #8");
+
+        // exponent of one
+        test(DfpMath.pow(factory.newDfp("0"), factory.newDfp("1")),
+             factory.newDfp("0"), 
+             0, "pow #9");
+
+        test(DfpMath.pow(factory.newDfp("-0"), factory.newDfp("1")),      
+             factory.newDfp("-0"), 
+             0, "pow #10");
+
+        test(DfpMath.pow(factory.newDfp("2"), factory.newDfp("1")),
+             factory.newDfp("2"), 
+             0, "pow #11");
+
+        test(DfpMath.pow(factory.newDfp("-2"), factory.newDfp("1")),
+             factory.newDfp("-2"), 
+             0, "pow #12");
+
+        test(DfpMath.pow(pinf, factory.newDfp("1")),      
+             pinf, 
+             0, "pow #13");
+
+        test(DfpMath.pow(ninf, factory.newDfp("1")),
+             ninf, 
+             0, "pow #14");
+
+        test(DfpMath.pow(qnan, factory.newDfp("1")),
+             qnan, 
+             DfpField.FLAG_INVALID, "pow #14.1");
+
+        // exponent of NaN
+        test(DfpMath.pow(factory.newDfp("0"), qnan),
+             qnan, 
+             DfpField.FLAG_INVALID, "pow #15");
+
+        test(DfpMath.pow(factory.newDfp("-0"), qnan),      
+             qnan, 
+             DfpField.FLAG_INVALID, "pow #16");
+
+        test(DfpMath.pow(factory.newDfp("2"), qnan),
+             qnan, 
+             DfpField.FLAG_INVALID, "pow #17");
+
+        test(DfpMath.pow(factory.newDfp("-2"), qnan),
+             qnan, 
+             DfpField.FLAG_INVALID, "pow #18");
+
+        test(DfpMath.pow(pinf, qnan),      
+             qnan, 
+             DfpField.FLAG_INVALID, "pow #19");
+
+        test(DfpMath.pow(ninf, qnan),
+             qnan, 
+             DfpField.FLAG_INVALID, "pow #20");
+
+        test(DfpMath.pow(qnan, qnan),
+             qnan, 
+             DfpField.FLAG_INVALID, "pow #21");
+
+        // radix of NaN
+        test(DfpMath.pow(qnan, factory.newDfp("1")),
+             qnan, 
+             DfpField.FLAG_INVALID, "pow #22");
+
+        test(DfpMath.pow(qnan, factory.newDfp("-1")),      
+             qnan,
+             DfpField.FLAG_INVALID, "pow #23");
+
+        test(DfpMath.pow(qnan, pinf),
+             qnan,
+             DfpField.FLAG_INVALID, "pow #24");
+
+        test(DfpMath.pow(qnan, ninf),
+             qnan, 
+             DfpField.FLAG_INVALID, "pow #25");
+
+        test(DfpMath.pow(qnan, qnan),
+             qnan, 
+             DfpField.FLAG_INVALID, "pow #26");
+
+        // (x > 1) ^ pinf = pinf,    (x < -1) ^ pinf = pinf
+        test(DfpMath.pow(factory.newDfp("2"), pinf),
+             pinf, 
+             0, "pow #27");
+
+        test(DfpMath.pow(factory.newDfp("-2"), pinf),      
+             pinf,
+             0, "pow #28");
+
+        test(DfpMath.pow(pinf, pinf),
+             pinf,
+             0, "pow #29");
+
+        test(DfpMath.pow(ninf, pinf),
+             pinf, 
+             0, "pow #30");
+
+        // (x > 1) ^ ninf = +0,    (x < -1) ^ ninf = +0
+        test(DfpMath.pow(factory.newDfp("2"), ninf),
+             factory.getZero(), 
+             0, "pow #31");
+
+        test(DfpMath.pow(factory.newDfp("-2"), ninf),      
+             factory.getZero(),
+             0, "pow #32");
+
+        test(DfpMath.pow(pinf, ninf),
+             factory.getZero(),
+             0, "pow #33");
+
+        test(DfpMath.pow(ninf, ninf),
+             factory.getZero(), 
+             0, "pow #34");
+
+        // (-1 < x < 1) ^ pinf = 0
+        test(DfpMath.pow(factory.newDfp("0.5"), pinf),
+             factory.getZero(), 
+             0, "pow #35");
+
+        test(DfpMath.pow(factory.newDfp("-0.5"), pinf),      
+             factory.getZero(),
+             0, "pow #36");
+
+        // (-1 < x < 1) ^ ninf = pinf 
+        test(DfpMath.pow(factory.newDfp("0.5"), ninf),
+             pinf, 
+             0, "pow #37");
+
+        test(DfpMath.pow(factory.newDfp("-0.5"), ninf),      
+             pinf,
+             0, "pow #38");
+
+        // +/- 1  ^ +/-inf  = NaN
+        test(DfpMath.pow(factory.getOne(), pinf),
+             qnan, 
+             DfpField.FLAG_INVALID, "pow #39");
+
+        test(DfpMath.pow(factory.getOne(), ninf),      
+             qnan,
+             DfpField.FLAG_INVALID, "pow #40");
+
+        test(DfpMath.pow(factory.newDfp("-1"), pinf),
+             qnan, 
+             DfpField.FLAG_INVALID, "pow #41");
+
+        test(DfpMath.pow(factory.getOne().negate(), ninf),      
+             qnan,
+             DfpField.FLAG_INVALID, "pow #42");
+
+        // +0  ^ +anything except 0, NAN  = +0
+
+        test(DfpMath.pow(factory.newDfp("0"), factory.newDfp("1")),
+             factory.newDfp("0"),
+             0, "pow #43");
+
+        test(DfpMath.pow(factory.newDfp("0"), factory.newDfp("1e30")),
+             factory.newDfp("0"),
+             0, "pow #44");
+
+        test(DfpMath.pow(factory.newDfp("0"), factory.newDfp("1e-30")),
+             factory.newDfp("0"),
+             0, "pow #45");
+
+        test(DfpMath.pow(factory.newDfp("0"), pinf),
+             factory.newDfp("0"),
+             0, "pow #46");
+
+        // -0  ^ +anything except 0, NAN, odd integer  = +0
+
+        test(DfpMath.pow(factory.newDfp("-0"), factory.newDfp("2")),
+             factory.newDfp("0"),
+             0, "pow #47");
+
+        test(DfpMath.pow(factory.newDfp("-0"), factory.newDfp("1e30")),
+             factory.newDfp("0"),
+             0, "pow #48");
+
+        test(DfpMath.pow(factory.newDfp("-0"), factory.newDfp("1e-30")),
+             factory.newDfp("0"),
+             DfpField.FLAG_INEXACT, "pow #49");
+
+        test(DfpMath.pow(factory.newDfp("-0"), pinf),
+             factory.newDfp("0"),
+             0, "pow #50");
+
+        // +0  ^ -anything except 0, NAN  = +INF
+
+        test(DfpMath.pow(factory.newDfp("0"), factory.newDfp("-1")),
+             pinf,
+             0, "pow #51");
+
+        test(DfpMath.pow(factory.newDfp("0"), factory.newDfp("-1e30")),
+             pinf,
+             0, "pow #52");
+
+        test(DfpMath.pow(factory.newDfp("0"), factory.newDfp("-1e-30")),
+             pinf,
+             0, "pow #53");
+
+        test(DfpMath.pow(factory.newDfp("0"), ninf),
+             pinf,
+             0, "pow #54");
+
+        // -0  ^ -anything except 0, NAN, odd integer  = +INF
+
+        test(DfpMath.pow(factory.newDfp("-0"), factory.newDfp("-2")),
+             pinf,
+             0, "pow #55");
+
+        test(DfpMath.pow(factory.newDfp("-0"), factory.newDfp("-1e30")),
+             pinf,
+             0, "pow #56");
+
+        test(DfpMath.pow(factory.newDfp("-0"), factory.newDfp("-1e-30")),
+             pinf,
+             DfpField.FLAG_INEXACT, "pow #57");
+
+        test(DfpMath.pow(factory.newDfp("-0"), ninf),
+             pinf,
+             0, "pow #58");
+
+        // -0  ^ -odd integer   =  -INF
+        test(DfpMath.pow(factory.newDfp("-0"), factory.newDfp("-1")),
+             ninf,
+             DfpField.FLAG_INEXACT, "pow #59");
+
+        test(DfpMath.pow(factory.newDfp("-0"), factory.newDfp("-12345")),
+             ninf,
+             DfpField.FLAG_INEXACT, "pow #60");
+
+        // -0  ^ +odd integer   =  -0
+        test(DfpMath.pow(factory.newDfp("-0"), factory.newDfp("3")),
+             factory.newDfp("-0"),
+             DfpField.FLAG_INEXACT, "pow #61");
+
+        test(DfpMath.pow(factory.newDfp("-0"), factory.newDfp("12345")),
+             factory.newDfp("-0"),
+             DfpField.FLAG_INEXACT, "pow #62");
+
+        // pinf  ^ +anything   = pinf 
+        test(DfpMath.pow(pinf, factory.newDfp("3")),
+             pinf,
+             0, "pow #63");
+
+        test(DfpMath.pow(pinf, factory.newDfp("1e30")),
+             pinf,
+             0, "pow #64");
+
+        test(DfpMath.pow(pinf, factory.newDfp("1e-30")),
+             pinf,
+             0, "pow #65");
+
+        test(DfpMath.pow(pinf, pinf),
+             pinf,
+             0, "pow #66");
+
+        // pinf  ^ -anything   = +0 
+
+        test(DfpMath.pow(pinf, factory.newDfp("-3")),
+             factory.getZero(),
+             0, "pow #67");
+
+        test(DfpMath.pow(pinf, factory.newDfp("-1e30")),
+             factory.getZero(),
+             0, "pow #68");
+
+        test(DfpMath.pow(pinf, factory.newDfp("-1e-30")),
+             factory.getZero(),
+             0, "pow #69");
+
+        test(DfpMath.pow(pinf, ninf),
+             factory.getZero(),
+             0, "pow #70");
+
+        // ninf  ^ anything   = -0 ^ -anything
+        // ninf  ^ -anything except 0, NAN, odd integer  = +0
+
+        test(DfpMath.pow(ninf, factory.newDfp("-2")),
+             factory.newDfp("0"),
+             0, "pow #71");
+
+        test(DfpMath.pow(ninf, factory.newDfp("-1e30")),
+             factory.newDfp("0"),
+             0, "pow #72");
+
+        test(DfpMath.pow(ninf, factory.newDfp("-1e-30")),
+             factory.newDfp("0"),
+             DfpField.FLAG_INEXACT, "pow #73");
+
+        test(DfpMath.pow(ninf, ninf),
+             factory.newDfp("0"),
+             0, "pow #74");
+
+        // ninf  ^ +anything except 0, NAN, odd integer  = +INF
+
+        test(DfpMath.pow(ninf, factory.newDfp("2")),
+             pinf,
+             0, "pow #75");
+
+        test(DfpMath.pow(ninf, factory.newDfp("1e30")),
+             pinf,
+             0, "pow #76");
+
+        test(DfpMath.pow(ninf, factory.newDfp("1e-30")),
+             pinf,
+             DfpField.FLAG_INEXACT, "pow #77");
+
+        test(DfpMath.pow(ninf, pinf),
+             pinf,
+             0, "pow #78");
+
+        // ninf  ^ +odd integer   =  -INF
+        test(DfpMath.pow(ninf, factory.newDfp("3")),
+             ninf,
+             DfpField.FLAG_INEXACT, "pow #79");
+
+        test(DfpMath.pow(ninf, factory.newDfp("12345")),
+             ninf,
+             DfpField.FLAG_INEXACT, "pow #80");
+
+        // ninf  ^ -odd integer   =  -0
+        test(DfpMath.pow(ninf, factory.newDfp("-3")),
+             factory.newDfp("-0"),
+             DfpField.FLAG_INEXACT, "pow #81");
+
+        test(DfpMath.pow(ninf, factory.newDfp("-12345")),
+             factory.newDfp("-0"),
+             DfpField.FLAG_INEXACT, "pow #82");
+
+        // -anything ^ integer 
+        test(DfpMath.pow(factory.newDfp("-2"), factory.newDfp("3")),
+             factory.newDfp("-8"),
+             DfpField.FLAG_INEXACT, "pow #83");
+
+        test(DfpMath.pow(factory.newDfp("-2"), factory.newDfp("16")),
+             factory.newDfp("65536"),
+             0, "pow #84");
+
+        test(DfpMath.pow(factory.newDfp("-2"), factory.newDfp("-3")),
+             factory.newDfp("-0.125"),
+             DfpField.FLAG_INEXACT, "pow #85");
+
+        test(DfpMath.pow(factory.newDfp("-2"), factory.newDfp("-4")),
+             factory.newDfp("0.0625"),
+             0, "pow #86");
+
+        // -anything ^ noninteger = NaN
+
+        test(DfpMath.pow(factory.newDfp("-2"), factory.newDfp("-4.1")),
+             qnan,
+             DfpField.FLAG_INVALID|DfpField.FLAG_INEXACT, "pow #87");
+
+        // Some fractional cases.
+        test(DfpMath.pow(factory.newDfp("2"),factory.newDfp("1.5")),
+             factory.newDfp("2.8284271247461901"), 
+             DfpField.FLAG_INEXACT, "pow #88");
+    }
+
+    @Test
+    public void testSin()
+    {
+        test(DfpMath.sin(pinf),
+             nan,
+             DfpField.FLAG_INVALID|DfpField.FLAG_INEXACT, "sin #1");
+
+        test(DfpMath.sin(nan),
+             nan,
+             DfpField.FLAG_INVALID|DfpField.FLAG_INEXACT, "sin #2");
+
+        test(DfpMath.sin(factory.getZero()),
+             factory.getZero(),
+             DfpField.FLAG_INEXACT, "sin #3");
+
+        test(DfpMath.sin(factory.getPi()),
+             factory.getZero(),
+             DfpField.FLAG_INEXACT, "sin #4");
+
+        test(DfpMath.sin(factory.getPi().negate()),
+             factory.newDfp("-0"),
+             DfpField.FLAG_INEXACT, "sin #5");
+
+        test(DfpMath.sin(factory.getPi().multiply(2)),
+             factory.getZero(),
+             DfpField.FLAG_INEXACT, "sin #6");
+
+        test(DfpMath.sin(factory.getPi().divide(2)),
+             factory.getOne(),
+             DfpField.FLAG_INEXACT, "sin #7");
+
+        test(DfpMath.sin(factory.getPi().divide(2).negate()),
+             factory.getOne().negate(),
+             DfpField.FLAG_INEXACT, "sin #8");
+
+        test(DfpMath.sin(DfpMath.atan(factory.getOne())),  // pi/4
+             factory.newDfp("0.5").sqrt(),
+             DfpField.FLAG_INEXACT, "sin #9");
+
+        test(DfpMath.sin(DfpMath.atan(factory.getOne())).negate(),  // -pi/4
+             factory.newDfp("0.5").sqrt().negate(),
+             DfpField.FLAG_INEXACT, "sin #10");
+
+        test(DfpMath.sin(DfpMath.atan(factory.getOne())).negate(),  // -pi/4
+             factory.newDfp("0.5").sqrt().negate(),
+             DfpField.FLAG_INEXACT, "sin #11");
+
+        test(DfpMath.sin(factory.newDfp("0.1")),
+             factory.newDfp("0.0998334166468281523"),
+             DfpField.FLAG_INEXACT, "sin #12");
+
+        test(DfpMath.sin(factory.newDfp("0.2")),
+             factory.newDfp("0.19866933079506121546"),
+             DfpField.FLAG_INEXACT, "sin #13");
+
+        test(DfpMath.sin(factory.newDfp("0.3")),
+             factory.newDfp("0.2955202066613395751"),
+             DfpField.FLAG_INEXACT, "sin #14");
+
+        test(DfpMath.sin(factory.newDfp("0.4")),
+             factory.newDfp("0.38941834230865049166"),
+             DfpField.FLAG_INEXACT, "sin #15");
+
+        test(DfpMath.sin(factory.newDfp("0.5")),
+             factory.newDfp("0.47942553860420300026"),  // off by one ULP
+             DfpField.FLAG_INEXACT, "sin #16");
+
+        test(DfpMath.sin(factory.newDfp("0.6")),
+             factory.newDfp("0.56464247339503535721"),  // off by one ULP
+             DfpField.FLAG_INEXACT, "sin #17");
+
+        test(DfpMath.sin(factory.newDfp("0.7")),
+             factory.newDfp("0.64421768723769105367"),  
+             DfpField.FLAG_INEXACT, "sin #18");
+
+        test(DfpMath.sin(factory.newDfp("0.8")),        
+             factory.newDfp("0.71735609089952276163"),
+             DfpField.FLAG_INEXACT, "sin #19");
+
+        test(DfpMath.sin(factory.newDfp("0.9")),        // off by one ULP
+             factory.newDfp("0.78332690962748338847"),
+             DfpField.FLAG_INEXACT, "sin #20");
+
+        test(DfpMath.sin(factory.newDfp("1.0")),
+             factory.newDfp("0.84147098480789650666"),
+             DfpField.FLAG_INEXACT, "sin #21");
+
+        test(DfpMath.sin(factory.newDfp("1.1")),
+             factory.newDfp("0.89120736006143533995"),
+             DfpField.FLAG_INEXACT, "sin #22");
+
+        test(DfpMath.sin(factory.newDfp("1.2")),
+             factory.newDfp("0.93203908596722634968"),
+             DfpField.FLAG_INEXACT, "sin #23");
+
+        test(DfpMath.sin(factory.newDfp("1.3")),
+             factory.newDfp("0.9635581854171929647"),
+             DfpField.FLAG_INEXACT, "sin #24");
+
+        test(DfpMath.sin(factory.newDfp("1.4")),
+             factory.newDfp("0.98544972998846018066"),
+             DfpField.FLAG_INEXACT, "sin #25");
+
+        test(DfpMath.sin(factory.newDfp("1.5")),
+             factory.newDfp("0.99749498660405443096"),
+             DfpField.FLAG_INEXACT, "sin #26");
+
+        test(DfpMath.sin(factory.newDfp("1.6")),
+             factory.newDfp("0.99957360304150516323"),
+             DfpField.FLAG_INEXACT, "sin #27");
+    }
+
+
+}
diff --git a/src/test/java/org/apache/commons/math/dfp/DfpTest.java b/src/test/java/org/apache/commons/math/dfp/DfpTest.java
new file mode 100644
index 0000000..db5c05c
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/dfp/DfpTest.java
@@ -0,0 +1,1507 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class DfpTest {
+
+    private DfpField field;
+    private Dfp pinf;
+    private Dfp ninf;
+    private Dfp nan;
+    private Dfp snan;
+    private Dfp qnan;
+
+    @Before
+    public void setUp() {
+        // Some basic setup.  Define some constants and clear the status flags
+        field = new DfpField(20);
+        pinf = field.newDfp("1").divide(field.newDfp("0"));
+        ninf = field.newDfp("-1").divide(field.newDfp("0"));
+        nan = field.newDfp("0").divide(field.newDfp("0"));
+        snan = field.newDfp((byte)1, Dfp.SNAN);
+        qnan = field.newDfp((byte)1, Dfp.QNAN);
+        ninf.getField().clearIEEEFlags();
+    }
+
+    @After
+    public void tearDown() {
+        field = null;
+        pinf    = null;
+        ninf    = null;
+        nan     = null;
+        snan    = null;
+        qnan    = null;
+    }
+
+    // Generic test function.  Takes params x and y and tests them for 
+    // equality.  Then checks the status flags against the flags argument.
+    // If the test fail, it prints the desc string
+    private void test(Dfp x, Dfp y, int flags, String desc)
+    {
+        boolean b = x.equals(y);
+
+        if (!x.equals(y) && !x.unequal(y))  // NaNs involved 
+            b = (x.toString().equals(y.toString()));
+
+        if (x.equals(field.newDfp("0")))  // distinguish +/- zero
+            b = (b && (x.toString().equals(y.toString())));
+
+        b = (b && x.getField().getIEEEFlags() == flags);
+
+        if (!b)
+            Assert.assertTrue("assersion failed "+desc+" x = "+x.toString()+" flags = "+x.getField().getIEEEFlags(), b);
+
+        x.getField().clearIEEEFlags();
+    }
+
+    @Test
+    public void testByteConstructor() {
+        Assert.assertEquals("0.", new Dfp(field, (byte) 0).toString());
+        Assert.assertEquals("1.", new Dfp(field, (byte) 1).toString());
+        Assert.assertEquals("-1.", new Dfp(field, (byte) -1).toString());
+        Assert.assertEquals("-128.", new Dfp(field, Byte.MIN_VALUE).toString());
+        Assert.assertEquals("127.", new Dfp(field, Byte.MAX_VALUE).toString());
+    }
+
+    @Test
+    public void testIntConstructor() {
+        Assert.assertEquals("0.", new Dfp(field, 0).toString());
+        Assert.assertEquals("1.", new Dfp(field, 1).toString());
+        Assert.assertEquals("-1.", new Dfp(field, -1).toString());
+        Assert.assertEquals("1234567890.", new Dfp(field, 1234567890).toString());
+        Assert.assertEquals("-1234567890.", new Dfp(field, -1234567890).toString());
+        Assert.assertEquals("-2147483648.", new Dfp(field, Integer.MIN_VALUE).toString());
+        Assert.assertEquals("2147483647.", new Dfp(field, Integer.MAX_VALUE).toString());
+    }
+
+    @Test
+    public void testLongConstructor() {
+        Assert.assertEquals("0.", new Dfp(field, 0l).toString());
+        Assert.assertEquals("1.", new Dfp(field, 1l).toString());
+        Assert.assertEquals("-1.", new Dfp(field, -1l).toString());
+        Assert.assertEquals("1234567890.", new Dfp(field, 1234567890l).toString());
+        Assert.assertEquals("-1234567890.", new Dfp(field, -1234567890l).toString());
+        Assert.assertEquals("-9223372036854775808.", new Dfp(field, Long.MIN_VALUE).toString());
+        Assert.assertEquals("9223372036854775807.", new Dfp(field, Long.MAX_VALUE).toString());
+    }
+
+    /*
+     *  Test addition
+     */
+    @Test
+    public void testAdd()
+    {
+        test(field.newDfp("1").add(field.newDfp("1")),      // Basic tests   1+1 = 2
+             field.newDfp("2"), 
+             0, "Add #1");
+
+        test(field.newDfp("1").add(field.newDfp("-1")),     // 1 + (-1) = 0
+             field.newDfp("0"), 
+             0, "Add #2");
+
+        test(field.newDfp("-1").add(field.newDfp("1")),     // (-1) + 1 = 0
+             field.newDfp("0"), 
+             0, "Add #3");
+
+        test(field.newDfp("-1").add(field.newDfp("-1")),     // (-1) + (-1) = -2
+             field.newDfp("-2"), 
+             0, "Add #4");
+
+        // rounding mode is round half even
+
+        test(field.newDfp("1").add(field.newDfp("1e-16")),     // rounding on add
+             field.newDfp("1.0000000000000001"), 
+             0, "Add #5");
+
+        test(field.newDfp("1").add(field.newDfp("1e-17")),     // rounding on add
+             field.newDfp("1"), 
+             DfpField.FLAG_INEXACT, "Add #6");
+
+        test(field.newDfp("0.90999999999999999999").add(field.newDfp("0.1")),     // rounding on add
+             field.newDfp("1.01"), 
+             DfpField.FLAG_INEXACT, "Add #7");
+
+        test(field.newDfp(".10000000000000005000").add(field.newDfp(".9")),     // rounding on add
+             field.newDfp("1."), 
+             DfpField.FLAG_INEXACT, "Add #8");
+
+        test(field.newDfp(".10000000000000015000").add(field.newDfp(".9")),     // rounding on add
+             field.newDfp("1.0000000000000002"), 
+             DfpField.FLAG_INEXACT, "Add #9");
+
+        test(field.newDfp(".10000000000000014999").add(field.newDfp(".9")),     // rounding on add
+             field.newDfp("1.0000000000000001"), 
+             DfpField.FLAG_INEXACT, "Add #10");
+
+        test(field.newDfp(".10000000000000015001").add(field.newDfp(".9")),     // rounding on add
+             field.newDfp("1.0000000000000002"), 
+             DfpField.FLAG_INEXACT, "Add #11");
+
+        test(field.newDfp(".11111111111111111111").add(field.newDfp("11.1111111111111111")), // rounding on add
+             field.newDfp("11.22222222222222222222"), 
+             DfpField.FLAG_INEXACT, "Add #12");
+
+        test(field.newDfp(".11111111111111111111").add(field.newDfp("1111111111111111.1111")), // rounding on add
+             field.newDfp("1111111111111111.2222"), 
+             DfpField.FLAG_INEXACT, "Add #13");
+
+        test(field.newDfp(".11111111111111111111").add(field.newDfp("11111111111111111111")), // rounding on add
+             field.newDfp("11111111111111111111"), 
+             DfpField.FLAG_INEXACT, "Add #14");
+
+        test(field.newDfp("9.9999999999999999999e131071").add(field.newDfp("-1e131052")), // overflow on add
+             field.newDfp("9.9999999999999999998e131071"), 
+             0, "Add #15");
+
+        test(field.newDfp("9.9999999999999999999e131071").add(field.newDfp("1e131052")), // overflow on add
+             pinf, 
+             DfpField.FLAG_OVERFLOW, "Add #16");
+
+        test(field.newDfp("-9.9999999999999999999e131071").add(field.newDfp("-1e131052")), // overflow on add
+             ninf, 
+             DfpField.FLAG_OVERFLOW, "Add #17");
+
+        test(field.newDfp("-9.9999999999999999999e131071").add(field.newDfp("1e131052")), // overflow on add
+             field.newDfp("-9.9999999999999999998e131071"), 
+             0, "Add #18");
+
+        test(field.newDfp("1e-131072").add(field.newDfp("1e-131072")), // underflow on add
+             field.newDfp("2e-131072"), 
+             0, "Add #19");
+
+        test(field.newDfp("1.0000000000000001e-131057").add(field.newDfp("-1e-131057")), // underflow on add
+             field.newDfp("1e-131073"), 
+             DfpField.FLAG_UNDERFLOW, "Add #20");
+
+        test(field.newDfp("1.1e-131072").add(field.newDfp("-1e-131072")), // underflow on add
+             field.newDfp("1e-131073"), 
+             DfpField.FLAG_UNDERFLOW, "Add #21");
+
+        test(field.newDfp("1.0000000000000001e-131072").add(field.newDfp("-1e-131072")), // underflow on add
+             field.newDfp("1e-131088"), 
+             DfpField.FLAG_UNDERFLOW, "Add #22");
+
+        test(field.newDfp("1.0000000000000001e-131078").add(field.newDfp("-1e-131078")), // underflow on add
+             field.newDfp("0"), 
+             DfpField.FLAG_UNDERFLOW, "Add #23");
+
+        test(field.newDfp("1.0").add(field.newDfp("-1e-20")), // loss of precision on alignment?
+             field.newDfp("0.99999999999999999999"), 
+             0, "Add #23.1");
+
+        test(field.newDfp("-0.99999999999999999999").add(field.newDfp("1")), // proper normalization?
+             field.newDfp("0.00000000000000000001"), 
+             0, "Add #23.2");
+
+        test(field.newDfp("1").add(field.newDfp("0")), // adding zeros
+             field.newDfp("1"), 
+             0, "Add #24");
+
+        test(field.newDfp("0").add(field.newDfp("0")), // adding zeros
+             field.newDfp("0"), 
+             0, "Add #25");
+
+        test(field.newDfp("-0").add(field.newDfp("0")), // adding zeros
+             field.newDfp("0"), 
+             0, "Add #26");
+
+        test(field.newDfp("0").add(field.newDfp("-0")), // adding zeros
+             field.newDfp("0"), 
+             0, "Add #27");
+
+        test(field.newDfp("-0").add(field.newDfp("-0")), // adding zeros
+             field.newDfp("-0"), 
+             0, "Add #28");
+
+        test(field.newDfp("1e-20").add(field.newDfp("0")), // adding zeros
+             field.newDfp("1e-20"), 
+             0, "Add #29");
+
+        test(field.newDfp("1e-40").add(field.newDfp("0")), // adding zeros
+             field.newDfp("1e-40"), 
+             0, "Add #30");
+
+        test(pinf.add(ninf), // adding infinities
+             nan, 
+             DfpField.FLAG_INVALID, "Add #31");
+
+        test(ninf.add(pinf), // adding infinities
+             nan, 
+             DfpField.FLAG_INVALID, "Add #32");
+
+        test(ninf.add(ninf), // adding infinities
+             ninf, 
+             0, "Add #33");
+
+        test(pinf.add(pinf), // adding infinities
+             pinf, 
+             0, "Add #34");
+
+        test(pinf.add(field.newDfp("0")), // adding infinities
+             pinf, 
+             0, "Add #35");
+
+        test(pinf.add(field.newDfp("-1e131071")), // adding infinities
+             pinf, 
+             0, "Add #36");
+
+        test(pinf.add(field.newDfp("1e131071")), // adding infinities
+             pinf, 
+             0, "Add #37");
+
+        test(field.newDfp("0").add(pinf), // adding infinities
+             pinf, 
+             0, "Add #38");
+
+        test(field.newDfp("-1e131071").add(pinf), // adding infinities
+             pinf, 
+             0, "Add #39");
+
+        test(field.newDfp("1e131071").add(pinf), // adding infinities
+             pinf, 
+             0, "Add #40");
+
+        test(ninf.add(field.newDfp("0")), // adding infinities
+             ninf, 
+             0, "Add #41");
+
+        test(ninf.add(field.newDfp("-1e131071")), // adding infinities
+             ninf, 
+             0, "Add #42");
+
+        test(ninf.add(field.newDfp("1e131071")), // adding infinities
+             ninf, 
+             0, "Add #43");
+
+        test(field.newDfp("0").add(ninf), // adding infinities
+             ninf, 
+             0, "Add #44");
+
+        test(field.newDfp("-1e131071").add(ninf), // adding infinities
+             ninf, 
+             0, "Add #45");
+
+        test(field.newDfp("1e131071").add(ninf), // adding infinities
+             ninf, 
+             0, "Add #46");
+
+        test(field.newDfp("9.9999999999999999999e131071").add(field.newDfp("5e131051")),  // overflow
+             pinf,
+             DfpField.FLAG_OVERFLOW, "Add #47");
+
+        test(field.newDfp("9.9999999999999999999e131071").add(field.newDfp("4.9999999999999999999e131051")),  // overflow
+             field.newDfp("9.9999999999999999999e131071"),
+             DfpField.FLAG_INEXACT, "Add #48");
+
+        test(nan.add(field.newDfp("1")),
+             nan,
+             0, "Add #49");
+
+        test(field.newDfp("1").add(nan),
+             nan,
+             0, "Add #50");
+
+        test(field.newDfp("12345678123456781234").add(field.newDfp("0.12345678123456781234")),
+             field.newDfp("12345678123456781234"),
+             DfpField.FLAG_INEXACT, "Add #51");
+
+        test(field.newDfp("12345678123456781234").add(field.newDfp("123.45678123456781234")),
+             field.newDfp("12345678123456781357"),
+             DfpField.FLAG_INEXACT, "Add #52");
+
+        test(field.newDfp("123.45678123456781234").add(field.newDfp("12345678123456781234")),
+             field.newDfp("12345678123456781357"),
+             DfpField.FLAG_INEXACT, "Add #53");
+
+        test(field.newDfp("12345678123456781234").add(field.newDfp(".00001234567812345678")),
+             field.newDfp("12345678123456781234"),
+             DfpField.FLAG_INEXACT, "Add #54");
+
+        test(field.newDfp("12345678123456781234").add(field.newDfp(".00000000123456781234")),
+             field.newDfp("12345678123456781234"),
+             DfpField.FLAG_INEXACT, "Add #55");
+
+        test(field.newDfp("-0").add(field.newDfp("-0")),
+             field.newDfp("-0"),
+             0, "Add #56"); 
+
+        test(field.newDfp("0").add(field.newDfp("-0")),
+             field.newDfp("0"),
+             0, "Add #57"); 
+
+        test(field.newDfp("-0").add(field.newDfp("0")),
+             field.newDfp("0"),
+             0, "Add #58"); 
+
+        test(field.newDfp("0").add(field.newDfp("0")),
+             field.newDfp("0"),
+             0, "Add #59"); 
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+    // Test comparisons
+
+    // utility function to help test comparisons
+    private void cmptst(Dfp a, Dfp b, String op, boolean result, double num)
+    {
+        if (op == "equal")
+            if (a.equals(b) != result)
+                Assert.fail("assersion failed.  "+op+" compare #"+num);
+
+        if (op == "unequal")
+            if (a.unequal(b) != result)
+                Assert.fail("assersion failed.  "+op+" compare #"+num);
+
+        if (op == "lessThan")
+            if (a.lessThan(b) != result)
+                Assert.fail("assersion failed.  "+op+" compare #"+num);
+
+        if (op == "greaterThan")
+            if (a.greaterThan(b) != result)
+                Assert.fail("assersion failed.  "+op+" compare #"+num);
+    }
+
+    @Test
+    public void  testCompare()
+    {
+        // test equal() comparison
+        // check zero vs. zero
+        field.clearIEEEFlags();
+
+        cmptst(field.newDfp("0"), field.newDfp("0"), "equal", true, 1);         // 0 == 0
+        cmptst(field.newDfp("0"), field.newDfp("-0"), "equal", true, 2);        // 0 == -0
+        cmptst(field.newDfp("-0"), field.newDfp("-0"), "equal", true, 3);       // -0 == -0
+        cmptst(field.newDfp("-0"), field.newDfp("0"), "equal", true, 4);        // -0 == 0
+
+        // check zero vs normal numbers
+
+        cmptst(field.newDfp("0"), field.newDfp("1"), "equal", false, 5);         // 0 == 1
+        cmptst(field.newDfp("1"), field.newDfp("0"), "equal", false, 6);         // 1 == 0
+        cmptst(field.newDfp("-1"), field.newDfp("0"), "equal", false, 7);        // -1 == 0
+        cmptst(field.newDfp("0"), field.newDfp("-1"), "equal", false, 8);        // 0 == -1
+        cmptst(field.newDfp("0"), field.newDfp("1e-131072"), "equal", false, 9); // 0 == 1e-131072
+        // check flags 
+        if (field.getIEEEFlags() != 0)
+            Assert.fail("assersion failed.  compare flags = "+field.getIEEEFlags());
+
+        cmptst(field.newDfp("0"), field.newDfp("1e-131078"), "equal", false, 10); // 0 == 1e-131078
+
+        // check flags  -- underflow should be set
+        if (field.getIEEEFlags() != DfpField.FLAG_UNDERFLOW)
+            Assert.fail("assersion failed.  compare flags = "+field.getIEEEFlags());
+
+        field.clearIEEEFlags();
+
+        cmptst(field.newDfp("0"), field.newDfp("1e+131071"), "equal", false, 11); // 0 == 1e+131071
+
+        // check zero vs infinities
+
+        cmptst(field.newDfp("0"), pinf, "equal", false, 12);    // 0 == pinf
+        cmptst(field.newDfp("0"), ninf, "equal", false, 13);    // 0 == ninf
+        cmptst(field.newDfp("-0"), pinf, "equal", false, 14);   // -0 == pinf
+        cmptst(field.newDfp("-0"), ninf, "equal", false, 15);   // -0 == ninf
+        cmptst(pinf, field.newDfp("0"), "equal", false, 16);    // pinf == 0
+        cmptst(ninf, field.newDfp("0"), "equal", false, 17);    // ninf == 0
+        cmptst(pinf, field.newDfp("-0"), "equal", false, 18);   // pinf == -0
+        cmptst(ninf, field.newDfp("-0"), "equal", false, 19);   // ninf == -0
+        cmptst(ninf, pinf, "equal", false, 19.10);     // ninf == pinf
+        cmptst(pinf, ninf, "equal", false, 19.11);     // pinf == ninf
+        cmptst(pinf, pinf, "equal", true, 19.12);     // pinf == pinf
+        cmptst(ninf, ninf, "equal", true, 19.13);     // ninf == ninf
+
+        // check some normal numbers
+        cmptst(field.newDfp("1"), field.newDfp("1"), "equal", true, 20);   // 1 == 1
+        cmptst(field.newDfp("1"), field.newDfp("-1"), "equal", false, 21);   // 1 == -1
+        cmptst(field.newDfp("-1"), field.newDfp("-1"), "equal", true, 22);   // -1 == -1
+        cmptst(field.newDfp("1"), field.newDfp("1.0000000000000001"), "equal", false, 23);   // 1 == 1.0000000000000001
+
+        // The tests below checks to ensure that comparisons don't set FLAG_INEXACT
+        // 100000 == 1.0000000000000001
+        cmptst(field.newDfp("1e20"), field.newDfp("1.0000000000000001"), "equal", false, 24);
+        if (field.getIEEEFlags() != 0)
+            Assert.fail("assersion failed.  compare flags = "+field.getIEEEFlags());
+
+        cmptst(field.newDfp("0.000001"), field.newDfp("1e-6"), "equal", true, 25);
+
+        // check some nans -- nans shouldnt equal anything
+
+        cmptst(snan, snan, "equal", false, 27);
+        cmptst(qnan, qnan, "equal", false, 28);
+        cmptst(snan, qnan, "equal", false, 29);
+        cmptst(qnan, snan, "equal", false, 30);
+        cmptst(qnan, field.newDfp("0"), "equal", false, 31);
+        cmptst(snan, field.newDfp("0"), "equal", false, 32);
+        cmptst(field.newDfp("0"), snan, "equal", false, 33);
+        cmptst(field.newDfp("0"), qnan, "equal", false, 34);
+        cmptst(qnan, pinf, "equal", false, 35);
+        cmptst(snan, pinf, "equal", false, 36);
+        cmptst(pinf, snan, "equal", false, 37);
+        cmptst(pinf, qnan, "equal", false, 38);
+        cmptst(qnan, ninf, "equal", false, 39);
+        cmptst(snan, ninf, "equal", false, 40);
+        cmptst(ninf, snan, "equal", false, 41);
+        cmptst(ninf, qnan, "equal", false, 42);
+        cmptst(qnan, field.newDfp("-1"), "equal", false, 43);
+        cmptst(snan, field.newDfp("-1"), "equal", false, 44);
+        cmptst(field.newDfp("-1"), snan, "equal", false, 45);
+        cmptst(field.newDfp("-1"), qnan, "equal", false, 46);
+        cmptst(qnan, field.newDfp("1"), "equal", false, 47);
+        cmptst(snan, field.newDfp("1"), "equal", false, 48);
+        cmptst(field.newDfp("1"), snan, "equal", false, 49);
+        cmptst(field.newDfp("1"), qnan, "equal", false, 50);
+        cmptst(snan.negate(), snan, "equal", false, 51);
+        cmptst(qnan.negate(), qnan, "equal", false, 52);
+
+        //
+        // Tests for un equal  -- do it all over again
+        //
+
+        cmptst(field.newDfp("0"), field.newDfp("0"), "unequal", false, 1);         // 0 == 0
+        cmptst(field.newDfp("0"), field.newDfp("-0"), "unequal", false, 2);        // 0 == -0
+        cmptst(field.newDfp("-0"), field.newDfp("-0"), "unequal", false, 3);       // -0 == -0
+        cmptst(field.newDfp("-0"), field.newDfp("0"), "unequal", false, 4);        // -0 == 0
+
+        // check zero vs normal numbers
+
+        cmptst(field.newDfp("0"), field.newDfp("1"), "unequal", true, 5);         // 0 == 1
+        cmptst(field.newDfp("1"), field.newDfp("0"), "unequal", true, 6);         // 1 == 0
+        cmptst(field.newDfp("-1"), field.newDfp("0"), "unequal", true, 7);        // -1 == 0
+        cmptst(field.newDfp("0"), field.newDfp("-1"), "unequal", true, 8);        // 0 == -1
+        cmptst(field.newDfp("0"), field.newDfp("1e-131072"), "unequal", true, 9); // 0 == 1e-131072
+        // check flags 
+        if (field.getIEEEFlags() != 0)
+            Assert.fail("assersion failed.  compare flags = "+field.getIEEEFlags());
+
+        cmptst(field.newDfp("0"), field.newDfp("1e-131078"), "unequal", true, 10); // 0 == 1e-131078
+
+        // check flags  -- underflow should be set
+        if (field.getIEEEFlags() != DfpField.FLAG_UNDERFLOW)
+            Assert.fail("assersion failed.  compare flags = "+field.getIEEEFlags());
+
+        field.clearIEEEFlags();
+
+        cmptst(field.newDfp("0"), field.newDfp("1e+131071"), "unequal", true, 11); // 0 == 1e+131071
+
+        // check zero vs infinities
+
+        cmptst(field.newDfp("0"), pinf, "unequal", true, 12);    // 0 == pinf
+        cmptst(field.newDfp("0"), ninf, "unequal", true, 13);    // 0 == ninf
+        cmptst(field.newDfp("-0"), pinf, "unequal", true, 14);   // -0 == pinf
+        cmptst(field.newDfp("-0"), ninf, "unequal", true, 15);   // -0 == ninf
+        cmptst(pinf, field.newDfp("0"), "unequal", true, 16);    // pinf == 0
+        cmptst(ninf, field.newDfp("0"), "unequal", true, 17);    // ninf == 0
+        cmptst(pinf, field.newDfp("-0"), "unequal", true, 18);   // pinf == -0
+        cmptst(ninf, field.newDfp("-0"), "unequal", true, 19);   // ninf == -0
+        cmptst(ninf, pinf, "unequal", true, 19.10);     // ninf == pinf
+        cmptst(pinf, ninf, "unequal", true, 19.11);     // pinf == ninf
+        cmptst(pinf, pinf, "unequal", false, 19.12);     // pinf == pinf
+        cmptst(ninf, ninf, "unequal", false, 19.13);     // ninf == ninf
+
+        // check some normal numbers
+        cmptst(field.newDfp("1"), field.newDfp("1"), "unequal", false, 20);   // 1 == 1
+        cmptst(field.newDfp("1"), field.newDfp("-1"), "unequal", true, 21);   // 1 == -1
+        cmptst(field.newDfp("-1"), field.newDfp("-1"), "unequal", false, 22);   // -1 == -1
+        cmptst(field.newDfp("1"), field.newDfp("1.0000000000000001"), "unequal", true, 23);   // 1 == 1.0000000000000001
+
+        // The tests below checks to ensure that comparisons don't set FLAG_INEXACT
+        // 100000 == 1.0000000000000001
+        cmptst(field.newDfp("1e20"), field.newDfp("1.0000000000000001"), "unequal", true, 24);
+        if (field.getIEEEFlags() != 0)
+            Assert.fail("assersion failed.  compare flags = "+field.getIEEEFlags());
+
+        cmptst(field.newDfp("0.000001"), field.newDfp("1e-6"), "unequal", false, 25);
+
+        // check some nans -- nans shouldnt be unequal to anything
+
+        cmptst(snan, snan, "unequal", false, 27);
+        cmptst(qnan, qnan, "unequal", false, 28);
+        cmptst(snan, qnan, "unequal", false, 29);
+        cmptst(qnan, snan, "unequal", false, 30);
+        cmptst(qnan, field.newDfp("0"), "unequal", false, 31);
+        cmptst(snan, field.newDfp("0"), "unequal", false, 32);
+        cmptst(field.newDfp("0"), snan, "unequal", false, 33);
+        cmptst(field.newDfp("0"), qnan, "unequal", false, 34);
+        cmptst(qnan, pinf, "unequal", false, 35);
+        cmptst(snan, pinf, "unequal", false, 36);
+        cmptst(pinf, snan, "unequal", false, 37);
+        cmptst(pinf, qnan, "unequal", false, 38);
+        cmptst(qnan, ninf, "unequal", false, 39);
+        cmptst(snan, ninf, "unequal", false, 40);
+        cmptst(ninf, snan, "unequal", false, 41);
+        cmptst(ninf, qnan, "unequal", false, 42);
+        cmptst(qnan, field.newDfp("-1"), "unequal", false, 43);
+        cmptst(snan, field.newDfp("-1"), "unequal", false, 44);
+        cmptst(field.newDfp("-1"), snan, "unequal", false, 45);
+        cmptst(field.newDfp("-1"), qnan, "unequal", false, 46);
+        cmptst(qnan, field.newDfp("1"), "unequal", false, 47);
+        cmptst(snan, field.newDfp("1"), "unequal", false, 48);
+        cmptst(field.newDfp("1"), snan, "unequal", false, 49);
+        cmptst(field.newDfp("1"), qnan, "unequal", false, 50);
+        cmptst(snan.negate(), snan, "unequal", false, 51);
+        cmptst(qnan.negate(), qnan, "unequal", false, 52);
+
+        if (field.getIEEEFlags() != 0)
+            Assert.fail("assersion failed.  compare unequal flags = "+field.getIEEEFlags());
+
+        //
+        // Tests for lessThan  -- do it all over again
+        //
+
+        cmptst(field.newDfp("0"), field.newDfp("0"), "lessThan", false, 1);         // 0 < 0
+        cmptst(field.newDfp("0"), field.newDfp("-0"), "lessThan", false, 2);        // 0 < -0
+        cmptst(field.newDfp("-0"), field.newDfp("-0"), "lessThan", false, 3);       // -0 < -0
+        cmptst(field.newDfp("-0"), field.newDfp("0"), "lessThan", false, 4);        // -0 < 0
+
+        // check zero vs normal numbers
+
+        cmptst(field.newDfp("0"), field.newDfp("1"), "lessThan", true, 5);         // 0 < 1
+        cmptst(field.newDfp("1"), field.newDfp("0"), "lessThan", false, 6);         // 1 < 0
+        cmptst(field.newDfp("-1"), field.newDfp("0"), "lessThan", true, 7);        // -1 < 0
+        cmptst(field.newDfp("0"), field.newDfp("-1"), "lessThan", false, 8);        // 0 < -1
+        cmptst(field.newDfp("0"), field.newDfp("1e-131072"), "lessThan", true, 9); // 0 < 1e-131072
+        // check flags 
+        if (field.getIEEEFlags() != 0)
+            Assert.fail("assersion failed.  compare flags = "+field.getIEEEFlags());
+
+        cmptst(field.newDfp("0"), field.newDfp("1e-131078"), "lessThan", true, 10); // 0 < 1e-131078
+
+        // check flags  -- underflow should be set
+        if (field.getIEEEFlags() != DfpField.FLAG_UNDERFLOW)
+            Assert.fail("assersion failed.  compare flags = "+field.getIEEEFlags());
+        field.clearIEEEFlags();
+
+        cmptst(field.newDfp("0"), field.newDfp("1e+131071"), "lessThan", true, 11); // 0 < 1e+131071
+
+        // check zero vs infinities
+
+        cmptst(field.newDfp("0"), pinf, "lessThan", true, 12);    // 0 < pinf
+        cmptst(field.newDfp("0"), ninf, "lessThan", false, 13);    // 0 < ninf
+        cmptst(field.newDfp("-0"), pinf, "lessThan", true, 14);   // -0 < pinf
+        cmptst(field.newDfp("-0"), ninf, "lessThan", false, 15);   // -0 < ninf
+        cmptst(pinf, field.newDfp("0"), "lessThan", false, 16);    // pinf < 0
+        cmptst(ninf, field.newDfp("0"), "lessThan", true, 17);    // ninf < 0
+        cmptst(pinf, field.newDfp("-0"), "lessThan", false, 18);   // pinf < -0
+        cmptst(ninf, field.newDfp("-0"), "lessThan", true, 19);   // ninf < -0
+        cmptst(ninf, pinf, "lessThan", true, 19.10);     // ninf < pinf
+        cmptst(pinf, ninf, "lessThan", false, 19.11);     // pinf < ninf
+        cmptst(pinf, pinf, "lessThan", false, 19.12);     // pinf < pinf
+        cmptst(ninf, ninf, "lessThan", false, 19.13);     // ninf < ninf
+
+        // check some normal numbers
+        cmptst(field.newDfp("1"), field.newDfp("1"), "lessThan", false, 20);   // 1 < 1
+        cmptst(field.newDfp("1"), field.newDfp("-1"), "lessThan", false, 21);   // 1 < -1
+        cmptst(field.newDfp("-1"), field.newDfp("-1"), "lessThan", false, 22);   // -1 < -1
+        cmptst(field.newDfp("1"), field.newDfp("1.0000000000000001"), "lessThan", true, 23);   // 1 < 1.0000000000000001
+
+        // The tests below checks to ensure that comparisons don't set FLAG_INEXACT
+        // 100000 < 1.0000000000000001
+        cmptst(field.newDfp("1e20"), field.newDfp("1.0000000000000001"), "lessThan", false, 24);
+        if (field.getIEEEFlags() != 0)
+            Assert.fail("assersion failed.  compare flags = "+field.getIEEEFlags());
+
+        cmptst(field.newDfp("0.000001"), field.newDfp("1e-6"), "lessThan", false, 25);
+
+        // check some nans -- nans shouldnt be lessThan to anything
+        cmptst(snan, snan, "lessThan", false, 27);
+        cmptst(qnan, qnan, "lessThan", false, 28);
+        cmptst(snan, qnan, "lessThan", false, 29);
+        cmptst(qnan, snan, "lessThan", false, 30);
+        cmptst(qnan, field.newDfp("0"), "lessThan", false, 31);
+        cmptst(snan, field.newDfp("0"), "lessThan", false, 32);
+        cmptst(field.newDfp("0"), snan, "lessThan", false, 33);
+        cmptst(field.newDfp("0"), qnan, "lessThan", false, 34);
+        cmptst(qnan, pinf, "lessThan", false, 35);
+        cmptst(snan, pinf, "lessThan", false, 36);
+        cmptst(pinf, snan, "lessThan", false, 37);
+        cmptst(pinf, qnan, "lessThan", false, 38);
+        cmptst(qnan, ninf, "lessThan", false, 39);
+        cmptst(snan, ninf, "lessThan", false, 40);
+        cmptst(ninf, snan, "lessThan", false, 41);
+        cmptst(ninf, qnan, "lessThan", false, 42);
+        cmptst(qnan, field.newDfp("-1"), "lessThan", false, 43);
+        cmptst(snan, field.newDfp("-1"), "lessThan", false, 44);
+        cmptst(field.newDfp("-1"), snan, "lessThan", false, 45);
+        cmptst(field.newDfp("-1"), qnan, "lessThan", false, 46);
+        cmptst(qnan, field.newDfp("1"), "lessThan", false, 47);
+        cmptst(snan, field.newDfp("1"), "lessThan", false, 48);
+        cmptst(field.newDfp("1"), snan, "lessThan", false, 49);
+        cmptst(field.newDfp("1"), qnan, "lessThan", false, 50);
+        cmptst(snan.negate(), snan, "lessThan", false, 51);
+        cmptst(qnan.negate(), qnan, "lessThan", false, 52);
+
+        //lessThan compares with nans should raise FLAG_INVALID
+        if (field.getIEEEFlags() != DfpField.FLAG_INVALID)
+            Assert.fail("assersion failed.  compare lessThan flags = "+field.getIEEEFlags());
+        field.clearIEEEFlags();
+
+        //
+        // Tests for greaterThan  -- do it all over again
+        //
+
+        cmptst(field.newDfp("0"), field.newDfp("0"), "greaterThan", false, 1);         // 0 > 0
+        cmptst(field.newDfp("0"), field.newDfp("-0"), "greaterThan", false, 2);        // 0 > -0
+        cmptst(field.newDfp("-0"), field.newDfp("-0"), "greaterThan", false, 3);       // -0 > -0
+        cmptst(field.newDfp("-0"), field.newDfp("0"), "greaterThan", false, 4);        // -0 > 0
+
+        // check zero vs normal numbers
+
+        cmptst(field.newDfp("0"), field.newDfp("1"), "greaterThan", false, 5);         // 0 > 1
+        cmptst(field.newDfp("1"), field.newDfp("0"), "greaterThan", true, 6);         // 1 > 0
+        cmptst(field.newDfp("-1"), field.newDfp("0"), "greaterThan", false, 7);        // -1 > 0
+        cmptst(field.newDfp("0"), field.newDfp("-1"), "greaterThan", true, 8);        // 0 > -1
+        cmptst(field.newDfp("0"), field.newDfp("1e-131072"), "greaterThan", false, 9); // 0 > 1e-131072
+        // check flags 
+        if (field.getIEEEFlags() != 0)
+            Assert.fail("assersion failed.  compare flags = "+field.getIEEEFlags());
+
+        cmptst(field.newDfp("0"), field.newDfp("1e-131078"), "greaterThan", false, 10); // 0 > 1e-131078
+
+        // check flags  -- underflow should be set
+        if (field.getIEEEFlags() != DfpField.FLAG_UNDERFLOW)
+            Assert.fail("assersion failed.  compare flags = "+field.getIEEEFlags());
+        field.clearIEEEFlags();
+
+        cmptst(field.newDfp("0"), field.newDfp("1e+131071"), "greaterThan", false, 11); // 0 > 1e+131071
+
+        // check zero vs infinities
+
+        cmptst(field.newDfp("0"), pinf, "greaterThan", false, 12);    // 0 > pinf
+        cmptst(field.newDfp("0"), ninf, "greaterThan", true, 13);    // 0 > ninf
+        cmptst(field.newDfp("-0"), pinf, "greaterThan", false, 14);   // -0 > pinf
+        cmptst(field.newDfp("-0"), ninf, "greaterThan", true, 15);   // -0 > ninf
+        cmptst(pinf, field.newDfp("0"), "greaterThan", true, 16);    // pinf > 0
+        cmptst(ninf, field.newDfp("0"), "greaterThan", false, 17);    // ninf > 0
+        cmptst(pinf, field.newDfp("-0"), "greaterThan", true, 18);   // pinf > -0
+        cmptst(ninf, field.newDfp("-0"), "greaterThan", false, 19);   // ninf > -0
+        cmptst(ninf, pinf, "greaterThan", false, 19.10);     // ninf > pinf
+        cmptst(pinf, ninf, "greaterThan", true, 19.11);     // pinf > ninf
+        cmptst(pinf, pinf, "greaterThan", false, 19.12);     // pinf > pinf
+        cmptst(ninf, ninf, "greaterThan", false, 19.13);     // ninf > ninf
+
+        // check some normal numbers
+        cmptst(field.newDfp("1"), field.newDfp("1"), "greaterThan", false, 20);   // 1 > 1
+        cmptst(field.newDfp("1"), field.newDfp("-1"), "greaterThan", true, 21);   // 1 > -1
+        cmptst(field.newDfp("-1"), field.newDfp("-1"), "greaterThan", false, 22);   // -1 > -1
+        cmptst(field.newDfp("1"), field.newDfp("1.0000000000000001"), "greaterThan", false, 23);   // 1 > 1.0000000000000001
+
+        // The tests below checks to ensure that comparisons don't set FLAG_INEXACT
+        // 100000 > 1.0000000000000001
+        cmptst(field.newDfp("1e20"), field.newDfp("1.0000000000000001"), "greaterThan", true, 24);
+        if (field.getIEEEFlags() != 0)
+            Assert.fail("assersion failed.  compare flags = "+field.getIEEEFlags());
+
+        cmptst(field.newDfp("0.000001"), field.newDfp("1e-6"), "greaterThan", false, 25);
+
+        // check some nans -- nans shouldnt be greaterThan to anything
+        cmptst(snan, snan, "greaterThan", false, 27);
+        cmptst(qnan, qnan, "greaterThan", false, 28);
+        cmptst(snan, qnan, "greaterThan", false, 29);
+        cmptst(qnan, snan, "greaterThan", false, 30);
+        cmptst(qnan, field.newDfp("0"), "greaterThan", false, 31);
+        cmptst(snan, field.newDfp("0"), "greaterThan", false, 32);
+        cmptst(field.newDfp("0"), snan, "greaterThan", false, 33);
+        cmptst(field.newDfp("0"), qnan, "greaterThan", false, 34);
+        cmptst(qnan, pinf, "greaterThan", false, 35);
+        cmptst(snan, pinf, "greaterThan", false, 36);
+        cmptst(pinf, snan, "greaterThan", false, 37);
+        cmptst(pinf, qnan, "greaterThan", false, 38);
+        cmptst(qnan, ninf, "greaterThan", false, 39);
+        cmptst(snan, ninf, "greaterThan", false, 40);
+        cmptst(ninf, snan, "greaterThan", false, 41);
+        cmptst(ninf, qnan, "greaterThan", false, 42);
+        cmptst(qnan, field.newDfp("-1"), "greaterThan", false, 43);
+        cmptst(snan, field.newDfp("-1"), "greaterThan", false, 44);
+        cmptst(field.newDfp("-1"), snan, "greaterThan", false, 45);
+        cmptst(field.newDfp("-1"), qnan, "greaterThan", false, 46);
+        cmptst(qnan, field.newDfp("1"), "greaterThan", false, 47);
+        cmptst(snan, field.newDfp("1"), "greaterThan", false, 48);
+        cmptst(field.newDfp("1"), snan, "greaterThan", false, 49);
+        cmptst(field.newDfp("1"), qnan, "greaterThan", false, 50);
+        cmptst(snan.negate(), snan, "greaterThan", false, 51);
+        cmptst(qnan.negate(), qnan, "greaterThan", false, 52);
+
+        //greaterThan compares with nans should raise FLAG_INVALID
+        if (field.getIEEEFlags() != DfpField.FLAG_INVALID)
+            Assert.fail("assersion failed.  compare greaterThan flags = "+field.getIEEEFlags());
+        field.clearIEEEFlags();
+    }
+
+    //
+    // Test multiplication
+    //
+    @Test
+    public void testMultiply()
+    {
+        test(field.newDfp("1").multiply(field.newDfp("1")),      // Basic tests   1*1 = 1
+             field.newDfp("1"), 
+             0, "Multiply #1");
+
+        test(field.newDfp("1").multiply(1),             // Basic tests   1*1 = 1
+             field.newDfp("1"), 
+             0, "Multiply #2");
+
+        test(field.newDfp("-1").multiply(field.newDfp("1")),     // Basic tests   -1*1 = -1
+             field.newDfp("-1"), 
+             0, "Multiply #3");
+
+        test(field.newDfp("-1").multiply(1),            // Basic tests   -1*1 = -1
+             field.newDfp("-1"), 
+             0, "Multiply #4");
+
+        // basic tests with integers
+        test(field.newDfp("2").multiply(field.newDfp("3")),
+             field.newDfp("6"), 
+             0, "Multiply #5");
+
+        test(field.newDfp("2").multiply(3),
+             field.newDfp("6"), 
+             0, "Multiply #6");
+
+        test(field.newDfp("-2").multiply(field.newDfp("3")),
+             field.newDfp("-6"), 
+             0, "Multiply #7");
+
+        test(field.newDfp("-2").multiply(3),
+             field.newDfp("-6"), 
+             0, "Multiply #8");
+
+        test(field.newDfp("2").multiply(field.newDfp("-3")),
+             field.newDfp("-6"), 
+             0, "Multiply #9");
+
+        test(field.newDfp("-2").multiply(field.newDfp("-3")),
+             field.newDfp("6"), 
+             0, "Multiply #10");
+
+        //multiply by zero
+
+        test(field.newDfp("-2").multiply(field.newDfp("0")),
+             field.newDfp("-0"), 
+             0, "Multiply #11");
+
+        test(field.newDfp("-2").multiply(0),
+             field.newDfp("-0"), 
+             0, "Multiply #12");
+
+        test(field.newDfp("2").multiply(field.newDfp("0")),
+             field.newDfp("0"), 
+             0, "Multiply #13");
+
+        test(field.newDfp("2").multiply(0),
+             field.newDfp("0"), 
+             0, "Multiply #14");
+
+        test(field.newDfp("2").multiply(pinf),
+             pinf,
+             0, "Multiply #15");
+
+        test(field.newDfp("2").multiply(ninf),
+             ninf,
+             0, "Multiply #16");
+
+        test(field.newDfp("-2").multiply(pinf),
+             ninf,
+             0, "Multiply #17");
+
+        test(field.newDfp("-2").multiply(ninf),
+             pinf,
+             0, "Multiply #18");
+
+        test(ninf.multiply(field.newDfp("-2")),
+             pinf,
+             0, "Multiply #18.1");
+
+        test(field.newDfp("5e131071").multiply(2),
+             pinf,
+             DfpField.FLAG_OVERFLOW, "Multiply #19");        
+
+        test(field.newDfp("5e131071").multiply(field.newDfp("1.999999999999999")),
+             field.newDfp("9.9999999999999950000e131071"),
+             0, "Multiply #20");        
+
+        test(field.newDfp("-5e131071").multiply(2),
+             ninf,
+             DfpField.FLAG_OVERFLOW, "Multiply #22");        
+
+        test(field.newDfp("-5e131071").multiply(field.newDfp("1.999999999999999")),
+             field.newDfp("-9.9999999999999950000e131071"),
+             0, "Multiply #23");        
+
+        test(field.newDfp("1e-65539").multiply(field.newDfp("1e-65539")),
+             field.newDfp("1e-131078"),
+             DfpField.FLAG_UNDERFLOW, "Multiply #24");
+
+        test(field.newDfp("1").multiply(nan),
+             nan,
+             0, "Multiply #25");
+
+        test(nan.multiply(field.newDfp("1")),
+             nan,
+             0, "Multiply #26");
+
+        test(nan.multiply(pinf),
+             nan,
+             0, "Multiply #27");
+
+        test(pinf.multiply(nan),
+             nan,
+             0, "Multiply #27");
+
+        test(pinf.multiply(field.newDfp("0")),
+             nan,
+             DfpField.FLAG_INVALID, "Multiply #28");
+
+        test(field.newDfp("0").multiply(pinf),
+             nan,
+             DfpField.FLAG_INVALID, "Multiply #29");
+
+        test(pinf.multiply(pinf),
+             pinf,
+             0, "Multiply #30");
+
+        test(ninf.multiply(pinf),
+             ninf,
+             0, "Multiply #31");
+
+        test(pinf.multiply(ninf),
+             ninf,
+             0, "Multiply #32");
+
+        test(ninf.multiply(ninf),
+             pinf,
+             0, "Multiply #33");
+
+        test(pinf.multiply(1),
+             pinf,
+             0, "Multiply #34");
+
+        test(pinf.multiply(0),
+             nan,
+             DfpField.FLAG_INVALID, "Multiply #35");
+
+        test(nan.multiply(1),
+             nan,
+             0, "Multiply #36");
+
+        test(field.newDfp("1").multiply(10000),  // out of range
+             nan,
+             DfpField.FLAG_INVALID, "Multiply #37");
+
+        test(field.newDfp("1").multiply(-1),  // out of range
+             nan,
+             DfpField.FLAG_INVALID, "Multiply #38");
+    }
+
+    @Test
+    public void testDivide()
+    {
+        test(field.newDfp("1").divide(nan),      // divide by NaN = NaN
+             nan, 
+             0, "Divide #1");
+
+        test(nan.divide(field.newDfp("1")),      // NaN / number = NaN
+             nan, 
+             0, "Divide #2");
+
+        test(pinf.divide(field.newDfp("1")),
+             pinf,
+             0, "Divide #3");
+
+        test(pinf.divide(field.newDfp("-1")),
+             ninf,
+             0, "Divide #4");
+
+        test(pinf.divide(pinf),
+             nan,
+             DfpField.FLAG_INVALID, "Divide #5");
+
+        test(ninf.divide(pinf),
+             nan,
+             DfpField.FLAG_INVALID, "Divide #6");
+
+        test(pinf.divide(ninf),
+             nan,
+             DfpField.FLAG_INVALID, "Divide #7");
+
+        test(ninf.divide(ninf),
+             nan,
+             DfpField.FLAG_INVALID, "Divide #8");
+
+        test(field.newDfp("0").divide(field.newDfp("0")),
+             nan,
+             DfpField.FLAG_DIV_ZERO, "Divide #9");
+
+        test(field.newDfp("1").divide(field.newDfp("0")),
+             pinf,
+             DfpField.FLAG_DIV_ZERO, "Divide #10");
+
+        test(field.newDfp("1").divide(field.newDfp("-0")),
+             ninf,
+             DfpField.FLAG_DIV_ZERO, "Divide #11");
+
+        test(field.newDfp("-1").divide(field.newDfp("0")),
+             ninf,
+             DfpField.FLAG_DIV_ZERO, "Divide #12");
+
+        test(field.newDfp("-1").divide(field.newDfp("-0")),
+             pinf,
+             DfpField.FLAG_DIV_ZERO, "Divide #13");
+
+        test(field.newDfp("1").divide(field.newDfp("3")),
+             field.newDfp("0.33333333333333333333"),
+             DfpField.FLAG_INEXACT, "Divide #14");
+
+        test(field.newDfp("1").divide(field.newDfp("6")),
+             field.newDfp("0.16666666666666666667"),
+             DfpField.FLAG_INEXACT, "Divide #15");
+
+        test(field.newDfp("10").divide(field.newDfp("6")),
+             field.newDfp("1.6666666666666667"),
+             DfpField.FLAG_INEXACT, "Divide #16");
+
+        test(field.newDfp("100").divide(field.newDfp("6")),
+             field.newDfp("16.6666666666666667"),
+             DfpField.FLAG_INEXACT, "Divide #17");
+
+        test(field.newDfp("1000").divide(field.newDfp("6")),
+             field.newDfp("166.6666666666666667"),
+             DfpField.FLAG_INEXACT, "Divide #18");
+
+        test(field.newDfp("10000").divide(field.newDfp("6")),
+             field.newDfp("1666.6666666666666667"),
+             DfpField.FLAG_INEXACT, "Divide #19");
+
+        test(field.newDfp("1").divide(field.newDfp("1")),
+             field.newDfp("1"),
+             0, "Divide #20");
+
+        test(field.newDfp("1").divide(field.newDfp("-1")),
+             field.newDfp("-1"),
+             0, "Divide #21");
+
+        test(field.newDfp("-1").divide(field.newDfp("1")),
+             field.newDfp("-1"),
+             0, "Divide #22");
+
+        test(field.newDfp("-1").divide(field.newDfp("-1")),
+             field.newDfp("1"),
+             0, "Divide #23");
+
+        test(field.newDfp("1e-65539").divide(field.newDfp("1e65539")),
+             field.newDfp("1e-131078"),
+             DfpField.FLAG_UNDERFLOW, "Divide #24");
+
+        test(field.newDfp("1e65539").divide(field.newDfp("1e-65539")),
+             pinf,
+             DfpField.FLAG_OVERFLOW, "Divide #24");
+
+        test(field.newDfp("2").divide(field.newDfp("1.5")),     // test trial-divisor too high
+             field.newDfp("1.3333333333333333"),
+             DfpField.FLAG_INEXACT, "Divide #25");
+
+        test(field.newDfp("2").divide(pinf),
+             field.newDfp("0"),
+             0, "Divide #26");
+
+        test(field.newDfp("2").divide(ninf),
+             field.newDfp("-0"),
+             0, "Divide #27");
+
+        test(field.newDfp("0").divide(field.newDfp("1")),
+             field.newDfp("0"),
+             0, "Divide #28");
+    }
+
+    @Test
+    public void testDivideInt()
+    {
+        test(nan.divide(1),      // NaN / number = NaN
+             nan, 
+             0, "DivideInt #1");
+
+        test(pinf.divide(1),
+             pinf,
+             0, "DivideInt #2");
+
+        test(field.newDfp("0").divide(0),
+             nan,
+             DfpField.FLAG_DIV_ZERO, "DivideInt #3");
+
+        test(field.newDfp("1").divide(0),
+             pinf,
+             DfpField.FLAG_DIV_ZERO, "DivideInt #4");
+
+        test(field.newDfp("-1").divide(0),
+             ninf,
+             DfpField.FLAG_DIV_ZERO, "DivideInt #5");
+
+        test(field.newDfp("1").divide(3),
+             field.newDfp("0.33333333333333333333"),
+             DfpField.FLAG_INEXACT, "DivideInt #6");
+
+        test(field.newDfp("1").divide(6),
+             field.newDfp("0.16666666666666666667"),
+             DfpField.FLAG_INEXACT, "DivideInt #7");
+
+        test(field.newDfp("10").divide(6),
+             field.newDfp("1.6666666666666667"),
+             DfpField.FLAG_INEXACT, "DivideInt #8");
+
+        test(field.newDfp("100").divide(6),
+             field.newDfp("16.6666666666666667"),
+             DfpField.FLAG_INEXACT, "DivideInt #9");
+
+        test(field.newDfp("1000").divide(6),
+             field.newDfp("166.6666666666666667"),
+             DfpField.FLAG_INEXACT, "DivideInt #10");
+
+        test(field.newDfp("10000").divide(6),
+             field.newDfp("1666.6666666666666667"),
+             DfpField.FLAG_INEXACT, "DivideInt #20");
+
+        test(field.newDfp("1").divide(1),
+             field.newDfp("1"),
+             0, "DivideInt #21");
+
+        test(field.newDfp("1e-131077").divide(10),
+             field.newDfp("1e-131078"),
+             DfpField.FLAG_UNDERFLOW, "DivideInt #22");
+
+        test(field.newDfp("0").divide(1),
+             field.newDfp("0"),
+             0, "DivideInt #23");
+
+        test(field.newDfp("1").divide(10000),
+             nan,
+             DfpField.FLAG_INVALID, "DivideInt #24");
+
+        test(field.newDfp("1").divide(-1),
+             nan,
+             DfpField.FLAG_INVALID, "DivideInt #25");
+    }
+
+    @Test
+    public void testNextAfter()
+    {
+        test(field.newDfp("1").nextAfter(pinf),
+             field.newDfp("1.0000000000000001"),
+             0, "NextAfter #1");
+
+        test(field.newDfp("1.0000000000000001").nextAfter(ninf),
+             field.newDfp("1"),
+             0, "NextAfter #1.5");
+
+        test(field.newDfp("1").nextAfter(ninf),
+             field.newDfp("0.99999999999999999999"),
+             0, "NextAfter #2");
+
+        test(field.newDfp("0.99999999999999999999").nextAfter(field.newDfp("2")),
+             field.newDfp("1"),
+             0, "NextAfter #3");
+
+        test(field.newDfp("-1").nextAfter(ninf),
+             field.newDfp("-1.0000000000000001"),
+             0, "NextAfter #4");
+
+        test(field.newDfp("-1").nextAfter(pinf),
+             field.newDfp("-0.99999999999999999999"),
+             0, "NextAfter #5");
+
+        test(field.newDfp("-0.99999999999999999999").nextAfter(field.newDfp("-2")),
+             field.newDfp("-1"),
+             0, "NextAfter #6");
+
+        test(field.newDfp("2").nextAfter(field.newDfp("2")),
+             field.newDfp("2"),
+             0, "NextAfter #7");
+
+        test(field.newDfp("0").nextAfter(field.newDfp("0")),
+             field.newDfp("0"),
+             0, "NextAfter #8");
+
+        test(field.newDfp("-2").nextAfter(field.newDfp("-2")),
+             field.newDfp("-2"),
+             0, "NextAfter #9");
+
+        test(field.newDfp("0").nextAfter(field.newDfp("1")),
+             field.newDfp("1e-131092"),
+             DfpField.FLAG_UNDERFLOW, "NextAfter #10");
+
+        test(field.newDfp("0").nextAfter(field.newDfp("-1")),
+             field.newDfp("-1e-131092"),
+             DfpField.FLAG_UNDERFLOW, "NextAfter #11");
+
+        test(field.newDfp("-1e-131092").nextAfter(pinf),
+             field.newDfp("-0"),
+             DfpField.FLAG_UNDERFLOW|DfpField.FLAG_INEXACT, "Next After #12");
+
+        test(field.newDfp("1e-131092").nextAfter(ninf), 
+             field.newDfp("0"),
+             DfpField.FLAG_UNDERFLOW|DfpField.FLAG_INEXACT, "Next After #13");
+
+        test(field.newDfp("9.9999999999999999999e131078").nextAfter(pinf),
+             pinf,
+             DfpField.FLAG_OVERFLOW|DfpField.FLAG_INEXACT, "Next After #14");
+    }
+
+    @Test
+    public void testToString()
+    {
+        Assert.assertEquals("toString #1", "Infinity", pinf.toString());
+        Assert.assertEquals("toString #2", "-Infinity", ninf.toString());
+        Assert.assertEquals("toString #3", "NaN", nan.toString());
+        Assert.assertEquals("toString #4", "NaN", field.newDfp((byte) 1, Dfp.QNAN).toString());
+        Assert.assertEquals("toString #5", "NaN", field.newDfp((byte) 1, Dfp.SNAN).toString());
+        Assert.assertEquals("toString #6", "1.2300000000000000e100", field.newDfp("1.23e100").toString());
+        Assert.assertEquals("toString #7", "-1.2300000000000000e100", field.newDfp("-1.23e100").toString());
+        Assert.assertEquals("toString #8", "12345678.1234", field.newDfp("12345678.1234").toString());
+        Assert.assertEquals("toString #9", "0.00001234", field.newDfp("0.00001234").toString());
+    }
+
+    @Test
+    public void testRound()
+    {
+        field.setRoundingMode(DfpField.RoundingMode.ROUND_DOWN);
+
+        // Round down
+        test(field.newDfp("12345678901234567890").add(field.newDfp("0.9")),
+             field.newDfp("12345678901234567890"),
+             DfpField.FLAG_INEXACT, "Round #1");
+
+        test(field.newDfp("12345678901234567890").add(field.newDfp("0.99999999")),
+             field.newDfp("12345678901234567890"),
+             DfpField.FLAG_INEXACT, "Round #2");
+
+        test(field.newDfp("-12345678901234567890").add(field.newDfp("-0.99999999")),
+             field.newDfp("-12345678901234567890"),
+             DfpField.FLAG_INEXACT, "Round #3");
+
+        field.setRoundingMode(DfpField.RoundingMode.ROUND_UP);
+
+        // Round up
+        test(field.newDfp("12345678901234567890").add(field.newDfp("0.1")),
+             field.newDfp("12345678901234567891"),
+             DfpField.FLAG_INEXACT, "Round #4");
+
+        test(field.newDfp("12345678901234567890").add(field.newDfp("0.0001")),
+             field.newDfp("12345678901234567891"),
+             DfpField.FLAG_INEXACT, "Round #5");
+
+        test(field.newDfp("-12345678901234567890").add(field.newDfp("-0.1")),
+             field.newDfp("-12345678901234567891"),
+             DfpField.FLAG_INEXACT, "Round #6");
+
+        test(field.newDfp("-12345678901234567890").add(field.newDfp("-0.0001")),
+             field.newDfp("-12345678901234567891"),
+             DfpField.FLAG_INEXACT, "Round #7");
+
+        field.setRoundingMode(DfpField.RoundingMode.ROUND_HALF_UP);
+
+        // Round half up
+        test(field.newDfp("12345678901234567890").add(field.newDfp("0.4999")),
+             field.newDfp("12345678901234567890"),
+             DfpField.FLAG_INEXACT, "Round #8");
+
+        test(field.newDfp("12345678901234567890").add(field.newDfp("0.5000")),
+             field.newDfp("12345678901234567891"),
+             DfpField.FLAG_INEXACT, "Round #9");
+
+        test(field.newDfp("-12345678901234567890").add(field.newDfp("-0.4999")),
+             field.newDfp("-12345678901234567890"),
+             DfpField.FLAG_INEXACT, "Round #10");
+
+        test(field.newDfp("-12345678901234567890").add(field.newDfp("-0.5000")),
+             field.newDfp("-12345678901234567891"),
+             DfpField.FLAG_INEXACT, "Round #11");
+
+        field.setRoundingMode(DfpField.RoundingMode.ROUND_HALF_DOWN);
+
+        // Round half down
+        test(field.newDfp("12345678901234567890").add(field.newDfp("0.5001")),
+             field.newDfp("12345678901234567891"),
+             DfpField.FLAG_INEXACT, "Round #12");
+
+        test(field.newDfp("12345678901234567890").add(field.newDfp("0.5000")),
+             field.newDfp("12345678901234567890"),
+             DfpField.FLAG_INEXACT, "Round #13");
+
+        test(field.newDfp("-12345678901234567890").add(field.newDfp("-0.5001")),
+             field.newDfp("-12345678901234567891"),
+             DfpField.FLAG_INEXACT, "Round #14");
+
+        test(field.newDfp("-12345678901234567890").add(field.newDfp("-0.5000")),
+             field.newDfp("-12345678901234567890"),
+             DfpField.FLAG_INEXACT, "Round #15");
+
+        field.setRoundingMode(DfpField.RoundingMode.ROUND_HALF_ODD);
+
+        // Round half odd
+        test(field.newDfp("12345678901234567890").add(field.newDfp("0.5000")),
+             field.newDfp("12345678901234567891"),
+             DfpField.FLAG_INEXACT, "Round #16");
+
+        test(field.newDfp("12345678901234567891").add(field.newDfp("0.5000")),
+             field.newDfp("12345678901234567891"),
+             DfpField.FLAG_INEXACT, "Round #17");
+
+        test(field.newDfp("-12345678901234567890").add(field.newDfp("-0.5000")),
+             field.newDfp("-12345678901234567891"),
+             DfpField.FLAG_INEXACT, "Round #18");
+
+        test(field.newDfp("-12345678901234567891").add(field.newDfp("-0.5000")),
+             field.newDfp("-12345678901234567891"),
+             DfpField.FLAG_INEXACT, "Round #19");
+
+        field.setRoundingMode(DfpField.RoundingMode.ROUND_CEIL);
+
+        // Round ceil
+        test(field.newDfp("12345678901234567890").add(field.newDfp("0.0001")),
+             field.newDfp("12345678901234567891"),
+             DfpField.FLAG_INEXACT, "Round #20");
+
+        test(field.newDfp("-12345678901234567890").add(field.newDfp("-0.9999")),
+             field.newDfp("-12345678901234567890"),
+             DfpField.FLAG_INEXACT, "Round #21");
+
+        field.setRoundingMode(DfpField.RoundingMode.ROUND_FLOOR);
+
+        // Round floor
+        test(field.newDfp("12345678901234567890").add(field.newDfp("0.9999")),
+             field.newDfp("12345678901234567890"),
+             DfpField.FLAG_INEXACT, "Round #22");
+
+        test(field.newDfp("-12345678901234567890").add(field.newDfp("-0.0001")),
+             field.newDfp("-12345678901234567891"),
+             DfpField.FLAG_INEXACT, "Round #23");
+
+        field.setRoundingMode(DfpField.RoundingMode.ROUND_HALF_EVEN);  // reset
+    }
+
+    @Test
+    public void testCeil()
+    {
+        test(field.newDfp("1234.0000000000000001").ceil(),
+             field.newDfp("1235"),
+             DfpField.FLAG_INEXACT, "Ceil #1");
+    }
+
+    @Test
+    public void testFloor()
+    {
+        test(field.newDfp("1234.9999999999999999").floor(),
+             field.newDfp("1234"),
+             DfpField.FLAG_INEXACT, "Floor #1");
+    }
+
+    @Test
+    public void testRint()
+    {
+        test(field.newDfp("1234.50000000001").rint(),
+             field.newDfp("1235"),
+             DfpField.FLAG_INEXACT, "Rint #1");
+
+        test(field.newDfp("1234.5000").rint(),
+             field.newDfp("1234"),
+             DfpField.FLAG_INEXACT, "Rint #2");
+
+        test(field.newDfp("1235.5000").rint(),
+             field.newDfp("1236"),
+             DfpField.FLAG_INEXACT, "Rint #3");
+    }
+
+    @Test
+    public void testCopySign()
+    {
+        test(Dfp.copysign(field.newDfp("1234."), field.newDfp("-1")),
+             field.newDfp("-1234"),
+             0, "CopySign #1");
+
+        test(Dfp.copysign(field.newDfp("-1234."), field.newDfp("-1")),
+             field.newDfp("-1234"),
+             0, "CopySign #2");
+
+        test(Dfp.copysign(field.newDfp("-1234."), field.newDfp("1")),
+             field.newDfp("1234"),
+             0, "CopySign #3");
+
+        test(Dfp.copysign(field.newDfp("1234."), field.newDfp("1")),
+             field.newDfp("1234"),
+             0, "CopySign #4");
+    }
+
+    @Test
+    public void testIntValue()
+    {
+        Assert.assertEquals("intValue #1", 1234, field.newDfp("1234").intValue());
+        Assert.assertEquals("intValue #2", -1234, field.newDfp("-1234").intValue());
+        Assert.assertEquals("intValue #3", 1234, field.newDfp("1234.5").intValue());
+        Assert.assertEquals("intValue #4", 1235, field.newDfp("1234.500001").intValue());
+        Assert.assertEquals("intValue #5", 2147483647, field.newDfp("1e1000").intValue());
+        Assert.assertEquals("intValue #6", -2147483648, field.newDfp("-1e1000").intValue());
+    }
+
+    @Test
+    public void testLog10K()
+    {
+        Assert.assertEquals("log10K #1", 1, field.newDfp("123456").log10K());
+        Assert.assertEquals("log10K #2", 2, field.newDfp("123456789").log10K());
+        Assert.assertEquals("log10K #3", 0, field.newDfp("2").log10K());
+        Assert.assertEquals("log10K #3", 0, field.newDfp("1").log10K());
+        Assert.assertEquals("log10K #4", -1, field.newDfp("0.1").log10K());
+    }
+
+    @Test
+    public void testPower10K()
+    {
+        Dfp d = field.newDfp();
+
+        test(d.power10K(0), field.newDfp("1"), 0, "Power10 #1");
+        test(d.power10K(1), field.newDfp("10000"), 0, "Power10 #2");
+        test(d.power10K(2), field.newDfp("100000000"), 0, "Power10 #3");
+
+        test(d.power10K(-1), field.newDfp("0.0001"), 0, "Power10 #4");
+        test(d.power10K(-2), field.newDfp("0.00000001"), 0, "Power10 #5");
+        test(d.power10K(-3), field.newDfp("0.000000000001"), 0, "Power10 #6");
+    }
+
+    @Test
+    public void testLog10()
+    {
+
+        Assert.assertEquals("log10 #1", 1, field.newDfp("12").log10());
+        Assert.assertEquals("log10 #2", 2, field.newDfp("123").log10());
+        Assert.assertEquals("log10 #3", 3, field.newDfp("1234").log10());
+        Assert.assertEquals("log10 #4", 4, field.newDfp("12345").log10());
+        Assert.assertEquals("log10 #5", 5, field.newDfp("123456").log10());
+        Assert.assertEquals("log10 #6", 6, field.newDfp("1234567").log10());
+        Assert.assertEquals("log10 #6", 7, field.newDfp("12345678").log10());
+        Assert.assertEquals("log10 #7", 8, field.newDfp("123456789").log10());
+        Assert.assertEquals("log10 #8", 9, field.newDfp("1234567890").log10());
+        Assert.assertEquals("log10 #9", 10, field.newDfp("12345678901").log10());
+        Assert.assertEquals("log10 #10", 11, field.newDfp("123456789012").log10());
+        Assert.assertEquals("log10 #11", 12, field.newDfp("1234567890123").log10());
+
+        Assert.assertEquals("log10 #12", 0, field.newDfp("2").log10());
+        Assert.assertEquals("log10 #13", 0, field.newDfp("1").log10());
+        Assert.assertEquals("log10 #14", -1, field.newDfp("0.12").log10());
+        Assert.assertEquals("log10 #15", -2, field.newDfp("0.012").log10());
+    }
+
+    @Test
+    public void testPower10()
+    {
+        Dfp d = field.newDfp();
+
+        test(d.power10(0), field.newDfp("1"), 0, "Power10 #1");
+        test(d.power10(1), field.newDfp("10"), 0, "Power10 #2");
+        test(d.power10(2), field.newDfp("100"), 0, "Power10 #3");
+        test(d.power10(3), field.newDfp("1000"), 0, "Power10 #4");
+        test(d.power10(4), field.newDfp("10000"), 0, "Power10 #5");
+        test(d.power10(5), field.newDfp("100000"), 0, "Power10 #6");
+        test(d.power10(6), field.newDfp("1000000"), 0, "Power10 #7");
+        test(d.power10(7), field.newDfp("10000000"), 0, "Power10 #8");
+        test(d.power10(8), field.newDfp("100000000"), 0, "Power10 #9");
+        test(d.power10(9), field.newDfp("1000000000"), 0, "Power10 #10");
+
+        test(d.power10(-1), field.newDfp(".1"), 0, "Power10 #11");
+        test(d.power10(-2), field.newDfp(".01"), 0, "Power10 #12");
+        test(d.power10(-3), field.newDfp(".001"), 0, "Power10 #13");
+        test(d.power10(-4), field.newDfp(".0001"), 0, "Power10 #14");
+        test(d.power10(-5), field.newDfp(".00001"), 0, "Power10 #15");
+        test(d.power10(-6), field.newDfp(".000001"), 0, "Power10 #16");
+        test(d.power10(-7), field.newDfp(".0000001"), 0, "Power10 #17");
+        test(d.power10(-8), field.newDfp(".00000001"), 0, "Power10 #18");
+        test(d.power10(-9), field.newDfp(".000000001"), 0, "Power10 #19");
+        test(d.power10(-10), field.newDfp(".0000000001"), 0, "Power10 #20");
+    }
+
+    @Test
+    public void testRemainder()
+    {
+        test(field.newDfp("10").remainder(field.newDfp("3")),
+             field.newDfp("1"),
+             DfpField.FLAG_INEXACT, "Remainder #1");
+
+        test(field.newDfp("9").remainder(field.newDfp("3")),
+             field.newDfp("0"),
+             0, "Remainder #2");
+
+        test(field.newDfp("-9").remainder(field.newDfp("3")),
+             field.newDfp("-0"),
+             0, "Remainder #3");
+    }
+
+    @Test
+    public void testSqrt()
+    {
+        test(field.newDfp("0").sqrt(),
+             field.newDfp("0"),
+             0, "Sqrt #1");
+
+        test(field.newDfp("-0").sqrt(),
+             field.newDfp("-0"),
+             0, "Sqrt #2");
+
+        test(field.newDfp("1").sqrt(),
+             field.newDfp("1"),
+             0, "Sqrt #3");
+
+        test(field.newDfp("2").sqrt(),
+             field.newDfp("1.4142135623730950"),
+             DfpField.FLAG_INEXACT, "Sqrt #4");
+
+        test(field.newDfp("3").sqrt(),
+             field.newDfp("1.7320508075688773"),
+             DfpField.FLAG_INEXACT, "Sqrt #5");
+
+        test(field.newDfp("5").sqrt(),
+             field.newDfp("2.2360679774997897"),
+             DfpField.FLAG_INEXACT, "Sqrt #6");
+
+        test(field.newDfp("500").sqrt(),
+             field.newDfp("22.3606797749978970"),
+             DfpField.FLAG_INEXACT, "Sqrt #6.2");
+
+        test(field.newDfp("50000").sqrt(),
+             field.newDfp("223.6067977499789696"),
+             DfpField.FLAG_INEXACT, "Sqrt #6.3");
+
+        test(field.newDfp("-1").sqrt(),
+             nan,
+             DfpField.FLAG_INVALID, "Sqrt #7");
+
+        test(pinf.sqrt(),
+             pinf,
+             0, "Sqrt #8");
+
+        test(field.newDfp((byte) 1, Dfp.QNAN).sqrt(),
+             nan,
+             0, "Sqrt #9");
+
+        test(field.newDfp((byte) 1, Dfp.SNAN).sqrt(),
+             nan,
+             DfpField.FLAG_INVALID, "Sqrt #9");
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/distribution/BetaDistributionTest.java b/src/test/java/org/apache/commons/math/distribution/BetaDistributionTest.java
new file mode 100644
index 0000000..f96aa99
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/distribution/BetaDistributionTest.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 junit.framework.TestCase;
+import org.apache.commons.math.MathException;
+
+public class BetaDistributionTest extends TestCase {
+    public void testCumulative() throws MathException {
+        double[] x = new double[]{-0.1, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1};
+        // all test data computed using R 2.5
+        checkCumulative(0.1, 0.1,
+                x, new double[]{
+                0.0000000000, 0.0000000000, 0.4063850939, 0.4397091902, 0.4628041861,
+                0.4821200456, 0.5000000000, 0.5178799544, 0.5371958139, 0.5602908098,
+                0.5936149061, 1.0000000000, 1.0000000000});
+        checkCumulative(0.1, 0.5,
+                x, new double[]{
+                0.0000000000, 0.0000000000, 0.7048336221, 0.7593042194, 0.7951765304,
+                0.8234948385, 0.8480017124, 0.8706034370, 0.8926585878, 0.9156406404,
+                0.9423662883, 1.0000000000, 1.0000000000});
+        checkCumulative(0.1, 1.0,
+                x, new double[]{
+                0.0000000000, 0.0000000000, 0.7943282347, 0.8513399225, 0.8865681506,
+                0.9124435366, 0.9330329915, 0.9502002165, 0.9649610951, 0.9779327685,
+                0.9895192582, 1.0000000000, 1.0000000000});
+        checkCumulative(0.1, 2.0,
+                x, new double[]{
+                0.0000000000, 0.0000000000, 0.8658177758, 0.9194471163, 0.9486279211,
+                0.9671901487, 0.9796846411, 0.9882082252, 0.9939099280, 0.9974914239,
+                0.9994144508, 1.0000000000, 1.0000000000});
+        checkCumulative(0.1, 4.0,
+                x, new double[]{
+                0.0000000000, 0.0000000000, 0.9234991121, 0.9661958941, 0.9842285085,
+                0.9928444112, 0.9970040660, 0.9989112804, 0.9996895625, 0.9999440793,
+                0.9999967829, 1.0000000000, 1.0000000000});
+        checkCumulative(0.5, 0.1,
+                x, new double[]{
+                0.00000000000, 0.00000000000, 0.05763371168, 0.08435935962,
+                0.10734141216, 0.12939656302, 0.15199828760, 0.17650516146,
+                0.20482346963, 0.24069578055, 0.29516637795, 1.00000000000, 1.00000000000});
+
+        checkCumulative(0.5, 0.5,
+                x, new double[]{
+                0.0000000000, 0.0000000000, 0.2048327647, 0.2951672353, 0.3690101196,
+                0.4359057832, 0.5000000000, 0.5640942168, 0.6309898804, 0.7048327647,
+                0.7951672353, 1.0000000000, 1.0000000000});
+        checkCumulative(0.5, 1.0,
+                x, new double[]{
+                0.0000000000, 0.0000000000, 0.3162277660, 0.4472135955, 0.5477225575,
+                0.6324555320, 0.7071067812, 0.7745966692, 0.8366600265, 0.8944271910,
+                0.9486832981, 1.0000000000, 1.0000000000});
+        checkCumulative(0.5, 2.0,
+                x, new double[]{
+                0.0000000000, 0.0000000000, 0.4585302607, 0.6260990337, 0.7394254526,
+                0.8221921916, 0.8838834765, 0.9295160031, 0.9621590305, 0.9838699101,
+                0.9961174630, 1.0000000000, 1.0000000000});
+        checkCumulative(0.5, 4.0,
+                x, new double[]{
+                0.0000000000, 0.0000000000, 0.6266250826, 0.8049844719, 0.8987784842,
+                0.9502644369, 0.9777960959, 0.9914837366, 0.9974556254, 0.9995223859,
+                0.9999714889, 1.0000000000, 1.0000000000});
+        checkCumulative(1.0, 0.1,
+                x, new double[]{
+                0.00000000000, 0.00000000000, 0.01048074179, 0.02206723146,
+                0.03503890488, 0.04979978349, 0.06696700846, 0.08755646344,
+                0.11343184943, 0.14866007748, 0.20567176528, 1.00000000000, 1.00000000000});
+        checkCumulative(1.0, 0.5,
+                x, new double[]{
+                0.00000000000, 0.00000000000, 0.05131670195, 0.10557280900,
+                0.16333997347, 0.22540333076, 0.29289321881, 0.36754446797,
+                0.45227744249, 0.55278640450, 0.68377223398, 1.00000000000, 1.00000000000});
+        checkCumulative(1, 1,
+                x, new double[]{
+                0.0, 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.0});
+        checkCumulative(1, 2,
+                x, new double[]{
+                0.00, 0.00, 0.19, 0.36, 0.51, 0.64, 0.75, 0.84, 0.91, 0.96, 0.99, 1.00, 1.00});
+        checkCumulative(1, 4,
+                x, new double[]{
+                0.0000, 0.0000, 0.3439, 0.5904, 0.7599, 0.8704, 0.9375, 0.9744, 0.9919,
+                0.9984, 0.9999, 1.0000, 1.0000});
+        checkCumulative(2.0, 0.1,
+                x, new double[]{
+                0.0000000000000, 0.0000000000000, 0.0005855492117, 0.0025085760862,
+                0.0060900720266, 0.0117917748341, 0.0203153588864, 0.0328098512512,
+                0.0513720788952, 0.0805528836776, 0.1341822241505, 1.0000000000000, 1.0000000000000});
+        checkCumulative(2, 1,
+                x, new double[]{
+                0.00, 0.00, 0.01, 0.04, 0.09, 0.16, 0.25, 0.36, 0.49, 0.64, 0.81, 1.00, 1.00});
+        checkCumulative(2.0, 0.5,
+                x, new double[]{
+                0.000000000000, 0.000000000000, 0.003882537047, 0.016130089900,
+                0.037840969486, 0.070483996910, 0.116116523517, 0.177807808356,
+                0.260574547368, 0.373900966300, 0.541469739276, 1.000000000000, 1.000000000000});
+        checkCumulative(2, 2,
+                x, new double[]{
+                0.000, 0.000, 0.028, 0.104, 0.216, 0.352, 0.500, 0.648, 0.784, 0.896, 0.972, 1.000, 1.000});
+        checkCumulative(2, 4,
+                x, new double[]{
+                0.00000, 0.00000, 0.08146, 0.26272, 0.47178, 0.66304, 0.81250, 0.91296,
+                0.96922, 0.99328, 0.99954, 1.00000, 1.00000});
+        checkCumulative(4.0, 0.1,
+                x, new double[]{
+                0.000000000e+00, 0.000000000e+00, 3.217128269e-06, 5.592070271e-05,
+                3.104375474e-04, 1.088719595e-03, 2.995933981e-03, 7.155588777e-03,
+                1.577149153e-02, 3.380410585e-02, 7.650088789e-02, 1.000000000e+00, 1.000000000e+00});
+        checkCumulative(4.0, 0.5,
+                x, new double[]{
+                0.000000000e+00, 0.000000000e+00, 2.851114863e-05, 4.776140576e-04,
+                2.544374616e-03, 8.516263371e-03, 2.220390414e-02, 4.973556312e-02,
+                1.012215158e-01, 1.950155281e-01, 3.733749174e-01, 1.000000000e+00, 1.000000000e+00});
+        checkCumulative(4, 1,
+                x, new double[]{
+                0.0000, 0.0000, 0.0001, 0.0016, 0.0081, 0.0256, 0.0625, 0.1296, 0.2401,
+                0.4096, 0.6561, 1.0000, 1.0000});
+        checkCumulative(4, 2,
+                x, new double[]{
+                0.00000, 0.00000, 0.00046, 0.00672, 0.03078, 0.08704, 0.18750, 0.33696,
+                0.52822, 0.73728, 0.91854, 1.00000, 1.00000});
+        checkCumulative(4, 4,
+                x, new double[]{
+                0.000000, 0.000000, 0.002728, 0.033344, 0.126036, 0.289792, 0.500000,
+                0.710208, 0.873964, 0.966656, 0.997272, 1.000000, 1.000000});
+
+    }
+
+    private void checkCumulative(double alpha, double beta, double[] x, double[] cumes) throws MathException {
+        BetaDistribution d = new BetaDistributionImpl(alpha, beta);
+        for (int i = 0; i < x.length; i++) {
+            assertEquals(cumes[i], d.cumulativeProbability(x[i]), 1e-8);
+        }
+
+        for (int i = 1; i < x.length - 1; i++) {
+            assertEquals(x[i], d.inverseCumulativeProbability(cumes[i]), 1e-5);
+        }
+    }
+
+    public void testDensity() throws MathException {
+        double[] x = new double[]{1e-6, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9};
+        checkDensity(0.1, 0.1,
+                x, new double[]{
+                12741.2357380649, 0.4429889586665234, 2.639378715e-01, 2.066393611e-01,
+                1.832401831e-01, 1.766302780e-01, 1.832404579e-01, 2.066400696e-01,
+                2.639396531e-01, 4.429925026e-01});
+        checkDensity(0.1, 0.5,
+                x, new double[]{
+                2.218377102e+04, 7.394524202e-01, 4.203020268e-01, 3.119435533e-01,
+                2.600787829e-01, 2.330648626e-01, 2.211408259e-01, 2.222728708e-01,
+                2.414013907e-01, 3.070567405e-01});
+        checkDensity(0.1, 1.0,
+                x, new double[]{
+                2.511886432e+04, 7.943210858e-01, 4.256680458e-01, 2.955218303e-01,
+                2.281103709e-01, 1.866062624e-01, 1.583664652e-01, 1.378514078e-01,
+                1.222414585e-01, 1.099464743e-01});
+        checkDensity(0.1, 2.0,
+                x, new double[]{
+                2.763072312e+04, 7.863770012e-01, 3.745874120e-01, 2.275514842e-01,
+                1.505525939e-01, 1.026332391e-01, 6.968107049e-02, 4.549081293e-02,
+                2.689298641e-02, 1.209399123e-02});
+        checkDensity(0.1, 4.0,
+                x, new double[]{
+                2.997927462e+04, 6.911058917e-01, 2.601128486e-01, 1.209774010e-01,
+                5.880564714e-02, 2.783915474e-02, 1.209657335e-02, 4.442148268e-03,
+                1.167143939e-03, 1.312171805e-04});
+        checkDensity(0.5, 0.1,
+                x, new double[]{
+                88.3152184726, 0.3070542841, 0.2414007269, 0.2222727015,
+                0.2211409364, 0.2330652355, 0.2600795198, 0.3119449793,
+                0.4203052841, 0.7394649088});
+        checkDensity(0.5, 0.5,
+                x, new double[]{
+                318.3100453389, 1.0610282383, 0.7957732234, 0.6946084565,
+                0.6497470636, 0.6366197724, 0.6497476051, 0.6946097796,
+                0.7957762075, 1.0610376697});
+        checkDensity(0.5, 1.0,
+                x, new double[]{
+                500.0000000000, 1.5811309244, 1.1180311937, 0.9128694077,
+                0.7905684268, 0.7071060741, 0.6454966865, 0.5976138778,
+                0.5590166450, 0.5270459839});
+        checkDensity(0.5, 2.0,
+                x, new double[]{
+                749.99925000000, 2.134537420613655, 1.34163575536, 0.95851150881,
+                0.71151039830, 0.53032849490, 0.38729704363, 0.26892534859,
+                0.16770415497, 0.07905610701});
+        checkDensity(0.5, 4.0,
+                x, new double[]{
+                1.093746719e+03, 2.52142232809988, 1.252190241e+00, 6.849343920e-01,
+                3.735417140e-01, 1.933481570e-01, 9.036885833e-02, 3.529621669e-02,
+                9.782644546e-03, 1.152878503e-03});
+        checkDensity(1.0, 0.1,
+                x, new double[]{
+                0.1000000900, 0.1099466942, 0.1222417336, 0.1378517623, 0.1583669403,
+                0.1866069342, 0.2281113974, 0.2955236034, 0.4256718768,
+                0.7943353837});
+        checkDensity(1.0, 0.5,
+                x, new double[]{
+                0.5000002500, 0.5270465695, 0.5590173438, 0.5976147315, 0.6454977623,
+                0.7071074883, 0.7905704033, 0.9128724506,
+                1.1180367838, 1.5811467358});
+        checkDensity(1, 1,
+                x, new double[]{
+                1, 1, 1,
+                1, 1, 1, 1,
+                1, 1, 1});
+        checkDensity(1, 2,
+                x, new double[]{
+                1.999998, 1.799998, 1.599998, 1.399998, 1.199998, 0.999998, 0.799998,
+                0.599998, 0.399998,
+                0.199998});
+        checkDensity(1, 4,
+                x, new double[]{
+                3.999988000012, 2.915990280011, 2.047992320010, 1.371994120008,
+                0.863995680007, 0.499997000006, 0.255998080005, 0.107998920004,
+                0.031999520002, 0.003999880001});
+        checkDensity(2.0, 0.1,
+                x, new double[]{
+                1.100000990e-07, 1.209425730e-02, 2.689331586e-02, 4.549123318e-02,
+                6.968162794e-02, 1.026340191e-01, 1.505537732e-01, 2.275534997e-01,
+                3.745917198e-01, 7.863929037e-01});
+        checkDensity(2.0, 0.5,
+                x, new double[]{
+                7.500003750e-07, 7.905777599e-02, 1.677060417e-01, 2.689275256e-01,
+                3.872996256e-01, 5.303316769e-01, 7.115145488e-01, 9.585174425e-01,
+                1.341645818e+00, 2.134537420613655});
+        checkDensity(2, 1,
+                x, new double[]{
+                0.000002, 0.200002, 0.400002, 0.600002, 0.800002, 1.000002, 1.200002,
+                1.400002, 1.600002,
+                1.800002});
+        checkDensity(2, 2,
+                x, new double[]{
+                5.9999940e-06, 5.4000480e-01, 9.6000360e-01, 1.2600024e+00,
+                1.4400012e+00, 1.5000000e+00, 1.4399988e+00, 1.2599976e+00,
+                9.5999640e-01, 5.3999520e-01});
+        checkDensity(2, 4,
+                x, new double[]{
+                0.00001999994, 1.45800971996, 2.04800255997, 2.05799803998,
+                1.72799567999, 1.24999500000, 0.76799552000, 0.37799676001,
+                0.12799824001, 0.01799948000});
+        checkDensity(4.0, 0.1,
+                x, new double[]{
+                1.193501074e-19, 1.312253162e-04, 1.167181580e-03, 4.442248535e-03,
+                1.209679109e-02, 2.783958903e-02, 5.880649983e-02, 1.209791638e-01,
+                2.601171405e-01, 6.911229392e-01});
+        checkDensity(4.0, 0.5,
+                x, new double[]{
+                1.093750547e-18, 1.152948959e-03, 9.782950259e-03, 3.529697305e-02,
+                9.037036449e-02, 1.933508639e-01, 3.735463833e-01, 6.849425461e-01,
+                1.252205894e+00, 2.52142232809988});
+        checkDensity(4, 1,
+                x, new double[]{
+                4.000000000e-18, 4.000120001e-03, 3.200048000e-02, 1.080010800e-01,
+                2.560019200e-01, 5.000030000e-01, 8.640043200e-01, 1.372005880e+00,
+                2.048007680e+00, 2.916009720e+00});
+        checkDensity(4, 2,
+                x, new double[]{
+                1.999998000e-17, 1.800052000e-02, 1.280017600e-01, 3.780032400e-01,
+                7.680044800e-01, 1.250005000e+00, 1.728004320e+00, 2.058001960e+00,
+                2.047997440e+00, 1.457990280e+00});
+        checkDensity(4, 4,
+                x, new double[]{
+                1.399995800e-16, 1.020627216e-01, 5.734464512e-01, 1.296547409e+00,
+                1.935364838e+00, 2.187500000e+00, 1.935355162e+00, 1.296532591e+00,
+                5.734335488e-01, 1.020572784e-01});
+
+    }
+
+    private void checkDensity(double alpha, double beta, double[] x, double[] expected) throws MathException {
+        BetaDistribution d = new BetaDistributionImpl(alpha, beta);
+        for (int i = 0; i < x.length; i++) {
+            assertEquals(String.format("density at x=%.1f for alpha=%.1f, beta=%.1f", x[i], alpha, beta), expected[i], d.density(x[i]), 1e-5);
+        }
+    }
+
+    public void testMomonts() {
+        final double tol = 1e-9;
+        BetaDistributionImpl dist;
+        
+        dist = new BetaDistributionImpl(1, 1);
+        assertEquals(dist.getNumericalMean(), 0.5, tol);
+        assertEquals(dist.getNumericalVariance(), 1.0 / 12.0, tol); 
+        
+        dist.setAlpha(2);
+        dist.setBeta(5);
+        assertEquals(dist.getNumericalMean(), 2.0 / 7.0, tol);
+        assertEquals(dist.getNumericalVariance(), 10.0 / (49.0 * 8.0), tol); 
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/distribution/BinomialDistributionTest.java b/src/test/java/org/apache/commons/math/distribution/BinomialDistributionTest.java
new file mode 100644
index 0000000..b4ae2b3
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/distribution/BinomialDistributionTest.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.distribution;
+
+/**
+ * Test cases for BinomialDistribution. Extends IntegerDistributionAbstractTest.
+ * See class javadoc for IntegerDistributionAbstractTest for details.
+ *
+ * @version $Revision: 1054524 $ $Date: 2009-09-05 12:36:48 -0500 (Sat, 05 Sep
+ *          2009) $
+ */
+public class BinomialDistributionTest extends IntegerDistributionAbstractTest {
+
+    /**
+     * Constructor for BinomialDistributionTest.
+     *
+     * @param name
+     */
+    public BinomialDistributionTest(String name) {
+        super(name);
+    }
+
+    // -------------- Implementations for abstract methods
+    // -----------------------
+
+    /** Creates the default discrete distribution instance to use in tests. */
+    @Override
+    public IntegerDistribution makeDistribution() {
+        return new BinomialDistributionImpl(10, 0.70);
+    }
+
+    /** Creates the default probability density test input values */
+    @Override
+    public int[] makeDensityTestPoints() {
+        return new int[] { -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 };
+    }
+
+    /** Creates the default probability density test expected values */
+    @Override
+    public double[] makeDensityTestValues() {
+        return new double[] { 0d, 0.0000059049d, 0.000137781d, 0.0014467d,
+                0.00900169d, 0.0367569d, 0.102919d, 0.200121d, 0.266828d,
+                0.233474d, 0.121061d, 0.0282475d, 0d };
+    }
+
+    /** Creates the default cumulative probability density test input values */
+    @Override
+    public int[] makeCumulativeTestPoints() {
+        return makeDensityTestPoints();
+    }
+
+    /** Creates the default cumulative probability density test expected values */
+    @Override
+    public double[] makeCumulativeTestValues() {
+        return new double[] { 0d, 0.0000d, 0.0001d, 0.0016d, 0.0106d, 0.0473d,
+                0.1503d, 0.3504d, 0.6172d, 0.8507d, 0.9718d, 1d, 1d };
+    }
+
+    /** Creates the default inverse cumulative probability test input values */
+    @Override
+    public double[] makeInverseCumulativeTestPoints() {
+        return new double[] { 0, 0.001d, 0.010d, 0.025d, 0.050d, 0.100d,
+                0.999d, 0.990d, 0.975d, 0.950d, 0.900d, 1 };
+    }
+
+    /**
+     * Creates the default inverse cumulative probability density test expected
+     * values
+     */
+    @Override
+    public int[] makeInverseCumulativeTestValues() {
+        return new int[] { -1, 1, 2, 3, 4, 4, 9, 9, 9, 8, 8, Integer.MAX_VALUE };
+    }
+
+    // ----------------- Additional test cases ---------------------------------
+
+    /** Test degenerate case p = 0 */
+    public void testDegenerate0() throws Exception {
+        setDistribution(new BinomialDistributionImpl(5, 0.0d));
+        setCumulativeTestPoints(new int[] { -1, 0, 1, 5, 10 });
+        setCumulativeTestValues(new double[] { 0d, 1d, 1d, 1d, 1d });
+        setDensityTestPoints(new int[] { -1, 0, 1, 10, 11 });
+        setDensityTestValues(new double[] { 0d, 1d, 0d, 0d, 0d });
+        setInverseCumulativeTestPoints(new double[] { 0.1d, 0.5d });
+        setInverseCumulativeTestValues(new int[] { -1, -1 });
+        verifyDensities();
+        verifyCumulativeProbabilities();
+        verifyInverseCumulativeProbabilities();
+    }
+
+    /** Test degenerate case p = 1 */
+    public void testDegenerate1() throws Exception {
+        setDistribution(new BinomialDistributionImpl(5, 1.0d));
+        setCumulativeTestPoints(new int[] { -1, 0, 1, 2, 5, 10 });
+        setCumulativeTestValues(new double[] { 0d, 0d, 0d, 0d, 1d, 1d });
+        setDensityTestPoints(new int[] { -1, 0, 1, 2, 5, 10 });
+        setDensityTestValues(new double[] { 0d, 0d, 0d, 0d, 1d, 0d });
+        setInverseCumulativeTestPoints(new double[] { 0.1d, 0.5d });
+        setInverseCumulativeTestValues(new int[] { 4, 4 });
+        verifyDensities();
+        verifyCumulativeProbabilities();
+        verifyInverseCumulativeProbabilities();
+    }
+
+    public void testMomonts() {
+        final double tol = 1e-9;
+        BinomialDistributionImpl dist;
+        
+        dist = new BinomialDistributionImpl(10, 0.5);
+        assertEquals(dist.getNumericalMean(), 10d * 0.5d, tol);
+        assertEquals(dist.getNumericalVariance(), 10d * 0.5d * 0.5d, tol); 
+        
+        dist.setNumberOfTrials(30);
+        dist.setProbabilityOfSuccess(0.3);
+        assertEquals(dist.getNumericalMean(), 30d * 0.3d, tol);
+        assertEquals(dist.getNumericalVariance(), 30d * 0.3d * (1d - 0.3d), tol);
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/distribution/CauchyDistributionTest.java b/src/test/java/org/apache/commons/math/distribution/CauchyDistributionTest.java
new file mode 100644
index 0000000..12ce219
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/distribution/CauchyDistributionTest.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.distribution;
+
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Test cases for CauchyDistribution.
+ * Extends ContinuousDistributionAbstractTest.  See class javadoc for
+ * ContinuousDistributionAbstractTest for details.
+ *
+ * @version $Revision: 1054524 $ $Date: 2011-01-03 05:59:18 +0100 (lun. 03 janv. 2011) $
+ */
+public class CauchyDistributionTest extends ContinuousDistributionAbstractTest  {
+
+    /**
+     * Constructor for CauchyDistributionTest.
+     * @param arg0
+     */
+    public CauchyDistributionTest(String arg0) {
+        super(arg0);
+    }
+
+    // --------------------- Override tolerance  --------------
+    protected double defaultTolerance = NormalDistributionImpl.DEFAULT_INVERSE_ABSOLUTE_ACCURACY;
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        setTolerance(defaultTolerance);
+    }
+
+    //-------------- Implementations for abstract methods -----------------------
+
+    /** Creates the default continuous distribution instance to use in tests. */
+    @Override
+    public CauchyDistribution makeDistribution() {
+        return new CauchyDistributionImpl(1.2, 2.1);
+    }
+
+    /** Creates the default cumulative probability distribution test input values */
+    @Override
+    public double[] makeCumulativeTestPoints() {
+        // quantiles computed using R 2.9.2
+        return new double[] {-667.24856187, -65.6230835029, -25.4830299460, -12.0588781808,
+                -5.26313542807, 669.64856187, 68.0230835029, 27.8830299460, 14.4588781808, 7.66313542807};
+    }
+
+    /** Creates the default cumulative probability density test expected values */
+    @Override
+    public double[] makeCumulativeTestValues() {
+        return new double[] {0.001, 0.01, 0.025, 0.05, 0.1, 0.999,
+                0.990, 0.975, 0.950, 0.900};
+    }
+
+    /** Creates the default probability density test expected values */
+    @Override
+    public double[] makeDensityTestValues() {
+        return new double[] {1.49599158008e-06, 0.000149550440335, 0.000933076881878, 0.00370933207799, 0.0144742330437,
+                1.49599158008e-06, 0.000149550440335, 0.000933076881878, 0.00370933207799, 0.0144742330437};
+    }
+
+    //---------------------------- Additional test cases -------------------------
+
+    public void testInverseCumulativeProbabilityExtremes() throws Exception {
+        setInverseCumulativeTestPoints(new double[] {0.0, 1.0});
+        setInverseCumulativeTestValues(
+                new double[] {Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY});
+        verifyInverseCumulativeProbabilities();
+    }
+
+    public void testMedian() {
+        CauchyDistribution distribution = (CauchyDistribution) getDistribution();
+        double expected = FastMath.random();
+        distribution.setMedian(expected);
+        assertEquals(expected, distribution.getMedian(), 0.0);
+    }
+
+    public void testScale() {
+        CauchyDistribution distribution = (CauchyDistribution) getDistribution();
+        double expected = FastMath.random();
+        distribution.setScale(expected);
+        assertEquals(expected, distribution.getScale(), 0.0);
+    }
+
+    public void testSetScale() {
+        CauchyDistribution distribution = (CauchyDistribution) getDistribution();
+        try {
+            distribution.setScale(0.0);
+            fail("Can not have 0.0 scale.");
+        } catch (IllegalArgumentException ex) {
+            // success
+        }
+
+        try {
+            distribution.setScale(-1.0);
+            fail("Can not have negative scale.");
+        } catch (IllegalArgumentException ex) {
+            // success
+        }
+    }
+
+    public void testMomonts() {
+        CauchyDistributionImpl dist;
+        
+        dist = new CauchyDistributionImpl(10.2, 0.15);
+        assertTrue(Double.isNaN(dist.getNumericalMean()));
+        assertTrue(Double.isNaN(dist.getNumericalVariance())); 
+        
+        dist.setMedian(23.12);
+        dist.setScale(2.12);
+        assertTrue(Double.isNaN(dist.getNumericalMean()));
+        assertTrue(Double.isNaN(dist.getNumericalVariance()));
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/distribution/ChiSquareDistributionTest.java b/src/test/java/org/apache/commons/math/distribution/ChiSquareDistributionTest.java
new file mode 100644
index 0000000..40636ba
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/distribution/ChiSquareDistributionTest.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.distribution;
+
+/**
+ * Test cases for ChiSquareDistribution.
+ * Extends ContinuousDistributionAbstractTest.  See class javadoc for
+ * ContinuousDistributionAbstractTest for details.
+ *
+ * @version $Revision: 1054524 $ $Date: 2011-01-03 05:59:18 +0100 (lun. 03 janv. 2011) $
+ */
+public class ChiSquareDistributionTest extends ContinuousDistributionAbstractTest {
+
+    /**
+     * Constructor for ChiSquareDistributionTest.
+     * @param name
+     */
+    public ChiSquareDistributionTest(String name) {
+        super(name);
+    }
+
+    //-------------- Implementations for abstract methods -----------------------
+
+    /** Creates the default continuous distribution instance to use in tests. */
+    @Override
+    public ChiSquaredDistribution makeDistribution() {
+        return new ChiSquaredDistributionImpl(5.0);
+    }
+
+    /** Creates the default cumulative probability distribution test input values */
+    @Override
+    public double[] makeCumulativeTestPoints() {
+        // quantiles computed using R version 2.9.2
+        return new double[] {0.210212602629, 0.554298076728, 0.831211613487, 1.14547622606, 1.61030798696,
+                20.5150056524, 15.0862724694, 12.8325019940, 11.0704976935, 9.23635689978};
+    }
+
+    /** Creates the default cumulative probability density test expected values */
+    @Override
+    public double[] makeCumulativeTestValues() {
+        return new double[] {0.001, 0.01, 0.025, 0.05, 0.1, 0.999, 0.990, 0.975, 0.950, 0.900};
+    }
+
+    /** Creates the default inverse cumulative probability test input values */
+    @Override
+    public double[] makeInverseCumulativeTestPoints() {
+        return new double[] {0, 0.001d, 0.01d, 0.025d, 0.05d, 0.1d, 0.999d,
+                0.990d, 0.975d, 0.950d, 0.900d, 1};
+    }
+
+    /** Creates the default inverse cumulative probability density test expected values */
+    @Override
+    public double[] makeInverseCumulativeTestValues() {
+        return new double[] {0, 0.210212602629, 0.554298076728, 0.831211613487, 1.14547622606, 1.61030798696,
+                20.5150056524, 15.0862724694, 12.8325019940, 11.0704976935, 9.23635689978,
+                Double.POSITIVE_INFINITY};
+    }
+
+    /** Creates the default probability density test expected values */
+    @Override
+    public double[] makeDensityTestValues() {
+        return new double[] {0.0115379817652, 0.0415948507811, 0.0665060119842, 0.0919455953114, 0.121472591024,
+                0.000433630076361, 0.00412780610309, 0.00999340341045, 0.0193246438937, 0.0368460089216};
+    }
+
+ // --------------------- Override tolerance  --------------
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        setTolerance(1e-9);
+    }
+
+ //---------------------------- Additional test cases -------------------------
+
+    public void testSmallDf() throws Exception {
+        setDistribution(new ChiSquaredDistributionImpl(0.1d));
+        setTolerance(1E-4);
+        // quantiles computed using R version 1.8.1 (linux version)
+        setCumulativeTestPoints(new double[] {1.168926E-60, 1.168926E-40, 1.063132E-32,
+                1.144775E-26, 1.168926E-20, 5.472917, 2.175255, 1.13438,
+                0.5318646, 0.1526342});
+        setInverseCumulativeTestValues(getCumulativeTestPoints());
+        setInverseCumulativeTestPoints(getCumulativeTestValues());
+        verifyCumulativeProbabilities();
+        verifyInverseCumulativeProbabilities();
+    }
+
+    public void testDfAccessors() {
+        ChiSquaredDistribution distribution = (ChiSquaredDistribution) getDistribution();
+        assertEquals(5d, distribution.getDegreesOfFreedom(), Double.MIN_VALUE);
+        distribution.setDegreesOfFreedom(4d);
+        assertEquals(4d, distribution.getDegreesOfFreedom(), Double.MIN_VALUE);
+        try {
+            distribution.setDegreesOfFreedom(0d);
+            fail("Expecting IllegalArgumentException for df = 0");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+
+    public void testDensity() {
+        double[] x = new double[]{-0.1, 1e-6, 0.5, 1, 2, 5};
+        //R 2.5: print(dchisq(x, df=1), digits=10)
+        checkDensity(1, x, new double[]{0.00000000000, 398.94208093034, 0.43939128947, 0.24197072452, 0.10377687436, 0.01464498256});
+        //R 2.5: print(dchisq(x, df=0.1), digits=10)
+        checkDensity(0.1, x, new double[]{0.000000000e+00, 2.486453997e+04, 7.464238732e-02, 3.009077718e-02, 9.447299159e-03, 8.827199396e-04});
+        //R 2.5: print(dchisq(x, df=2), digits=10)
+        checkDensity(2, x, new double[]{0.00000000000, 0.49999975000, 0.38940039154, 0.30326532986, 0.18393972059, 0.04104249931});
+        //R 2.5: print(dchisq(x, df=10), digits=10)
+        checkDensity(10, x, new double[]{0.000000000e+00, 1.302082682e-27, 6.337896998e-05, 7.897534632e-04, 7.664155024e-03, 6.680094289e-02});
+    }
+
+    private void checkDensity(double df, double[] x, double[] expected) {
+        ChiSquaredDistribution d = new ChiSquaredDistributionImpl(df);
+        for (int i = 0; i < x.length; i++) {
+            assertEquals(expected[i], d.density(x[i]), 1e-5);
+        }
+    }
+
+    public void testMomonts() {
+        final double tol = 1e-9;
+        ChiSquaredDistributionImpl dist;
+        
+        dist = new ChiSquaredDistributionImpl(1500);
+        assertEquals(dist.getNumericalMean(), 1500, tol);
+        assertEquals(dist.getNumericalVariance(), 3000, tol); 
+        
+        dist.setDegreesOfFreedom(1.12);
+        assertEquals(dist.getNumericalMean(), 1.12, tol);
+        assertEquals(dist.getNumericalVariance(), 2.24, tol);
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/distribution/ContinuousDistributionAbstractTest.java b/src/test/java/org/apache/commons/math/distribution/ContinuousDistributionAbstractTest.java
new file mode 100644
index 0000000..cc06acb
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/distribution/ContinuousDistributionAbstractTest.java
@@ -0,0 +1,371 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.distribution;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Abstract base class for {@link ContinuousDistribution} tests.
+ * <p>
+ * To create a concrete test class for a continuous distribution
+ * implementation, first implement makeDistribution() to return a distribution
+ * instance to use in tests. Then implement each of the test data generation
+ * methods below.  In each case, the test points and test values arrays
+ * returned represent parallel arrays of inputs and expected values for the
+ * distribution returned by makeDistribution().  Default implementations
+ * are provided for the makeInverseXxx methods that just invert the mapping
+ * defined by the arrays returned by the makeCumulativeXxx methods.
+ * <p>
+ * makeCumulativeTestPoints() -- arguments used to test cumulative probabilities
+ * makeCumulativeTestValues() -- expected cumulative probabilites
+ * makeDensityTestValues() -- expected density values at cumulativeTestPoints
+ * makeInverseCumulativeTestPoints() -- arguments used to test inverse cdf
+ * makeInverseCumulativeTestValues() -- expected inverse cdf values
+ * <p>
+ * To implement additional test cases with different distribution instances and
+ * test data, use the setXxx methods for the instance data in test cases and
+ * call the verifyXxx methods to verify results.
+ * <p>
+ * Error tolerance can be overriden by implementing getTolerance().
+ * <p>
+ * Test data should be validated against reference tables or other packages
+ * where possible, and the source of the reference data and/or validation
+ * should be documented in the test cases.  A framework for validating
+ * distribution data against R is included in the /src/test/R source tree.
+ * <p>
+ * See {@link NormalDistributionTest} and {@link ChiSquareDistributionTest}
+ * for examples.
+ *
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ */
+public abstract class ContinuousDistributionAbstractTest extends TestCase {
+
+//-------------------- Private test instance data -------------------------
+    /**  Distribution instance used to perform tests */
+    private ContinuousDistribution distribution;
+
+    /** Tolerance used in comparing expected and returned values */
+    private double tolerance = 1E-4;
+
+    /** Arguments used to test cumulative probability density calculations */
+    private double[] cumulativeTestPoints;
+
+    /** Values used to test cumulative probability density calculations */
+    private double[] cumulativeTestValues;
+
+    /** Arguments used to test inverse cumulative probability density calculations */
+    private double[] inverseCumulativeTestPoints;
+
+    /** Values used to test inverse cumulative probability density calculations */
+    private double[] inverseCumulativeTestValues;
+
+    /** Values used to test density calculations */
+    private double[] densityTestValues;
+
+    //-------------------------------------------------------------------------
+
+    /**
+     * Constructor for ContinuousDistributionAbstractTest.
+     * @param name
+     */
+    public ContinuousDistributionAbstractTest(String name) {
+        super(name);
+    }
+
+    //-------------------- Abstract methods -----------------------------------
+
+    /** Creates the default continuous distribution instance to use in tests. */
+    public abstract ContinuousDistribution makeDistribution();
+
+    /** Creates the default cumulative probability test input values */
+    public abstract double[] makeCumulativeTestPoints();
+
+    /** Creates the default cumulative probability test expected values */
+    public abstract double[] makeCumulativeTestValues();
+
+    /** Creates the default density test expected values */
+    public abstract double[] makeDensityTestValues();
+
+    //---- Default implementations of inverse test data generation methods ----
+
+    /** Creates the default inverse cumulative probability test input values */
+    public double[] makeInverseCumulativeTestPoints() {
+        return makeCumulativeTestValues();
+    }
+
+    /** Creates the default inverse cumulative probability density test expected values */
+    public double[] makeInverseCumulativeTestValues() {
+        return makeCumulativeTestPoints();
+    }
+
+    //-------------------- Setup / tear down ----------------------------------
+
+    /**
+     * Setup sets all test instance data to default values
+     */
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        distribution = makeDistribution();
+        cumulativeTestPoints = makeCumulativeTestPoints();
+        cumulativeTestValues = makeCumulativeTestValues();
+        inverseCumulativeTestPoints = makeInverseCumulativeTestPoints();
+        inverseCumulativeTestValues = makeInverseCumulativeTestValues();
+        densityTestValues = makeDensityTestValues();
+    }
+
+    /**
+     * Cleans up test instance data
+     */
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        distribution = null;
+        cumulativeTestPoints = null;
+        cumulativeTestValues = null;
+        inverseCumulativeTestPoints = null;
+        inverseCumulativeTestValues = null;
+        densityTestValues = null;
+    }
+
+    //-------------------- Verification methods -------------------------------
+
+    /**
+     * Verifies that cumulative probability density calculations match expected values
+     * using current test instance data
+     */
+    protected void verifyCumulativeProbabilities() throws Exception {
+        for (int i = 0; i < cumulativeTestPoints.length; i++) {
+            TestUtils.assertEquals("Incorrect cumulative probability value returned for "
+                + cumulativeTestPoints[i], cumulativeTestValues[i],
+                distribution.cumulativeProbability(cumulativeTestPoints[i]),
+                getTolerance());
+        }
+    }
+
+    /**
+     * Verifies that inverse cumulative probability density calculations match expected values
+     * using current test instance data
+     */
+    protected void verifyInverseCumulativeProbabilities() throws Exception {
+        for (int i = 0; i < inverseCumulativeTestPoints.length; i++) {
+            TestUtils.assertEquals("Incorrect inverse cumulative probability value returned for "
+                + inverseCumulativeTestPoints[i], inverseCumulativeTestValues[i],
+                 distribution.inverseCumulativeProbability(inverseCumulativeTestPoints[i]),
+                 getTolerance());
+        }
+    }
+
+    /**
+     * Verifies that density calculations match expected values
+     */
+    protected void verifyDensities() throws Exception {
+        for (int i = 0; i < cumulativeTestPoints.length; i++) {
+            TestUtils.assertEquals("Incorrect probability density value returned for "
+                + cumulativeTestPoints[i], densityTestValues[i],
+                 //TODO: remove cast when density(double) is added to ContinuousDistribution
+                 ((AbstractContinuousDistribution) distribution).density(cumulativeTestPoints[i]),
+                 getTolerance());
+        }
+    }
+
+    //------------------------ Default test cases -----------------------------
+
+    /**
+     * Verifies that cumulative probability density calculations match expected values
+     * using default test instance data
+     */
+    public void testCumulativeProbabilities() throws Exception {
+        verifyCumulativeProbabilities();
+    }
+
+    /**
+     * Verifies that inverse cumulative probability density calculations match expected values
+     * using default test instance data
+     */
+    public void testInverseCumulativeProbabilities() throws Exception {
+        verifyInverseCumulativeProbabilities();
+    }
+
+    /**
+     * Verifies that density calculations return expected values
+     * for default test instance data
+     */
+    public void testDensities() throws Exception {
+        verifyDensities();
+    }
+
+    /**
+     * Verifies that probability computations are consistent
+     */
+    public void testConsistency() throws Exception {
+        for (int i=1; i < cumulativeTestPoints.length; i++) {
+
+            // check that cdf(x, x) = 0
+            TestUtils.assertEquals(0d,
+               distribution.cumulativeProbability
+                 (cumulativeTestPoints[i], cumulativeTestPoints[i]), tolerance);
+
+            // check that P(a < X < b) = P(X < b) - P(X < a)
+            double upper = FastMath.max(cumulativeTestPoints[i], cumulativeTestPoints[i -1]);
+            double lower = FastMath.min(cumulativeTestPoints[i], cumulativeTestPoints[i -1]);
+            double diff = distribution.cumulativeProbability(upper) -
+                distribution.cumulativeProbability(lower);
+            double direct = distribution.cumulativeProbability(lower, upper);
+            TestUtils.assertEquals("Inconsistent cumulative probabilities for ("
+                    + lower + "," + upper + ")", diff, direct, tolerance);
+        }
+    }
+
+    /**
+     * Verifies that illegal arguments are correctly handled
+     */
+    public void testIllegalArguments() throws Exception {
+        try {
+            distribution.cumulativeProbability(1, 0);
+            fail("Expecting IllegalArgumentException for bad cumulativeProbability interval");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            distribution.inverseCumulativeProbability(-1);
+            fail("Expecting IllegalArgumentException for p = -1");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            distribution.inverseCumulativeProbability(2);
+            fail("Expecting IllegalArgumentException for p = 2");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+    
+    /**
+     * Test sampling
+     */
+    public void testSampling() throws Exception {
+        AbstractContinuousDistribution dist = (AbstractContinuousDistribution) makeDistribution();
+        final int sampleSize = 1000;
+        double[] sample = dist.sample(sampleSize);
+        double[] quartiles = TestUtils.getDistributionQuartiles(dist);
+        double[] expected = {250, 250, 250, 250};
+        long[] counts = new long[4];
+        dist.reseedRandomGenerator(1000);  // Use fixed seed
+        for (int i = 0; i < sampleSize; i++) {
+            TestUtils.updateCounts(sample[i], counts, quartiles);
+        }
+        TestUtils.assertChiSquareAccept(expected, counts, 0.001);
+    }
+
+    //------------------ Getters / Setters for test instance data -----------
+    /**
+     * @return Returns the cumulativeTestPoints.
+     */
+    protected double[] getCumulativeTestPoints() {
+        return cumulativeTestPoints;
+    }
+
+    /**
+     * @param cumulativeTestPoints The cumulativeTestPoints to set.
+     */
+    protected void setCumulativeTestPoints(double[] cumulativeTestPoints) {
+        this.cumulativeTestPoints = cumulativeTestPoints;
+    }
+
+    /**
+     * @return Returns the cumulativeTestValues.
+     */
+    protected double[] getCumulativeTestValues() {
+        return cumulativeTestValues;
+    }
+
+    /**
+     * @param cumulativeTestValues The cumulativeTestValues to set.
+     */
+    protected void setCumulativeTestValues(double[] cumulativeTestValues) {
+        this.cumulativeTestValues = cumulativeTestValues;
+    }
+
+    protected double[] getDensityTestValues() {
+        return densityTestValues;
+    }
+
+    protected void setDensityTestValues(double[] densityTestValues) {
+        this.densityTestValues = densityTestValues;
+    }
+
+    /**
+     * @return Returns the distribution.
+     */
+    protected ContinuousDistribution getDistribution() {
+        return distribution;
+    }
+
+    /**
+     * @param distribution The distribution to set.
+     */
+    protected void setDistribution(AbstractContinuousDistribution distribution) {
+        this.distribution = distribution;
+    }
+
+    /**
+     * @return Returns the inverseCumulativeTestPoints.
+     */
+    protected double[] getInverseCumulativeTestPoints() {
+        return inverseCumulativeTestPoints;
+    }
+
+    /**
+     * @param inverseCumulativeTestPoints The inverseCumulativeTestPoints to set.
+     */
+    protected void setInverseCumulativeTestPoints(double[] inverseCumulativeTestPoints) {
+        this.inverseCumulativeTestPoints = inverseCumulativeTestPoints;
+    }
+
+    /**
+     * @return Returns the inverseCumulativeTestValues.
+     */
+    protected double[] getInverseCumulativeTestValues() {
+        return inverseCumulativeTestValues;
+    }
+
+    /**
+     * @param inverseCumulativeTestValues The inverseCumulativeTestValues to set.
+     */
+    protected void setInverseCumulativeTestValues(double[] inverseCumulativeTestValues) {
+        this.inverseCumulativeTestValues = inverseCumulativeTestValues;
+    }
+
+    /**
+     * @return Returns the tolerance.
+     */
+    protected double getTolerance() {
+        return tolerance;
+    }
+
+    /**
+     * @param tolerance The tolerance to set.
+     */
+    protected void setTolerance(double tolerance) {
+        this.tolerance = tolerance;
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/distribution/ExponentialDistributionTest.java b/src/test/java/org/apache/commons/math/distribution/ExponentialDistributionTest.java
new file mode 100644
index 0000000..e6dcf6a
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/distribution/ExponentialDistributionTest.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.distribution;
+
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Test cases for ExponentialDistribution.
+ * Extends ContinuousDistributionAbstractTest.  See class javadoc for
+ * ContinuousDistributionAbstractTest for details.
+ *
+ * @version $Revision: 1054524 $ $Date: 2011-01-03 05:59:18 +0100 (lun. 03 janv. 2011) $
+ */
+public class ExponentialDistributionTest extends ContinuousDistributionAbstractTest {
+
+    /**
+     * Constructor for ExponentialDistributionTest.
+     * @param name
+     */
+    public ExponentialDistributionTest(String name) {
+        super(name);
+    }
+
+    // --------------------- Override tolerance  --------------
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        setTolerance(1E-9);
+    }
+
+    //-------------- Implementations for abstract methods -----------------------
+
+    /** Creates the default continuous distribution instance to use in tests. */
+    @Override
+    public ExponentialDistribution makeDistribution() {
+        return new ExponentialDistributionImpl(5.0);
+    }
+
+    /** Creates the default cumulative probability distribution test input values */
+    @Override
+    public double[] makeCumulativeTestPoints() {
+        // quantiles computed using R version 2.9.2
+        return new double[] {0.00500250166792, 0.0502516792675, 0.126589039921, 0.256466471938,
+                0.526802578289, 34.5387763949, 23.0258509299, 18.4443972706, 14.9786613678, 11.5129254650};
+    }
+
+    /** Creates the default cumulative probability density test expected values */
+    @Override
+    public double[] makeCumulativeTestValues() {
+        return new double[] {0.001, 0.01, 0.025, 0.05, 0.1, 0.999,
+                0.990, 0.975, 0.950, 0.900};
+    }
+
+    /** Creates the default probability density test expected values */
+    @Override
+    public double[] makeDensityTestValues() {
+        return new double[] {0.1998, 0.198, 0.195, 0.19, 0.18, 0.000200000000000,
+                0.00200000000002, 0.00499999999997, 0.00999999999994, 0.0199999999999};
+    }
+
+    //------------ Additional tests -------------------------------------------
+
+    public void testCumulativeProbabilityExtremes() throws Exception {
+        setCumulativeTestPoints(new double[] {-2, 0});
+        setCumulativeTestValues(new double[] {0, 0});
+        verifyCumulativeProbabilities();
+    }
+
+    public void testInverseCumulativeProbabilityExtremes() throws Exception {
+         setInverseCumulativeTestPoints(new double[] {0, 1});
+         setInverseCumulativeTestValues(new double[] {0, Double.POSITIVE_INFINITY});
+         verifyInverseCumulativeProbabilities();
+    }
+
+    public void testCumulativeProbability2() throws Exception {
+        double actual = getDistribution().cumulativeProbability(0.25, 0.75);
+        assertEquals(0.0905214, actual, 10e-4);
+    }
+
+    public void testDensity() {
+        ExponentialDistribution d1 = new ExponentialDistributionImpl(1);
+        assertEquals(0.0, d1.density(-1e-9));
+        assertEquals(1.0, d1.density(0.0));
+        assertEquals(0.0, d1.density(1000.0));
+        assertEquals(FastMath.exp(-1), d1.density(1.0));
+        assertEquals(FastMath.exp(-2), d1.density(2.0));
+
+        ExponentialDistribution d2 = new ExponentialDistributionImpl(3);
+        assertEquals(1/3.0, d2.density(0.0));
+        // computed using  print(dexp(1, rate=1/3), digits=10) in R 2.5
+        assertEquals(0.2388437702, d2.density(1.0), 1e-8);
+
+        // computed using  print(dexp(2, rate=1/3), digits=10) in R 2.5
+        assertEquals(0.1711390397, d2.density(2.0), 1e-8);
+    }
+
+    public void testMeanAccessors() {
+        ExponentialDistribution distribution = (ExponentialDistribution) getDistribution();
+        assertEquals(5d, distribution.getMean(), Double.MIN_VALUE);
+        distribution.setMean(2d);
+        assertEquals(2d, distribution.getMean(), Double.MIN_VALUE);
+        try {
+            distribution.setMean(0);
+            fail("Expecting IllegalArgumentException for 0 mean");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+
+
+    public void testMomonts() {
+        final double tol = 1e-9;
+        ExponentialDistributionImpl dist;
+        
+        dist = new ExponentialDistributionImpl(11d);
+        assertEquals(dist.getNumericalMean(), 11d, tol);
+        assertEquals(dist.getNumericalVariance(), 11d * 11d, tol);
+        
+        dist.setMean(10.5d);
+        assertEquals(dist.getNumericalMean(), 10.5d, tol);
+        assertEquals(dist.getNumericalVariance(), 10.5d * 10.5d, tol);
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/distribution/FDistributionTest.java b/src/test/java/org/apache/commons/math/distribution/FDistributionTest.java
new file mode 100644
index 0000000..e463415
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/distribution/FDistributionTest.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.distribution;
+
+
+/**
+ * Test cases for FDistribution.
+ * Extends ContinuousDistributionAbstractTest.  See class javadoc for
+ * ContinuousDistributionAbstractTest for details.
+ *
+ * @version $Revision: 1054524 $ $Date: 2011-01-03 05:59:18 +0100 (lun. 03 janv. 2011) $
+ */
+public class FDistributionTest extends ContinuousDistributionAbstractTest {
+
+    /**
+     * Constructor for FDistributionTest.
+     * @param name
+     */
+    public FDistributionTest(String name) {
+        super(name);
+    }
+
+    //-------------- Implementations for abstract methods -----------------------
+
+    /** Creates the default continuous distribution instance to use in tests. */
+    @Override
+    public FDistribution makeDistribution() {
+        return new FDistributionImpl(5.0, 6.0);
+    }
+
+    /** Creates the default cumulative probability distribution test input values */
+    @Override
+    public double[] makeCumulativeTestPoints() {
+        // quantiles computed using R version 2.9.2
+        return new double[] {0.0346808448626, 0.0937009113303, 0.143313661184, 0.202008445998, 0.293728320107,
+                20.8026639595, 8.74589525602, 5.98756512605, 4.38737418741, 3.10751166664};
+    }
+
+    /** Creates the default cumulative probability density test expected values */
+    @Override
+    public double[] makeCumulativeTestValues() {
+        return new double[] {0.001, 0.01, 0.025, 0.05, 0.1, 0.999, 0.990, 0.975, 0.950, 0.900};
+    }
+
+    /** Creates the default probability density test expected values */
+    @Override
+    public double[] makeDensityTestValues() {
+        return new double[] {0.0689156576706, 0.236735653193, 0.364074131941, 0.481570789649, 0.595880479994,
+                0.000133443915657, 0.00286681303403, 0.00969192007502, 0.0242883861471, 0.0605491314658};
+    }
+
+    // --------------------- Override tolerance  --------------
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        setTolerance(1e-9);
+    }
+
+    //---------------------------- Additional test cases -------------------------
+
+    public void testCumulativeProbabilityExtremes() throws Exception {
+        setCumulativeTestPoints(new double[] {-2, 0});
+        setCumulativeTestValues(new double[] {0, 0});
+        verifyCumulativeProbabilities();
+    }
+
+    public void testInverseCumulativeProbabilityExtremes() throws Exception {
+        setInverseCumulativeTestPoints(new double[] {0, 1});
+        setInverseCumulativeTestValues(new double[] {0, Double.POSITIVE_INFINITY});
+        verifyInverseCumulativeProbabilities();
+    }
+
+    public void testDfAccessors() {
+        FDistribution distribution = (FDistribution) getDistribution();
+        assertEquals(5d, distribution.getNumeratorDegreesOfFreedom(), Double.MIN_VALUE);
+        distribution.setNumeratorDegreesOfFreedom(4d);
+        assertEquals(4d, distribution.getNumeratorDegreesOfFreedom(), Double.MIN_VALUE);
+        assertEquals(6d, distribution.getDenominatorDegreesOfFreedom(), Double.MIN_VALUE);
+        distribution.setDenominatorDegreesOfFreedom(4d);
+        assertEquals(4d, distribution.getDenominatorDegreesOfFreedom(), Double.MIN_VALUE);
+        try {
+            distribution.setNumeratorDegreesOfFreedom(0d);
+            fail("Expecting IllegalArgumentException for df = 0");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            distribution.setDenominatorDegreesOfFreedom(0d);
+            fail("Expecting IllegalArgumentException for df = 0");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+
+    public void testLargeDegreesOfFreedom() throws Exception {
+        org.apache.commons.math.distribution.FDistributionImpl fd =
+            new org.apache.commons.math.distribution.FDistributionImpl(
+                100000., 100000.);
+        double p = fd.cumulativeProbability(.999);
+        double x = fd.inverseCumulativeProbability(p);
+        assertEquals(.999, x, 1.0e-5);
+    }
+
+    public void testSmallDegreesOfFreedom() throws Exception {
+        org.apache.commons.math.distribution.FDistributionImpl fd =
+            new org.apache.commons.math.distribution.FDistributionImpl(
+                1.0, 1.0);
+        double p = fd.cumulativeProbability(0.975);
+        double x = fd.inverseCumulativeProbability(p);
+        assertEquals(0.975, x, 1.0e-5);
+
+        fd.setDenominatorDegreesOfFreedom(2.0);
+        p = fd.cumulativeProbability(0.975);
+        x = fd.inverseCumulativeProbability(p);
+        assertEquals(0.975, x, 1.0e-5);
+    }
+
+    public void testMomonts() {
+        final double tol = 1e-9;
+        FDistributionImpl dist;
+        
+        dist = new FDistributionImpl(1, 2);
+        assertTrue(Double.isNaN(dist.getNumericalMean()));
+        assertTrue(Double.isNaN(dist.getNumericalVariance())); 
+        
+        dist.setNumeratorDegreesOfFreedom(1);
+        dist.setDenominatorDegreesOfFreedom(3);
+        assertEquals(dist.getNumericalMean(), 3d / (3d - 2d), tol);
+        assertTrue(Double.isNaN(dist.getNumericalVariance()));
+        
+        dist.setNumeratorDegreesOfFreedom(1);
+        dist.setDenominatorDegreesOfFreedom(5);
+        assertEquals(dist.getNumericalMean(), 5d / (5d - 2d), tol);
+        assertEquals(dist.getNumericalVariance(), (2d * 5d * 5d * 4d) / 9d, tol);        
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/distribution/GammaDistributionTest.java b/src/test/java/org/apache/commons/math/distribution/GammaDistributionTest.java
new file mode 100644
index 0000000..052f815
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/distribution/GammaDistributionTest.java
@@ -0,0 +1,169 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.distribution;
+
+/**
+ * Test cases for GammaDistribution.
+ * Extends ContinuousDistributionAbstractTest.  See class javadoc for
+ * ContinuousDistributionAbstractTest for details.
+ *
+ * @version $Revision: 1054524 $ $Date: 2011-01-03 05:59:18 +0100 (lun. 03 janv. 2011) $
+ */
+public class GammaDistributionTest extends ContinuousDistributionAbstractTest {
+
+    /**
+     * Constructor for GammaDistributionTest.
+     * @param name
+     */
+    public GammaDistributionTest(String name) {
+        super(name);
+    }
+
+    //-------------- Implementations for abstract methods -----------------------
+
+    /** Creates the default continuous distribution instance to use in tests. */
+    @Override
+    public GammaDistribution makeDistribution() {
+        return new GammaDistributionImpl(4d, 2d);
+    }
+
+    /** Creates the default cumulative probability distribution test input values */
+    @Override
+    public double[] makeCumulativeTestPoints() {
+        // quantiles computed using R version 2.9.2
+        return new double[] {0.857104827257, 1.64649737269, 2.17973074725, 2.7326367935, 3.48953912565,
+                26.1244815584, 20.0902350297, 17.5345461395, 15.5073130559, 13.3615661365};
+    }
+
+    /** Creates the default cumulative probability density test expected values */
+    @Override
+    public double[] makeCumulativeTestValues() {
+        return new double[] {0.001, 0.01, 0.025, 0.05, 0.1, 0.999, 0.990, 0.975, 0.950, 0.900};
+    }
+
+    /** Creates the default probability density test expected values */
+    @Override
+    public double[] makeDensityTestValues() {
+        return new double[] {0.00427280075546, 0.0204117166709, 0.0362756163658, 0.0542113174239, 0.0773195272491,
+                0.000394468852816, 0.00366559696761, 0.00874649473311, 0.0166712508128, 0.0311798227954};
+    }
+
+    // --------------------- Override tolerance  --------------
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        setTolerance(1e-9);
+    }
+
+    //---------------------------- Additional test cases -------------------------
+    public void testParameterAccessors() {
+        GammaDistribution distribution = (GammaDistribution) getDistribution();
+        assertEquals(4d, distribution.getAlpha(), 0);
+        distribution.setAlpha(3d);
+        assertEquals(3d, distribution.getAlpha(), 0);
+        assertEquals(2d, distribution.getBeta(), 0);
+        distribution.setBeta(4d);
+        assertEquals(4d, distribution.getBeta(), 0);
+        try {
+            distribution.setAlpha(0d);
+            fail("Expecting IllegalArgumentException for alpha = 0");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            distribution.setBeta(0d);
+            fail("Expecting IllegalArgumentException for beta = 0");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+
+    public void testProbabilities() throws Exception {
+        testProbability(-1.000, 4.0, 2.0, .0000);
+        testProbability(15.501, 4.0, 2.0, .9499);
+        testProbability(0.504, 4.0, 1.0, .0018);
+        testProbability(10.011, 1.0, 2.0, .9933);
+        testProbability(5.000, 2.0, 2.0, .7127);
+    }
+
+    public void testValues() throws Exception {
+        testValue(15.501, 4.0, 2.0, .9499);
+        testValue(0.504, 4.0, 1.0, .0018);
+        testValue(10.011, 1.0, 2.0, .9933);
+        testValue(5.000, 2.0, 2.0, .7127);
+    }
+
+    private void testProbability(double x, double a, double b, double expected) throws Exception {
+        GammaDistribution distribution = new GammaDistributionImpl( a, b );
+        double actual = distribution.cumulativeProbability(x);
+        assertEquals("probability for " + x, expected, actual, 10e-4);
+    }
+
+    private void testValue(double expected, double a, double b, double p) throws Exception {
+        GammaDistribution distribution = new GammaDistributionImpl( a, b );
+        double actual = distribution.inverseCumulativeProbability(p);
+        assertEquals("critical value for " + p, expected, actual, 10e-4);
+    }
+
+    public void testDensity() {
+        double[] x = new double[]{-0.1, 1e-6, 0.5, 1, 2, 5};
+        // R2.5: print(dgamma(x, shape=1, rate=1), digits=10)
+        checkDensity(1, 1, x, new double[]{0.000000000000, 0.999999000001, 0.606530659713, 0.367879441171, 0.135335283237, 0.006737946999});
+        // R2.5: print(dgamma(x, shape=2, rate=1), digits=10)
+        checkDensity(2, 1, x, new double[]{0.000000000000, 0.000000999999, 0.303265329856, 0.367879441171, 0.270670566473, 0.033689734995});
+        // R2.5: print(dgamma(x, shape=4, rate=1), digits=10)
+        checkDensity(4, 1, x, new double[]{0.000000000e+00, 1.666665000e-19, 1.263605541e-02, 6.131324020e-02, 1.804470443e-01, 1.403738958e-01});
+        // R2.5: print(dgamma(x, shape=4, rate=10), digits=10)
+        checkDensity(4, 10, x, new double[]{0.000000000e+00, 1.666650000e-15, 1.403738958e+00, 7.566654960e-02, 2.748204830e-05, 4.018228850e-17});
+        // R2.5: print(dgamma(x, shape=.1, rate=10), digits=10)
+        checkDensity(0.1, 10, x, new double[]{0.000000000e+00, 3.323953832e+04, 1.663849010e-03, 6.007786726e-06, 1.461647647e-10, 5.996008322e-24});
+        // R2.5: print(dgamma(x, shape=.1, rate=20), digits=10)
+        checkDensity(0.1, 20, x, new double[]{0.000000000e+00, 3.562489883e+04, 1.201557345e-05, 2.923295295e-10, 3.228910843e-19, 1.239484589e-45});
+        // R2.5: print(dgamma(x, shape=.1, rate=4), digits=10)
+        checkDensity(0.1, 4, x, new double[]{0.000000000e+00, 3.032938388e+04, 3.049322494e-02, 2.211502311e-03, 2.170613371e-05, 5.846590589e-11});
+        // R2.5: print(dgamma(x, shape=.1, rate=1), digits=10)
+        checkDensity(0.1, 1, x, new double[]{0.000000000e+00, 2.640334143e+04, 1.189704437e-01, 3.866916944e-02, 7.623306235e-03, 1.663849010e-04});
+    }
+
+    private void checkDensity(double alpha, double rate, double[] x, double[] expected) {
+        GammaDistribution d = new GammaDistributionImpl(alpha, 1 / rate);
+        for (int i = 0; i < x.length; i++) {
+            assertEquals(expected[i], d.density(x[i]), 1e-5);
+        }
+    }
+
+    public void testInverseCumulativeProbabilityExtremes() throws Exception {
+        setInverseCumulativeTestPoints(new double[] {0, 1});
+        setInverseCumulativeTestValues(new double[] {0, Double.POSITIVE_INFINITY});
+        verifyInverseCumulativeProbabilities();
+    }
+
+    public void testMomonts() {
+        final double tol = 1e-9;
+        GammaDistributionImpl dist;
+        
+        dist = new GammaDistributionImpl(1, 2);
+        assertEquals(dist.getNumericalMean(), 2, tol);
+        assertEquals(dist.getNumericalVariance(), 4, tol); 
+        
+        dist.setAlpha(1.1);
+        dist.setBeta(4.2);        
+        assertEquals(dist.getNumericalMean(), 1.1d * 4.2d, tol);
+        assertEquals(dist.getNumericalVariance(), 1.1d * 4.2d * 4.2d, tol);
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/distribution/HypergeometricDistributionTest.java b/src/test/java/org/apache/commons/math/distribution/HypergeometricDistributionTest.java
new file mode 100644
index 0000000..8522620
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/distribution/HypergeometricDistributionTest.java
@@ -0,0 +1,229 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.distribution;
+
+import org.apache.commons.math.TestUtils;
+
+/**
+ * Test cases for HyperGeometriclDistribution.
+ * Extends IntegerDistributionAbstractTest.  See class javadoc for
+ * IntegerDistributionAbstractTest for details.
+ *
+ * @version $Revision: 1054524 $ $Date: 2011-01-03 05:59:18 +0100 (lun. 03 janv. 2011) $
+ */
+public class HypergeometricDistributionTest extends IntegerDistributionAbstractTest {
+
+    /**
+     * Constructor for ChiSquareDistributionTest.
+     * @param name
+     */
+    public HypergeometricDistributionTest(String name) {
+        super(name);
+    }
+
+//-------------- Implementations for abstract methods -----------------------
+
+    /** Creates the default discrete distribution instance to use in tests. */
+    @Override
+    public IntegerDistribution makeDistribution() {
+        return new HypergeometricDistributionImpl(10,5, 5);
+    }
+
+    /** Creates the default probability density test input values */
+    @Override
+    public int[] makeDensityTestPoints() {
+        return new int[] {-1, 0, 1, 2, 3, 4, 5, 10};
+    }
+
+    /** Creates the default probability density test expected values */
+    @Override
+    public double[] makeDensityTestValues() {
+        return new double[] {0d, 0.003968d, 0.099206d, 0.396825d, 0.396825d,
+                0.099206d, 0.003968d, 0d};
+    }
+
+    /** Creates the default cumulative probability density test input values */
+    @Override
+    public int[] makeCumulativeTestPoints() {
+        return makeDensityTestPoints();
+    }
+
+    /** Creates the default cumulative probability density test expected values */
+    @Override
+    public double[] makeCumulativeTestValues() {
+        return new double[] {0d, .003968d, .103175d, .50000d, .896825d, .996032d,
+                1.00000d, 1d};
+    }
+
+    /** Creates the default inverse cumulative probability test input values */
+    @Override
+    public double[] makeInverseCumulativeTestPoints() {
+        return new double[] {0d, 0.001d, 0.010d, 0.025d, 0.050d, 0.100d, 0.999d,
+                0.990d, 0.975d, 0.950d, 0.900d, 1d};
+    }
+
+    /** Creates the default inverse cumulative probability density test expected values */
+    @Override
+    public int[] makeInverseCumulativeTestValues() {
+        return new int[] {-1, -1, 0, 0, 0, 0, 4, 3, 3, 3, 3, 5};
+    }
+
+    //-------------------- Additional test cases ------------------------------
+
+    /** Verify that if there are no failures, mass is concentrated on sampleSize */
+    public void testDegenerateNoFailures() throws Exception {
+        setDistribution(new HypergeometricDistributionImpl(5,5,3));
+        setCumulativeTestPoints(new int[] {-1, 0, 1, 3, 10 });
+        setCumulativeTestValues(new double[] {0d, 0d, 0d, 1d, 1d});
+        setDensityTestPoints(new int[] {-1, 0, 1, 3, 10});
+        setDensityTestValues(new double[] {0d, 0d, 0d, 1d, 0d});
+        setInverseCumulativeTestPoints(new double[] {0.1d, 0.5d});
+        setInverseCumulativeTestValues(new int[] {2, 2});
+        verifyDensities();
+        verifyCumulativeProbabilities();
+        verifyInverseCumulativeProbabilities();
+    }
+
+    /** Verify that if there are no successes, mass is concentrated on 0 */
+    public void testDegenerateNoSuccesses() throws Exception {
+        setDistribution(new HypergeometricDistributionImpl(5,0,3));
+        setCumulativeTestPoints(new int[] {-1, 0, 1, 3, 10 });
+        setCumulativeTestValues(new double[] {0d, 1d, 1d, 1d, 1d});
+        setDensityTestPoints(new int[] {-1, 0, 1, 3, 10});
+        setDensityTestValues(new double[] {0d, 1d, 0d, 0d, 0d});
+        setInverseCumulativeTestPoints(new double[] {0.1d, 0.5d});
+        setInverseCumulativeTestValues(new int[] {-1, -1});
+        verifyDensities();
+        verifyCumulativeProbabilities();
+        verifyInverseCumulativeProbabilities();
+    }
+
+    /** Verify that if sampleSize = populationSize, mass is concentrated on numberOfSuccesses */
+    public void testDegenerateFullSample() throws Exception {
+        setDistribution(new HypergeometricDistributionImpl(5,3,5));
+        setCumulativeTestPoints(new int[] {-1, 0, 1, 3, 10 });
+        setCumulativeTestValues(new double[] {0d, 0d, 0d, 1d, 1d});
+        setDensityTestPoints(new int[] {-1, 0, 1, 3, 10});
+        setDensityTestValues(new double[] {0d, 0d, 0d, 1d, 0d});
+        setInverseCumulativeTestPoints(new double[] {0.1d, 0.5d});
+        setInverseCumulativeTestValues(new int[] {2, 2});
+        verifyDensities();
+        verifyCumulativeProbabilities();
+        verifyInverseCumulativeProbabilities();
+    }
+
+    public void testPopulationSize() {
+        HypergeometricDistribution dist = new HypergeometricDistributionImpl(5,3,5);
+        try {
+            dist.setPopulationSize(-1);
+            fail("negative population size.  IllegalArgumentException expected");
+        } catch(IllegalArgumentException ex) {
+        }
+
+        dist.setPopulationSize(10);
+        assertEquals(10, dist.getPopulationSize());
+    }
+
+    public void testLargeValues() {
+        int populationSize = 3456;
+        int sampleSize = 789;
+        int numberOfSucceses = 101;
+        double[][] data = {
+            {0.0, 2.75646034603961e-12, 2.75646034603961e-12, 1.0},
+            {1.0, 8.55705370142386e-11, 8.83269973602783e-11, 0.999999999997244},
+            {2.0, 1.31288129219665e-9, 1.40120828955693e-9, 0.999999999911673},
+            {3.0, 1.32724172984193e-8, 1.46736255879763e-8, 0.999999998598792},
+            {4.0, 9.94501711734089e-8, 1.14123796761385e-7, 0.999999985326375},
+            {5.0, 5.89080768883643e-7, 7.03204565645028e-7, 0.999999885876203},
+            {20.0, 0.0760051397707708, 0.27349758476299, 0.802507555007781},
+            {21.0, 0.087144222047629, 0.360641806810619, 0.72650241523701},
+            {22.0, 0.0940378846881819, 0.454679691498801, 0.639358193189381},
+            {23.0, 0.0956897500614809, 0.550369441560282, 0.545320308501199},
+            {24.0, 0.0919766921922999, 0.642346133752582, 0.449630558439718},
+            {25.0, 0.083641637261095, 0.725987771013677, 0.357653866247418},
+            {96.0, 5.93849188852098e-57, 1.0, 6.01900244560712e-57},
+            {97.0, 7.96593036832547e-59, 1.0, 8.05105570861321e-59},
+            {98.0, 8.44582921934367e-61, 1.0, 8.5125340287733e-61},
+            {99.0, 6.63604297068222e-63, 1.0, 6.670480942963e-63},
+            {100.0, 3.43501099007557e-65, 1.0, 3.4437972280786e-65},
+            {101.0, 8.78623800302957e-68, 1.0, 8.78623800302957e-68},
+        };
+
+        testHypergeometricDistributionProbabilities(populationSize, sampleSize, numberOfSucceses, data);
+    }
+
+    private void testHypergeometricDistributionProbabilities(int populationSize, int sampleSize, int numberOfSucceses, double[][] data) {
+        HypergeometricDistributionImpl dist = new HypergeometricDistributionImpl(populationSize, numberOfSucceses, sampleSize);
+        for (int i = 0; i < data.length; ++i) {
+            int x = (int)data[i][0];
+            double pdf = data[i][1];
+            double actualPdf = dist.probability(x);
+            TestUtils.assertRelativelyEquals("Expected equals for <"+x+"> pdf",pdf, actualPdf, 1.0e-9);
+
+            double cdf = data[i][2];
+            double actualCdf = dist.cumulativeProbability(x);
+            TestUtils.assertRelativelyEquals("Expected equals for <"+x+"> cdf",cdf, actualCdf, 1.0e-9);
+
+            double cdf1 = data[i][3];
+            double actualCdf1 = dist.upperCumulativeProbability(x);
+            TestUtils.assertRelativelyEquals("Expected equals for <"+x+"> cdf1",cdf1, actualCdf1, 1.0e-9);
+        }
+    }
+
+    public void testMoreLargeValues() {
+        int populationSize = 26896;
+        int sampleSize = 895;
+        int numberOfSucceses = 55;
+        double[][] data = {
+            {0.0, 0.155168304750504, 0.155168304750504, 1.0},
+            {1.0, 0.29437545000746, 0.449543754757964, 0.844831695249496},
+            {2.0, 0.273841321577003, 0.723385076334967, 0.550456245242036},
+            {3.0, 0.166488572570786, 0.889873648905753, 0.276614923665033},
+            {4.0, 0.0743969744713231, 0.964270623377076, 0.110126351094247},
+            {5.0, 0.0260542785784855, 0.990324901955562, 0.0357293766229237},
+            {20.0, 3.57101101678792e-16, 1.0, 3.78252101622096e-16},
+            {21.0, 2.00551638598312e-17, 1.0, 2.11509999433041e-17},
+            {22.0, 1.04317070180562e-18, 1.0, 1.09583608347287e-18},
+            {23.0, 5.03153504903308e-20, 1.0, 5.266538166725e-20},
+            {24.0, 2.2525984149695e-21, 1.0, 2.35003117691919e-21},
+            {25.0, 9.3677424515947e-23, 1.0, 9.74327619496943e-23},
+            {50.0, 9.83633962945521e-69, 1.0, 9.8677629437617e-69},
+            {51.0, 3.13448949497553e-71, 1.0, 3.14233143064882e-71},
+            {52.0, 7.82755221928122e-74, 1.0, 7.84193567329055e-74},
+            {53.0, 1.43662126065532e-76, 1.0, 1.43834540093295e-76},
+            {54.0, 1.72312692517348e-79, 1.0, 1.7241402776278e-79},
+            {55.0, 1.01335245432581e-82, 1.0, 1.01335245432581e-82},
+        };
+        testHypergeometricDistributionProbabilities(populationSize, sampleSize, numberOfSucceses, data);
+    }
+
+    public void testMomonts() {
+        final double tol = 1e-9;
+        HypergeometricDistributionImpl dist;
+        
+        dist = new HypergeometricDistributionImpl(1500, 40, 100);
+        assertEquals(dist.getNumericalMean(), 40d * 100d / 1500d, tol);
+        assertEquals(dist.getNumericalVariance(), ( 100d * 40d * (1500d - 100d) * (1500d - 40d) ) / ( (1500d * 1500d * 1499d) ), tol); 
+        
+        dist.setPopulationSize(3000);
+        dist.setNumberOfSuccesses(55);
+        dist.setSampleSize(200);
+        assertEquals(dist.getNumericalMean(), 55d * 200d / 3000d, tol);
+        assertEquals(dist.getNumericalVariance(), ( 200d * 55d * (3000d - 200d) * (3000d - 55d) ) / ( (3000d * 3000d * 2999d) ), tol);
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/distribution/IntegerDistributionAbstractTest.java b/src/test/java/org/apache/commons/math/distribution/IntegerDistributionAbstractTest.java
new file mode 100644
index 0000000..aee205c
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/distribution/IntegerDistributionAbstractTest.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.distribution;
+
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.util.FastMath;
+
+import junit.framework.TestCase;
+
+/**
+ * Abstract base class for {@link IntegerDistribution} tests.
+ * <p>
+ * To create a concrete test class for an integer distribution implementation,
+ *  implement makeDistribution() to return a distribution instance to use in
+ *  tests and each of the test data generation methods below.  In each case, the
+ *  test points and test values arrays returned represent parallel arrays of
+ *  inputs and expected values for the distribution returned by makeDistribution().
+ *  <p>
+ *  makeDensityTestPoints() -- arguments used to test probability density calculation
+ *  makeDensityTestValues() -- expected probability densities
+ *  makeCumulativeTestPoints() -- arguments used to test cumulative probabilities
+ *  makeCumulativeTestValues() -- expected cumulative probabilites
+ *  makeInverseCumulativeTestPoints() -- arguments used to test inverse cdf evaluation
+ *  makeInverseCumulativeTestValues() -- expected inverse cdf values
+ * <p>
+ *  To implement additional test cases with different distribution instances and test data,
+ *  use the setXxx methods for the instance data in test cases and call the verifyXxx methods
+ *  to verify results.
+ *
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ */
+public abstract class IntegerDistributionAbstractTest extends TestCase {
+
+//-------------------- Private test instance data -------------------------
+    /** Discrete distribution instance used to perform tests */
+    private IntegerDistribution distribution;
+
+    /** Tolerance used in comparing expected and returned values */
+    private double tolerance = 1E-4;
+
+    /** Arguments used to test probability density calculations */
+    private int[] densityTestPoints;
+
+    /** Values used to test probability density calculations */
+    private double[] densityTestValues;
+
+    /** Arguments used to test cumulative probability density calculations */
+    private int[] cumulativeTestPoints;
+
+    /** Values used to test cumulative probability density calculations */
+    private double[] cumulativeTestValues;
+
+    /** Arguments used to test inverse cumulative probability density calculations */
+    private double[] inverseCumulativeTestPoints;
+
+    /** Values used to test inverse cumulative probability density calculations */
+    private int[] inverseCumulativeTestValues;
+
+    //-------------------------------------------------------------------------
+
+    /**
+     * Constructor for IntegerDistributionAbstractTest.
+     * @param name
+     */
+    public IntegerDistributionAbstractTest(String name) {
+        super(name);
+    }
+
+    //-------------------- Abstract methods -----------------------------------
+
+    /** Creates the default discrete distribution instance to use in tests. */
+    public abstract IntegerDistribution makeDistribution();
+
+    /** Creates the default probability density test input values */
+    public abstract int[] makeDensityTestPoints();
+
+    /** Creates the default probability density test expected values */
+    public abstract double[] makeDensityTestValues();
+
+    /** Creates the default cumulative probability density test input values */
+    public abstract int[] makeCumulativeTestPoints();
+
+    /** Creates the default cumulative probability density test expected values */
+    public abstract double[] makeCumulativeTestValues();
+
+    /** Creates the default inverse cumulative probability test input values */
+    public abstract double[] makeInverseCumulativeTestPoints();
+
+    /** Creates the default inverse cumulative probability density test expected values */
+    public abstract int[] makeInverseCumulativeTestValues();
+
+    //-------------------- Setup / tear down ----------------------------------
+
+    /**
+     * Setup sets all test instance data to default values
+     */
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        distribution = makeDistribution();
+        densityTestPoints = makeDensityTestPoints();
+        densityTestValues = makeDensityTestValues();
+        cumulativeTestPoints = makeCumulativeTestPoints();
+        cumulativeTestValues = makeCumulativeTestValues();
+        inverseCumulativeTestPoints = makeInverseCumulativeTestPoints();
+        inverseCumulativeTestValues = makeInverseCumulativeTestValues();
+    }
+
+    /**
+     * Cleans up test instance data
+     */
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        distribution = null;
+        densityTestPoints = null;
+        densityTestValues = null;
+        cumulativeTestPoints = null;
+        cumulativeTestValues = null;
+        inverseCumulativeTestPoints = null;
+        inverseCumulativeTestValues = null;
+    }
+
+    //-------------------- Verification methods -------------------------------
+
+    /**
+     * Verifies that probability density calculations match expected values
+     * using current test instance data
+     */
+    protected void verifyDensities() throws Exception {
+        for (int i = 0; i < densityTestPoints.length; i++) {
+            assertEquals("Incorrect density value returned for " + densityTestPoints[i],
+                    densityTestValues[i],
+                    distribution.probability(densityTestPoints[i]), tolerance);
+        }
+    }
+
+    /**
+     * Verifies that cumulative probability density calculations match expected values
+     * using current test instance data
+     */
+    protected void verifyCumulativeProbabilities() throws Exception {
+        for (int i = 0; i < cumulativeTestPoints.length; i++) {
+            assertEquals("Incorrect cumulative probability value returned for " + cumulativeTestPoints[i],
+                    cumulativeTestValues[i],
+                    distribution.cumulativeProbability(cumulativeTestPoints[i]), tolerance);
+        }
+    }
+
+
+    /**
+     * Verifies that inverse cumulative probability density calculations match expected values
+     * using current test instance data
+     */
+    protected void verifyInverseCumulativeProbabilities() throws Exception {
+        for (int i = 0; i < inverseCumulativeTestPoints.length; i++) {
+            assertEquals("Incorrect inverse cumulative probability value returned for "
+                    + inverseCumulativeTestPoints[i], inverseCumulativeTestValues[i],
+                    distribution.inverseCumulativeProbability(inverseCumulativeTestPoints[i]));
+        }
+    }
+
+    //------------------------ Default test cases -----------------------------
+
+    /**
+     * Verifies that probability density calculations match expected values
+     * using default test instance data
+     */
+    public void testDensities() throws Exception {
+        verifyDensities();
+    }
+
+    /**
+     * Verifies that cumulative probability density calculations match expected values
+     * using default test instance data
+     */
+    public void testCumulativeProbabilities() throws Exception {
+        verifyCumulativeProbabilities();
+    }
+
+    /**
+     * Verifies that floating point arguments are correctly handled by
+     * cumulativeProbablility(-,-)
+     * JIRA: MATH-184
+     */
+    public void testFloatingPointArguments() throws Exception {
+        for (int i = 0; i < cumulativeTestPoints.length; i++) {
+            double arg = cumulativeTestPoints[i];
+            assertEquals(
+                    "Incorrect cumulative probability value returned for " +
+                    cumulativeTestPoints[i],
+                    cumulativeTestValues[i],
+                    distribution.cumulativeProbability(arg), tolerance);
+            if (i < cumulativeTestPoints.length - 1) {
+                double arg2 = cumulativeTestPoints[i + 1];
+                assertEquals("Inconsistent probability for discrete range " +
+                        "[ " + arg + "," + arg2 + " ]",
+                   distribution.cumulativeProbability(
+                           cumulativeTestPoints[i],
+                           cumulativeTestPoints[i + 1]),
+                   distribution.cumulativeProbability(arg, arg2), tolerance);
+                arg = arg - FastMath.random();
+                arg2 = arg2 + FastMath.random();
+                assertEquals("Inconsistent probability for discrete range " +
+                        "[ " + arg + "," + arg2 + " ]",
+                   distribution.cumulativeProbability(
+                           cumulativeTestPoints[i],
+                           cumulativeTestPoints[i + 1]),
+                   distribution.cumulativeProbability(arg, arg2), tolerance);
+            }
+        }
+        int one = 1;
+        int ten = 10;
+        int two = 2;
+        double oned = one;
+        double twod = two;
+        double tend = ten;
+        assertEquals(distribution.cumulativeProbability(one, two),
+                distribution.cumulativeProbability(oned, twod), tolerance);
+        assertEquals(distribution.cumulativeProbability(one, two),
+                distribution.cumulativeProbability(oned - tolerance,
+                        twod + 0.9), tolerance);
+        assertEquals(distribution.cumulativeProbability(two, ten),
+                distribution.cumulativeProbability(twod, tend), tolerance);
+        assertEquals(distribution.cumulativeProbability(two, ten),
+                distribution.cumulativeProbability(twod - tolerance,
+                        tend + 0.9), tolerance);
+    }
+
+    /**
+     * Verifies that inverse cumulative probability density calculations match expected values
+     * using default test instance data
+     */
+    public void testInverseCumulativeProbabilities() throws Exception {
+        verifyInverseCumulativeProbabilities();
+    }
+
+    /**
+     * Verifies that illegal arguments are correctly handled
+     */
+    public void testIllegalArguments() throws Exception {
+        try {
+            distribution.cumulativeProbability(1, 0);
+            fail("Expecting IllegalArgumentException for bad cumulativeProbability interval");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            distribution.inverseCumulativeProbability(-1);
+            fail("Expecting IllegalArgumentException for p = -1");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            distribution.inverseCumulativeProbability(2);
+            fail("Expecting IllegalArgumentException for p = 2");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+    
+    /**
+     * Test sampling
+     */
+    public void testSampling() throws Exception {
+        int[] densityPoints = makeDensityTestPoints();
+        double[] densityValues = makeDensityTestValues();
+        int sampleSize = 1000;
+        int length = TestUtils.eliminateZeroMassPoints(densityPoints, densityValues);
+        AbstractIntegerDistribution distribution = (AbstractIntegerDistribution) makeDistribution();
+        double[] expectedCounts = new double[length];
+        long[] observedCounts = new long[length];
+        for (int i = 0; i < length; i++) {
+            expectedCounts[i] = sampleSize * densityValues[i];
+        }
+        distribution.reseedRandomGenerator(1000); // Use fixed seed
+        int[] sample = distribution.sample(sampleSize);
+        for (int i = 0; i < sampleSize; i++) {
+          for (int j = 0; j < length; j++) {
+              if (sample[i] == densityPoints[j]) {
+                  observedCounts[j]++;
+              }
+          }
+        }
+        TestUtils.assertChiSquareAccept(densityPoints, expectedCounts, observedCounts, .001);
+    }
+
+    //------------------ Getters / Setters for test instance data -----------
+    /**
+     * @return Returns the cumulativeTestPoints.
+     */
+    protected int[] getCumulativeTestPoints() {
+        return cumulativeTestPoints;
+    }
+
+    /**
+     * @param cumulativeTestPoints The cumulativeTestPoints to set.
+     */
+    protected void setCumulativeTestPoints(int[] cumulativeTestPoints) {
+        this.cumulativeTestPoints = cumulativeTestPoints;
+    }
+
+    /**
+     * @return Returns the cumulativeTestValues.
+     */
+    protected double[] getCumulativeTestValues() {
+        return cumulativeTestValues;
+    }
+
+    /**
+     * @param cumulativeTestValues The cumulativeTestValues to set.
+     */
+    protected void setCumulativeTestValues(double[] cumulativeTestValues) {
+        this.cumulativeTestValues = cumulativeTestValues;
+    }
+
+    /**
+     * @return Returns the densityTestPoints.
+     */
+    protected int[] getDensityTestPoints() {
+        return densityTestPoints;
+    }
+
+    /**
+     * @param densityTestPoints The densityTestPoints to set.
+     */
+    protected void setDensityTestPoints(int[] densityTestPoints) {
+        this.densityTestPoints = densityTestPoints;
+    }
+
+    /**
+     * @return Returns the densityTestValues.
+     */
+    protected double[] getDensityTestValues() {
+        return densityTestValues;
+    }
+
+    /**
+     * @param densityTestValues The densityTestValues to set.
+     */
+    protected void setDensityTestValues(double[] densityTestValues) {
+        this.densityTestValues = densityTestValues;
+    }
+
+    /**
+     * @return Returns the distribution.
+     */
+    protected IntegerDistribution getDistribution() {
+        return distribution;
+    }
+
+    /**
+     * @param distribution The distribution to set.
+     */
+    protected void setDistribution(IntegerDistribution distribution) {
+        this.distribution = distribution;
+    }
+
+    /**
+     * @return Returns the inverseCumulativeTestPoints.
+     */
+    protected double[] getInverseCumulativeTestPoints() {
+        return inverseCumulativeTestPoints;
+    }
+
+    /**
+     * @param inverseCumulativeTestPoints The inverseCumulativeTestPoints to set.
+     */
+    protected void setInverseCumulativeTestPoints(double[] inverseCumulativeTestPoints) {
+        this.inverseCumulativeTestPoints = inverseCumulativeTestPoints;
+    }
+
+    /**
+     * @return Returns the inverseCumulativeTestValues.
+     */
+    protected int[] getInverseCumulativeTestValues() {
+        return inverseCumulativeTestValues;
+    }
+
+    /**
+     * @param inverseCumulativeTestValues The inverseCumulativeTestValues to set.
+     */
+    protected void setInverseCumulativeTestValues(int[] inverseCumulativeTestValues) {
+        this.inverseCumulativeTestValues = inverseCumulativeTestValues;
+    }
+
+    /**
+     * @return Returns the tolerance.
+     */
+    protected double getTolerance() {
+        return tolerance;
+    }
+
+    /**
+     * @param tolerance The tolerance to set.
+     */
+    protected void setTolerance(double tolerance) {
+        this.tolerance = tolerance;
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/distribution/NormalDistributionTest.java b/src/test/java/org/apache/commons/math/distribution/NormalDistributionTest.java
new file mode 100644
index 0000000..d45c0dc
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/distribution/NormalDistributionTest.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.distribution;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Test cases for NormalDistribution.
+ * Extends ContinuousDistributionAbstractTest.  See class javadoc for
+ * ContinuousDistributionAbstractTest for details.
+ *
+ * @version $Revision: 1054524 $ $Date: 2011-01-03 05:59:18 +0100 (lun. 03 janv. 2011) $
+ */
+public class NormalDistributionTest extends ContinuousDistributionAbstractTest  {
+
+    /**
+     * Constructor for NormalDistributionTest.
+     * @param arg0
+     */
+    public NormalDistributionTest(String arg0) {
+        super(arg0);
+    }
+
+    //-------------- Implementations for abstract methods -----------------------
+
+    /** Creates the default continuous distribution instance to use in tests. */
+    @Override
+    public NormalDistribution makeDistribution() {
+        return new NormalDistributionImpl(2.1, 1.4);
+    }
+
+    /** Creates the default cumulative probability distribution test input values */
+    @Override
+    public double[] makeCumulativeTestPoints() {
+        // quantiles computed using R
+        return new double[] {-2.226325228634938d, -1.156887023657177d, -0.643949578356075d, -0.2027950777320613d, 0.305827808237559d,
+                6.42632522863494d, 5.35688702365718d, 4.843949578356074d, 4.40279507773206d, 3.89417219176244d};
+    }
+
+    /** Creates the default cumulative probability density test expected values */
+    @Override
+    public double[] makeCumulativeTestValues() {
+        return new double[] {0.001d, 0.01d, 0.025d, 0.05d, 0.1d, 0.999d,
+                0.990d, 0.975d, 0.950d, 0.900d};
+    }
+
+    /** Creates the default probability density test expected values */
+    @Override
+    public double[] makeDensityTestValues() {
+        return new double[] {0.00240506434076, 0.0190372444310, 0.0417464784322, 0.0736683145538, 0.125355951380,
+                0.00240506434076, 0.0190372444310, 0.0417464784322, 0.0736683145538, 0.125355951380};
+    }
+
+    // --------------------- Override tolerance  --------------
+    protected double defaultTolerance = NormalDistributionImpl.DEFAULT_INVERSE_ABSOLUTE_ACCURACY;
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        setTolerance(defaultTolerance);
+    }
+
+    //---------------------------- Additional test cases -------------------------
+
+    private void verifyQuantiles() throws Exception {
+        NormalDistribution distribution = (NormalDistribution) getDistribution();
+        double mu = distribution.getMean();
+        double sigma = distribution.getStandardDeviation();
+        setCumulativeTestPoints( new double[] {mu - 2 *sigma, mu - sigma,
+                mu, mu + sigma, mu + 2 * sigma,  mu + 3 * sigma, mu + 4 * sigma,
+                mu + 5 * sigma});
+        // Quantiles computed using R (same as Mathematica)
+        setCumulativeTestValues(new double[] {0.02275013194817921, 0.158655253931457, 0.5, 0.841344746068543,
+                0.977249868051821, 0.99865010196837, 0.999968328758167,  0.999999713348428});
+        verifyCumulativeProbabilities();
+    }
+
+    public void testQuantiles() throws Exception {
+        setDensityTestValues(new double[] {0.0385649760808, 0.172836231799, 0.284958771715, 0.172836231799, 0.0385649760808,
+                0.00316560600853, 9.55930184035e-05, 1.06194251052e-06});
+        verifyQuantiles();
+        verifyDensities();
+
+        setDistribution(new NormalDistributionImpl(0, 1));
+        setDensityTestValues(new double[] {0.0539909665132, 0.241970724519, 0.398942280401, 0.241970724519, 0.0539909665132,
+                0.00443184841194, 0.000133830225765, 1.48671951473e-06});
+        verifyQuantiles();
+        verifyDensities();
+
+        setDistribution(new NormalDistributionImpl(0, 0.1));
+        setDensityTestValues(new double[] {0.539909665132, 2.41970724519, 3.98942280401, 2.41970724519,
+                0.539909665132, 0.0443184841194, 0.00133830225765, 1.48671951473e-05});
+        verifyQuantiles();
+        verifyDensities();
+    }
+
+    public void testInverseCumulativeProbabilityExtremes() throws Exception {
+        setInverseCumulativeTestPoints(new double[] {0, 1});
+        setInverseCumulativeTestValues(
+                new double[] {Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY});
+        verifyInverseCumulativeProbabilities();
+    }
+
+    public void testGetMean() {
+        NormalDistribution distribution = (NormalDistribution) getDistribution();
+        assertEquals(2.1, distribution.getMean(), 0);
+    }
+
+    public void testSetMean() throws Exception {
+        double mu = FastMath.random();
+        NormalDistribution distribution = (NormalDistribution) getDistribution();
+        distribution.setMean(mu);
+        verifyQuantiles();
+    }
+
+    public void testGetStandardDeviation() {
+        NormalDistribution distribution = (NormalDistribution) getDistribution();
+        assertEquals(1.4, distribution.getStandardDeviation(), 0);
+    }
+
+    public void testSetStandardDeviation() throws Exception {
+        double sigma = 0.1d + FastMath.random();
+        NormalDistribution distribution = (NormalDistribution) getDistribution();
+        distribution.setStandardDeviation(sigma);
+        assertEquals(sigma, distribution.getStandardDeviation(), 0);
+        verifyQuantiles();
+        try {
+            distribution.setStandardDeviation(0);
+            fail("Expecting IllegalArgumentException for sd = 0");
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+    }
+
+    public void testDensity() {
+        double [] x = new double[]{-2, -1, 0, 1, 2};
+        // R 2.5: print(dnorm(c(-2,-1,0,1,2)), digits=10)
+        checkDensity(0, 1, x, new double[]{0.05399096651, 0.24197072452, 0.39894228040, 0.24197072452, 0.05399096651});
+        // R 2.5: print(dnorm(c(-2,-1,0,1,2), mean=1.1), digits=10)
+        checkDensity(1.1, 1, x, new double[]{0.003266819056,0.043983595980,0.217852177033,0.396952547477,0.266085249899});
+    }
+
+    private void checkDensity(double mean, double sd, double[] x, double[] expected) {
+        NormalDistribution d = new NormalDistributionImpl(mean, sd);
+        for (int i = 0; i < x.length; i++) {
+            assertEquals(expected[i], d.density(x[i]), 1e-9);
+        }
+    }
+
+    /**
+     * Check to make sure top-coding of extreme values works correctly.
+     * Verifies fixes for JIRA MATH-167, MATH-414
+     */
+    public void testExtremeValues() throws Exception {
+        NormalDistribution distribution = (NormalDistribution) getDistribution();
+        distribution.setMean(0);
+        distribution.setStandardDeviation(1);
+        for (int i = 0; i < 100; i++) { // make sure no convergence exception
+            double lowerTail = distribution.cumulativeProbability(-i);
+            double upperTail = distribution.cumulativeProbability(i);
+            if (i < 9) { // make sure not top-coded 
+                // For i = 10, due to bad tail precision in erf (MATH-364), 1 is returned
+                // TODO: once MATH-364 is resolved, replace 9 with 30
+                assertTrue(lowerTail > 0.0d);
+                assertTrue(upperTail < 1.0d);
+            }
+            else { // make sure top coding not reversed
+                assertTrue(lowerTail < 0.00001);
+                assertTrue(upperTail > 0.99999);
+            }
+        }
+        
+        assertEquals(distribution.cumulativeProbability(Double.MAX_VALUE), 1, 0);
+        assertEquals(distribution.cumulativeProbability(-Double.MAX_VALUE), 0, 0);
+        assertEquals(distribution.cumulativeProbability(Double.POSITIVE_INFINITY), 1, 0);
+        assertEquals(distribution.cumulativeProbability(Double.NEGATIVE_INFINITY), 0, 0);
+        
+   }
+
+    public void testMath280() throws MathException {
+        NormalDistribution normal = new NormalDistributionImpl(0,1);
+        double result = normal.inverseCumulativeProbability(0.9986501019683698);
+        assertEquals(3.0, result, defaultTolerance);
+        result = normal.inverseCumulativeProbability(0.841344746068543);
+        assertEquals(1.0, result, defaultTolerance);
+        result = normal.inverseCumulativeProbability(0.9999683287581673);
+        assertEquals(4.0, result, defaultTolerance);
+        result = normal.inverseCumulativeProbability(0.9772498680518209);
+        assertEquals(2.0, result, defaultTolerance);
+    }
+
+    public void testMomonts() {
+        final double tol = 1e-9;
+        NormalDistributionImpl dist;
+        
+        dist = new NormalDistributionImpl(0, 1);        
+        assertEquals(dist.getNumericalVariance(), 1, tol);        
+ 
+        dist.setMean(2.2);
+        dist.setStandardDeviation(1.4);        
+        assertEquals(dist.getNumericalVariance(), 1.4 * 1.4, tol);
+        
+        dist.setMean(-2000.9);
+        dist.setStandardDeviation(10.4);
+        assertEquals(dist.getNumericalVariance(), 10.4 * 10.4, tol);
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/distribution/PascalDistributionTest.java b/src/test/java/org/apache/commons/math/distribution/PascalDistributionTest.java
new file mode 100644
index 0000000..e476e78
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/distribution/PascalDistributionTest.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.distribution;
+
+/**
+ * Test cases for PascalDistribution.
+ * Extends IntegerDistributionAbstractTest.  See class javadoc for
+ * IntegerDistributionAbstractTest for details.
+ *
+ * @version $Revision: 1054524 $ $Date: 2011-01-03 05:59:18 +0100 (lun. 03 janv. 2011) $
+ */
+public class PascalDistributionTest extends IntegerDistributionAbstractTest {
+
+    /**
+     * Constructor for PascalDistributionTest.
+     * @param name
+     */
+    public PascalDistributionTest(String name) {
+        super(name);
+    }
+
+    // --------------------- Override tolerance  --------------
+    protected double defaultTolerance = NormalDistributionImpl.DEFAULT_INVERSE_ABSOLUTE_ACCURACY;
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        setTolerance(defaultTolerance);
+    }
+
+    //-------------- Implementations for abstract methods -----------------------
+
+    /** Creates the default discrete distribution instance to use in tests. */
+    @Override
+    public IntegerDistribution makeDistribution() {
+        return new PascalDistributionImpl(10,0.70);
+    }
+
+    /** Creates the default probability density test input values */
+    @Override
+    public int[] makeDensityTestPoints() {
+      return new int[] {-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
+    }
+
+    /** Creates the default probability density test expected values */
+    @Override
+    public double[] makeDensityTestValues() {
+      return new double[] {0, 0.0282475249, 0.0847425747, 0.139825248255, 0.167790297906, 0.163595540458,
+              0.137420253985, 0.103065190489, 0.070673273478, 0.0450542118422, 0.0270325271053,
+              0.0154085404500, 0.0084046584273};
+    }
+
+    /** Creates the default cumulative probability density test input values */
+    @Override
+    public int[] makeCumulativeTestPoints() {
+      return makeDensityTestPoints();
+    }
+
+    /** Creates the default cumulative probability density test expected values */
+    @Override
+    public double[] makeCumulativeTestValues() {
+      return new double[] {0, 0.0282475249, 0.1129900996, 0.252815347855, 0.420605645761, 0.584201186219,
+              0.721621440204, 0.824686630693, 0.895359904171, 0.940414116013, 0.967446643119,
+              0.982855183569, 0.991259841996};
+        }
+
+    /** Creates the default inverse cumulative probability test input values */
+    @Override
+    public double[] makeInverseCumulativeTestPoints() {
+      return new double[] {0, 0.001d, 0.010d, 0.025d, 0.050d, 0.100d, 0.999d,
+          0.990d, 0.975d, 0.950d, 0.900d, 1};
+        }
+
+    /** Creates the default inverse cumulative probability density test expected values */
+    @Override
+    public int[] makeInverseCumulativeTestValues() {
+      return new int[] {-1, -1, -1, -1, 0, 0, 13, 10, 9, 8, 7, Integer.MAX_VALUE};
+    }
+
+    //----------------- Additional test cases ---------------------------------
+
+    /** Test degenerate case p = 0   */
+    public void testDegenerate0() throws Exception {
+        setDistribution(new PascalDistributionImpl(5,0.0d));
+        setCumulativeTestPoints(new int[] {-1, 0, 1, 5, 10 });
+        setCumulativeTestValues(new double[] {0d, 0d, 0d, 0d, 0d});
+        setDensityTestPoints(new int[] {-1, 0, 1, 10, 11});
+        setDensityTestValues(new double[] {0d, 0d, 0d, 0d, 0d});
+        setInverseCumulativeTestPoints(new double[] {0.1d, 0.5d});
+        setInverseCumulativeTestValues(new int[] {Integer.MAX_VALUE - 1, Integer.MAX_VALUE - 1});
+        verifyDensities();
+        verifyCumulativeProbabilities();
+        verifyInverseCumulativeProbabilities();
+    }
+
+    /** Test degenerate case p = 1   */
+    public void testDegenerate1() throws Exception {
+        setDistribution(new PascalDistributionImpl(5,1.0d));
+        setCumulativeTestPoints(new int[] {-1, 0, 1, 2, 5, 10 });
+        setCumulativeTestValues(new double[] {0d, 1d, 1d, 1d, 1d, 1d});
+        setDensityTestPoints(new int[] {-1, 0, 1, 2, 5, 10});
+        setDensityTestValues(new double[] {0d, 1d, 0d, 0d, 0d, 0d});
+        setInverseCumulativeTestPoints(new double[] {0.1d, 0.5d});
+        setInverseCumulativeTestValues(new int[] {-1, -1});
+        verifyDensities();
+        verifyCumulativeProbabilities();
+        verifyInverseCumulativeProbabilities();
+    }
+
+    public void testMomonts() {
+        final double tol = 1e-9;
+        PascalDistributionImpl dist;
+        
+        dist = new PascalDistributionImpl(10, 0.5);
+        assertEquals(dist.getNumericalMean(), ( 10d * 0.5d ) / 0.5d, tol);
+        assertEquals(dist.getNumericalVariance(), ( 10d * 0.5d ) / (0.5d * 0.5d), tol); 
+        
+        dist.setNumberOfSuccesses(25);
+        dist.setProbabilityOfSuccess(0.3);
+        assertEquals(dist.getNumericalMean(), ( 25d * 0.3d ) / 0.7d, tol);
+        assertEquals(dist.getNumericalVariance(), ( 25d * 0.3d ) / (0.7d * 0.7d), tol);
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/distribution/PoissonDistributionTest.java b/src/test/java/org/apache/commons/math/distribution/PoissonDistributionTest.java
new file mode 100644
index 0000000..1d2bc35
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/distribution/PoissonDistributionTest.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 org.apache.commons.math.MathException;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * <code>PoissonDistributionTest</code>
+ *
+ * @version $Revision: 1054524 $ $Date: 2011-01-03 05:59:18 +0100 (lun. 03 janv. 2011) $
+ */
+public class PoissonDistributionTest extends IntegerDistributionAbstractTest {
+
+    /**
+     * Poisson parameter value for the test distribution.
+     */
+    private static final double DEFAULT_TEST_POISSON_PARAMETER = 4.0;
+
+    /**
+     * Constructor.
+     * @param name
+     */
+    public PoissonDistributionTest(String name) {
+        super(name);
+        setTolerance(1e-12);
+    }
+
+    /**
+     * Creates the default discrete distribution instance to use in tests.
+     */
+    @Override
+    public IntegerDistribution makeDistribution() {
+        return new PoissonDistributionImpl(DEFAULT_TEST_POISSON_PARAMETER);
+    }
+
+    /**
+     * Creates the default probability density test input values.
+     */
+    @Override
+    public int[] makeDensityTestPoints() {
+        return new int[] { -1, 0, 1, 2, 3, 4, 5, 10, 20};
+    }
+
+    /**
+     * Creates the default probability density test expected values.
+     * These and all other test values are generated by R, version 1.8.1
+     */
+    @Override
+    public double[] makeDensityTestValues() {
+        return new double[] { 0d, 0.0183156388887d,  0.073262555555d,
+                0.14652511111d, 0.195366814813d, 0.195366814813,
+                0.156293451851d, 0.00529247667642d, 8.27746364655e-09};
+    }
+
+    /**
+     * Creates the default cumulative probability density test input values.
+     */
+    @Override
+    public int[] makeCumulativeTestPoints() {
+        return new int[] { -1, 0, 1, 2, 3, 4, 5, 10, 20 };
+    }
+
+    /**
+     * Creates the default cumulative probability density test expected values.
+     */
+    @Override
+    public double[] makeCumulativeTestValues() {
+        return new double[] { 0d,  0.0183156388887d, 0.0915781944437d,
+                0.238103305554d, 0.433470120367d, 0.62883693518,
+                0.78513038703d,  0.99716023388d, 0.999999998077 };
+    }
+
+    /**
+     * Creates the default inverse cumulative probability test input values.
+     * Increased 3rd and 7th values slightly as computed cumulative
+     * probabilities for corresponding values exceeds the target value (still
+     * within tolerance).
+     */
+    @Override
+    public double[] makeInverseCumulativeTestPoints() {
+        return new double[] { 0d,  0.018315638889d, 0.0915781944437d,
+                0.238103305554d, 0.433470120367d, 0.62883693518,
+                0.78513038704d,  0.99716023388d, 0.999999998077 };
+    }
+
+    /**
+     * Creates the default inverse cumulative probability density test expected values.
+     */
+    @Override
+    public int[] makeInverseCumulativeTestValues() {
+        return new int[] { -1, 0, 1, 2, 3, 4, 5, 10, 20};
+    }
+
+    /**
+     * Test the normal approximation of the Poisson distribution by
+     * calculating P(90 ≤ X ≤ 110) for X = Po(100) and
+     * P(9900 ≤ X ≤ 10200) for X  = Po(10000)
+     */
+    public void testNormalApproximateProbability() throws Exception {
+        PoissonDistribution dist = new PoissonDistributionImpl(100);
+        double result = dist.normalApproximateProbability(110)
+                - dist.normalApproximateProbability(89);
+        assertEquals(0.706281887248, result, 1E-10);
+        dist.setMean(10000);
+        result = dist.normalApproximateProbability(10200)
+        - dist.normalApproximateProbability(9899);
+        assertEquals(0.820070051552, result, 1E-10);
+    }
+
+    /**
+     * Test the degenerate cases of a 0.0 and 1.0 inverse cumulative probability.
+     * @throws Exception
+     */
+    public void testDegenerateInverseCumulativeProbability() throws Exception {
+        PoissonDistribution dist = new PoissonDistributionImpl(DEFAULT_TEST_POISSON_PARAMETER);
+        assertEquals(Integer.MAX_VALUE, dist.inverseCumulativeProbability(1.0d));
+        assertEquals(-1, dist.inverseCumulativeProbability(0d));
+    }
+
+    public void testMean() {
+        PoissonDistribution dist = new PoissonDistributionImpl(DEFAULT_TEST_POISSON_PARAMETER);
+        try {
+            dist.setMean(-1);
+            fail("negative mean.  IllegalArgumentException expected");
+        } catch(IllegalArgumentException ex) {
+        }
+
+        dist.setMean(10.0);
+        assertEquals(10.0, dist.getMean(), 0.0);
+    }
+
+    public void testLargeMeanCumulativeProbability() {
+        PoissonDistribution dist = new PoissonDistributionImpl(1.0);
+        double mean = 1.0;
+        while (mean <= 10000000.0) {
+            dist.setMean(mean);
+
+            double x = mean * 2.0;
+            double dx = x / 10.0;
+            double p = Double.NaN;
+            double sigma = FastMath.sqrt(mean);
+            while (x >= 0) {
+                try {
+                    p = dist.cumulativeProbability(x);
+                    assertFalse("NaN cumulative probability returned for mean = " +
+                            mean + " x = " + x,Double.isNaN(p));
+                    if (x > mean - 2 * sigma) {
+                        assertTrue("Zero cum probaility returned for mean = " +
+                                mean + " x = " + x, p > 0);
+                    }
+                } catch (MathException ex) {
+                    fail("mean of " + mean + " and x of " + x + " caused " + ex.getMessage());
+                }
+                x -= dx;
+            }
+
+            mean *= 10.0;
+        }
+    }
+
+    /**
+     * JIRA: MATH-282
+     */
+    public void testCumulativeProbabilitySpecial() throws Exception {
+        PoissonDistribution dist = new PoissonDistributionImpl(1.0);
+        dist.setMean(9120);
+        checkProbability(dist, 9075);
+        checkProbability(dist, 9102);
+        dist.setMean(5058);
+        checkProbability(dist, 5044);
+        dist.setMean(6986);
+        checkProbability(dist, 6950);
+    }
+
+    private void checkProbability(PoissonDistribution dist, double x) throws Exception {
+        double p = dist.cumulativeProbability(x);
+        assertFalse("NaN cumulative probability returned for mean = " +
+                dist.getMean() + " x = " + x, Double.isNaN(p));
+        assertTrue("Zero cum probability returned for mean = " +
+                dist.getMean() + " x = " + x, p > 0);
+    }
+
+    public void testLargeMeanInverseCumulativeProbability() throws Exception {
+        PoissonDistribution dist = new PoissonDistributionImpl(1.0);
+        double mean = 1.0;
+        while (mean <= 100000.0) { // Extended test value: 1E7.  Reduced to limit run time.
+            dist.setMean(mean);
+            double p = 0.1;
+            double dp = p;
+            while (p < .99) {
+                double ret = Double.NaN;
+                try {
+                    ret = dist.inverseCumulativeProbability(p);
+                    // Verify that returned value satisties definition
+                    assertTrue(p >= dist.cumulativeProbability(ret));
+                    assertTrue(p < dist.cumulativeProbability(ret + 1));
+                } catch (MathException ex) {
+                    fail("mean of " + mean + " and p of " + p + " caused " + ex.getMessage());
+                }
+                p += dp;
+            }
+            mean *= 10.0;
+        }
+    }
+
+    public void testMomonts() {
+        final double tol = 1e-9;
+        PoissonDistributionImpl dist;
+        
+        dist = new PoissonDistributionImpl(1);
+        assertEquals(dist.getNumericalVariance(), 1, tol); 
+        
+        dist.setMean(11.23);
+        assertEquals(dist.getNumericalVariance(), 11.23, tol);
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/distribution/TDistributionTest.java b/src/test/java/org/apache/commons/math/distribution/TDistributionTest.java
new file mode 100644
index 0000000..9faf9a7
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/distribution/TDistributionTest.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.distribution;
+
+/**
+ * Test cases for TDistribution.
+ * Extends ContinuousDistributionAbstractTest.  See class javadoc for
+ * ContinuousDistributionAbstractTest for details.
+ *
+ * @version $Revision: 1054524 $ $Date: 2011-01-03 05:59:18 +0100 (lun. 03 janv. 2011) $
+ */
+public class TDistributionTest extends ContinuousDistributionAbstractTest {
+
+    /**
+     * Constructor for TDistributionTest.
+     * @param name
+     */
+    public TDistributionTest(String name) {
+        super(name);
+    }
+
+//-------------- Implementations for abstract methods -----------------------
+
+    /** Creates the default continuous distribution instance to use in tests. */
+    @Override
+    public TDistribution makeDistribution() {
+        return new TDistributionImpl(5.0);
+    }
+
+    /** Creates the default cumulative probability distribution test input values */
+    @Override
+    public double[] makeCumulativeTestPoints() {
+        // quantiles computed using R version 2.9.2
+        return new double[] {-5.89342953136, -3.36492999891, -2.57058183564, -2.01504837333, -1.47588404882,
+                5.89342953136, 3.36492999891, 2.57058183564, 2.01504837333, 1.47588404882};
+    }
+
+    /** Creates the default cumulative probability density test expected values */
+    @Override
+    public double[] makeCumulativeTestValues() {
+        return new double[] {0.001, 0.01, 0.025, 0.05, 0.1, 0.999,
+                0.990, 0.975, 0.950, 0.900};
+    }
+
+    /** Creates the default probability density test expected values */
+    @Override
+    public double[] makeDensityTestValues() {
+        return new double[] {0.000756494565517, 0.0109109752919, 0.0303377878006, 0.0637967988952, 0.128289492005,
+                0.000756494565517, 0.0109109752919, 0.0303377878006, 0.0637967988952, 0.128289492005};
+    }
+
+    // --------------------- Override tolerance  --------------
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        setTolerance(1E-9);
+    }
+
+    //---------------------------- Additional test cases -------------------------
+    /**
+     * @see <a href="http://issues.apache.org/bugzilla/show_bug.cgi?id=27243">
+     *      Bug report that prompted this unit test.</a>
+     */
+    public void testCumulativeProbabilityAgaintStackOverflow() throws Exception {
+        TDistributionImpl td = new TDistributionImpl(5.);
+        td.cumulativeProbability(.1);
+        td.cumulativeProbability(.01);
+    }
+
+    public void testSmallDf() throws Exception {
+        setDistribution(new TDistributionImpl(1d));
+        // quantiles computed using R version 2.9.2
+        setCumulativeTestPoints(new double[] {-318.308838986, -31.8205159538, -12.7062047362,
+                -6.31375151468, -3.07768353718, 318.308838986, 31.8205159538, 12.7062047362,
+                 6.31375151468, 3.07768353718});
+        setDensityTestValues(new double[] {3.14158231817e-06, 0.000314055924703, 0.00195946145194,
+                0.00778959736375, 0.0303958893917, 3.14158231817e-06, 0.000314055924703,
+                0.00195946145194, 0.00778959736375, 0.0303958893917});
+        setInverseCumulativeTestValues(getCumulativeTestPoints());
+        verifyCumulativeProbabilities();
+        verifyInverseCumulativeProbabilities();
+        verifyDensities();
+    }
+
+    public void testInverseCumulativeProbabilityExtremes() throws Exception {
+        setInverseCumulativeTestPoints(new double[] {0, 1});
+        setInverseCumulativeTestValues(
+                new double[] {Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY});
+        verifyInverseCumulativeProbabilities();
+    }
+
+    public void testDfAccessors() {
+        TDistribution distribution = (TDistribution) getDistribution();
+        assertEquals(5d, distribution.getDegreesOfFreedom(), Double.MIN_VALUE);
+        distribution.setDegreesOfFreedom(4d);
+        assertEquals(4d, distribution.getDegreesOfFreedom(), Double.MIN_VALUE);
+        try {
+            distribution.setDegreesOfFreedom(0d);
+            fail("Expecting IllegalArgumentException for df = 0");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+
+    public void testMomonts() {
+        final double tol = 1e-9;
+        TDistributionImpl dist;
+        
+        dist = new TDistributionImpl(1);
+        assertTrue(Double.isNaN(dist.getNumericalMean()));
+        assertTrue(Double.isNaN(dist.getNumericalVariance())); 
+        
+        dist.setDegreesOfFreedom(1.5);
+        assertEquals(dist.getNumericalMean(), 0, tol);
+        assertTrue(Double.isInfinite(dist.getNumericalVariance()));
+        
+        dist.setDegreesOfFreedom(5);
+        assertEquals(dist.getNumericalMean(), 0, tol);
+        assertEquals(dist.getNumericalVariance(), 5d / (5d - 2d), tol);        
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/distribution/WeibullDistributionTest.java b/src/test/java/org/apache/commons/math/distribution/WeibullDistributionTest.java
new file mode 100644
index 0000000..a25be43
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/distribution/WeibullDistributionTest.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.distribution;
+
+import org.apache.commons.math.special.Gamma;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Test cases for WeibullDistribution.
+ * Extends ContinuousDistributionAbstractTest.  See class javadoc for
+ * ContinuousDistributionAbstractTest for details.
+ *
+ * @version $Revision: 1054524 $ $Date: 2011-01-03 05:59:18 +0100 (lun. 03 janv. 2011) $
+ */
+public class WeibullDistributionTest extends ContinuousDistributionAbstractTest  {
+
+    /**
+     * Constructor for CauchyDistributionTest.
+     * @param arg0
+     */
+    public WeibullDistributionTest(String arg0) {
+        super(arg0);
+    }
+
+    //-------------- Implementations for abstract methods -----------------------
+
+    /** Creates the default continuous distribution instance to use in tests. */
+    @Override
+    public WeibullDistribution makeDistribution() {
+        return new WeibullDistributionImpl(1.2, 2.1);
+    }
+
+    /** Creates the default cumulative probability distribution test input values */
+    @Override
+    public double[] makeCumulativeTestPoints() {
+        // quantiles computed using R version 2.9.2
+        return new double[] {0.00664355180993, 0.0454328283309, 0.0981162737374, 0.176713524579, 0.321946865392,
+                10.5115496887, 7.4976304671, 6.23205600701, 5.23968436955, 4.2079028257};
+    }
+
+    /** Creates the default cumulative probability density test expected values */
+    @Override
+    public double[] makeCumulativeTestValues() {
+        return new double[] {0.001, 0.01, 0.025, 0.05, 0.1, 0.999, 0.990, 0.975, 0.950, 0.900};
+    }
+
+    /** Creates the default probability density test expected values */
+    @Override
+    public double[] makeDensityTestValues() {
+        return new double[] {0.180535929306, 0.262801138133, 0.301905425199, 0.330899152971,
+          0.353441418887, 0.000788590320203, 0.00737060094841, 0.0177576041516, 0.0343043442574, 0.065664589369};
+    }
+
+    //---------------------------- Additional test cases -------------------------
+
+    public void testInverseCumulativeProbabilityExtremes() throws Exception {
+        setInverseCumulativeTestPoints(new double[] {0.0, 1.0});
+        setInverseCumulativeTestValues(
+                new double[] {0.0, Double.POSITIVE_INFINITY});
+        verifyInverseCumulativeProbabilities();
+    }
+
+    public void testAlpha() {
+        WeibullDistribution distribution = (WeibullDistribution) getDistribution();
+        double expected = FastMath.random();
+        distribution.setShape(expected);
+        assertEquals(expected, distribution.getShape(), 0.0);
+    }
+
+    public void testBeta() {
+        WeibullDistribution distribution = (WeibullDistribution) getDistribution();
+        double expected = FastMath.random();
+        distribution.setScale(expected);
+        assertEquals(expected, distribution.getScale(), 0.0);
+    }
+
+    public void testSetAlpha() {
+        WeibullDistribution distribution = (WeibullDistribution) getDistribution();
+        try {
+            distribution.setShape(0.0);
+            fail("Can not have 0.0 alpha.");
+        } catch (IllegalArgumentException ex) {
+            // success
+        }
+
+        try {
+            distribution.setShape(-1.0);
+            fail("Can not have negative alpha.");
+        } catch (IllegalArgumentException ex) {
+            // success
+        }
+    }
+
+    public void testSetBeta() {
+        WeibullDistribution distribution = (WeibullDistribution) getDistribution();
+        try {
+            distribution.setScale(0.0);
+            fail("Can not have 0.0 beta.");
+        } catch (IllegalArgumentException ex) {
+            // success
+        }
+
+        try {
+            distribution.setScale(-1.0);
+            fail("Can not have negative beta.");
+        } catch (IllegalArgumentException ex) {
+            // success
+        }
+    }
+
+    public void testMomonts() {
+        final double tol = 1e-9;
+        WeibullDistributionImpl dist;
+        
+        dist = new WeibullDistributionImpl(2.5, 3.5);
+        // In R: 3.5*gamma(1+(1/2.5)) (or emperically: mean(rweibull(10000, 2.5, 3.5)))
+        assertEquals(dist.getNumericalMean(), 3.5 * FastMath.exp(Gamma.logGamma(1 + (1 / 2.5))), tol);
+        assertEquals(dist.getNumericalVariance(), (3.5 * 3.5) * 
+                FastMath.exp(Gamma.logGamma(1 + (2 / 2.5))) -
+                (dist.getNumericalMean() * dist.getNumericalMean()), tol); 
+        
+        dist.setShape(10.4);
+        dist.setScale(2.222);
+        assertEquals(dist.getNumericalMean(), 2.222 * FastMath.exp(Gamma.logGamma(1 + (1 / 10.4))), tol);
+        assertEquals(dist.getNumericalVariance(), (2.222 * 2.222) * 
+                FastMath.exp(Gamma.logGamma(1 + (2 / 10.4))) -
+                (dist.getNumericalMean() * dist.getNumericalMean()), tol);
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/distribution/ZipfDistributionTest.java b/src/test/java/org/apache/commons/math/distribution/ZipfDistributionTest.java
new file mode 100644
index 0000000..5a8c173
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/distribution/ZipfDistributionTest.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.distribution;
+
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Test cases for {@link ZipfDistribution}.
+ * Extends IntegerDistributionAbstractTest.  See class javadoc for
+ * IntegerDistributionAbstractTest for details.
+ *
+ * @version $Revision: 1054524 $ $Date: 2011-01-03 05:59:18 +0100 (lun. 03 janv. 2011) $
+ */
+public class ZipfDistributionTest extends IntegerDistributionAbstractTest {
+    public ZipfDistributionTest(String name) {
+        super(name);
+    }
+
+    //-------------- Implementations for abstract methods -----------------------
+
+    /** Creates the default discrete distribution instance to use in tests. */
+    @Override
+    public IntegerDistribution makeDistribution() {
+        return new ZipfDistributionImpl(10, 1);
+    }
+
+    /** Creates the default probability density test input values */
+    @Override
+    public int[] makeDensityTestPoints() {
+        return new int[] {-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11};
+    }
+
+    /** Creates the default probability density test expected values */
+    @Override
+    public double[] makeDensityTestValues() {
+        return new double[] {0d, 0d, 0.3414d, 0.1707d, 0.1138d, 0.0854d, 0.0683d,
+                0.0569d, 0.0488d, 0.0427d, 0.0379d, 0.0341d, 0d};
+    }
+
+    /** Creates the default cumulative probability density test input values */
+    @Override
+    public int[] makeCumulativeTestPoints() {
+        return makeDensityTestPoints();
+    }
+
+    /** Creates the default cumulative probability density test expected values */
+    @Override
+    public double[] makeCumulativeTestValues() {
+        return new double[] {0d, 0.0000d, 0.3414d, 0.5121d, 0.6259d, 0.7113d,
+                0.7796d, 0.8365d, 0.8852d, 0.9279d, 0.9659d, 1d, 1d};
+        }
+
+    /** Creates the default inverse cumulative probability test input values */
+    @Override
+    public double[] makeInverseCumulativeTestPoints() {
+        return new double[] {0, 0.001d, 0.010d, 0.025d, 0.050d, 0.3414d, 0.3415d, 0.999d,
+                0.990d, 0.975d, 0.950d, 0.900d, 1};
+        }
+
+    /** Creates the default inverse cumulative probability density test expected values */
+    @Override
+    public int[] makeInverseCumulativeTestValues() {
+        return new int[] {0, 0, 0, 0, 0, 0, 1, 9, 9, 9, 8, 7, 10};
+    }
+
+    public void testMomonts() {
+        final double tol = 1e-9;
+        ZipfDistributionImpl dist;
+        
+        dist = new ZipfDistributionImpl(2, 0.5);
+        assertEquals(dist.getNumericalMean(), FastMath.sqrt(2), tol);
+        assertEquals(dist.getNumericalVariance(), 0.24264068711928521, tol); 
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/estimation/EstimatedParameterTest.java b/src/test/java/org/apache/commons/math/estimation/EstimatedParameterTest.java
new file mode 100644
index 0000000..553cebf
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/estimation/EstimatedParameterTest.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.estimation;
+
+import org.apache.commons.math.estimation.EstimatedParameter;
+import org.apache.commons.math.util.FastMath;
+
+import junit.framework.*;
+
+ at Deprecated
+public class EstimatedParameterTest
+  extends TestCase {
+
+  public EstimatedParameterTest(String name) {
+    super(name);
+  }
+
+  public void testConstruction() {
+
+    EstimatedParameter p1 = new EstimatedParameter("p1", 1.0);
+    assertTrue(p1.getName().equals("p1"));
+    checkValue(p1.getEstimate(), 1.0);
+    assertTrue(! p1.isBound());
+
+    EstimatedParameter p2 = new EstimatedParameter("p2", 2.0, true);
+    assertTrue(p2.getName().equals("p2"));
+    checkValue(p2.getEstimate(), 2.0);
+    assertTrue(p2.isBound());
+
+  }
+
+  public void testBound() {
+
+    EstimatedParameter p = new EstimatedParameter("p", 0.0);
+    assertTrue(! p.isBound());
+    p.setBound(true);
+    assertTrue(p.isBound());
+    p.setBound(false);
+    assertTrue(! p.isBound());
+
+  }
+
+  public void testEstimate() {
+
+    EstimatedParameter p = new EstimatedParameter("p", 0.0);
+    checkValue(p.getEstimate(), 0.0);
+
+    for (double e = 0.0; e < 10.0; e += 0.5) {
+      p.setEstimate(e);
+      checkValue(p.getEstimate(), e);
+    }
+
+  }
+
+  private void checkValue(double value, double expected) {
+    assertTrue(FastMath.abs(value - expected) < 1.0e-10);
+  }
+
+}
diff --git a/src/test/java/org/apache/commons/math/estimation/GaussNewtonEstimatorTest.java b/src/test/java/org/apache/commons/math/estimation/GaussNewtonEstimatorTest.java
new file mode 100644
index 0000000..a9d9fc1
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/estimation/GaussNewtonEstimatorTest.java
@@ -0,0 +1,724 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.HashSet;
+
+import org.apache.commons.math.util.FastMath;
+
+import junit.framework.TestCase;
+
+/**
+ * <p>Some of the unit tests are re-implementations of the MINPACK <a
+ * href="http://www.netlib.org/minpack/ex/file17">file17</a> and <a
+ * href="http://www.netlib.org/minpack/ex/file22">file22</a> test files.
+ * 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>
+
+ * @author Argonne National Laboratory. MINPACK project. March 1980 (original fortran minpack tests)
+ * @author Burton S. Garbow (original fortran minpack tests)
+ * @author Kenneth E. Hillstrom (original fortran minpack tests)
+ * @author Jorge J. More (original fortran minpack tests)
+ * @author Luc Maisonobe (non-minpack tests and minpack tests Java translation)
+ */
+ at Deprecated
+public class GaussNewtonEstimatorTest
+  extends TestCase {
+
+  public GaussNewtonEstimatorTest(String name) {
+    super(name);
+  }
+
+  public void testTrivial() throws EstimationException {
+    LinearProblem problem =
+      new LinearProblem(new LinearMeasurement[] {
+        new LinearMeasurement(new double[] {2},
+                              new EstimatedParameter[] {
+                                 new EstimatedParameter("p0", 0)
+                              }, 3.0)
+      });
+    GaussNewtonEstimator estimator = new GaussNewtonEstimator(100, 1.0e-6, 1.0e-6);
+    estimator.estimate(problem);
+    assertEquals(0, estimator.getRMS(problem), 1.0e-10);
+    assertEquals(1.5,
+                 problem.getUnboundParameters()[0].getEstimate(),
+                 1.0e-10);
+   }
+
+  public void testQRColumnsPermutation() throws EstimationException {
+
+    EstimatedParameter[] x = {
+       new EstimatedParameter("p0", 0), new EstimatedParameter("p1", 0)
+    };
+    LinearProblem problem = new LinearProblem(new LinearMeasurement[] {
+      new LinearMeasurement(new double[] { 1.0, -1.0 },
+                            new EstimatedParameter[] { x[0], x[1] },
+                            4.0),
+      new LinearMeasurement(new double[] { 2.0 },
+                            new EstimatedParameter[] { x[1] },
+                            6.0),
+      new LinearMeasurement(new double[] { 1.0, -2.0 },
+                            new EstimatedParameter[] { x[0], x[1] },
+                            1.0)
+    });
+
+    GaussNewtonEstimator estimator = new GaussNewtonEstimator(100, 1.0e-6, 1.0e-6);
+    estimator.estimate(problem);
+    assertEquals(0, estimator.getRMS(problem), 1.0e-10);
+    assertEquals(7.0, x[0].getEstimate(), 1.0e-10);
+    assertEquals(3.0, x[1].getEstimate(), 1.0e-10);
+
+  }
+
+  public void testNoDependency() throws EstimationException {
+    EstimatedParameter[] p = new EstimatedParameter[] {
+      new EstimatedParameter("p0", 0),
+      new EstimatedParameter("p1", 0),
+      new EstimatedParameter("p2", 0),
+      new EstimatedParameter("p3", 0),
+      new EstimatedParameter("p4", 0),
+      new EstimatedParameter("p5", 0)
+    };
+    LinearProblem problem = new LinearProblem(new LinearMeasurement[] {
+      new LinearMeasurement(new double[] {2}, new EstimatedParameter[] { p[0] }, 0.0),
+      new LinearMeasurement(new double[] {2}, new EstimatedParameter[] { p[1] }, 1.1),
+      new LinearMeasurement(new double[] {2}, new EstimatedParameter[] { p[2] }, 2.2),
+      new LinearMeasurement(new double[] {2}, new EstimatedParameter[] { p[3] }, 3.3),
+      new LinearMeasurement(new double[] {2}, new EstimatedParameter[] { p[4] }, 4.4),
+      new LinearMeasurement(new double[] {2}, new EstimatedParameter[] { p[5] }, 5.5)
+    });
+  GaussNewtonEstimator estimator = new GaussNewtonEstimator(100, 1.0e-6, 1.0e-6);
+  estimator.estimate(problem);
+  assertEquals(0, estimator.getRMS(problem), 1.0e-10);
+  for (int i = 0; i < p.length; ++i) {
+    assertEquals(0.55 * i, p[i].getEstimate(), 1.0e-10);
+  }
+}
+
+  public void testOneSet() throws EstimationException {
+
+    EstimatedParameter[] p = {
+       new EstimatedParameter("p0", 0),
+       new EstimatedParameter("p1", 0),
+       new EstimatedParameter("p2", 0)
+    };
+    LinearProblem problem = new LinearProblem(new LinearMeasurement[] {
+      new LinearMeasurement(new double[] { 1.0 },
+                            new EstimatedParameter[] { p[0] },
+                            1.0),
+      new LinearMeasurement(new double[] { -1.0, 1.0 },
+                            new EstimatedParameter[] { p[0], p[1] },
+                            1.0),
+      new LinearMeasurement(new double[] { -1.0, 1.0 },
+                            new EstimatedParameter[] { p[1], p[2] },
+                            1.0)
+    });
+
+    GaussNewtonEstimator estimator = new GaussNewtonEstimator(100, 1.0e-6, 1.0e-6);
+    estimator.estimate(problem);
+    assertEquals(0, estimator.getRMS(problem), 1.0e-10);
+    assertEquals(1.0, p[0].getEstimate(), 1.0e-10);
+    assertEquals(2.0, p[1].getEstimate(), 1.0e-10);
+    assertEquals(3.0, p[2].getEstimate(), 1.0e-10);
+
+  }
+
+  public void testTwoSets() throws EstimationException {
+    EstimatedParameter[] p = {
+      new EstimatedParameter("p0", 0),
+      new EstimatedParameter("p1", 1),
+      new EstimatedParameter("p2", 2),
+      new EstimatedParameter("p3", 3),
+      new EstimatedParameter("p4", 4),
+      new EstimatedParameter("p5", 5)
+    };
+
+    double epsilon = 1.0e-7;
+    LinearProblem problem = new LinearProblem(new LinearMeasurement[] {
+
+      // 4 elements sub-problem
+      new LinearMeasurement(new double[] {  2.0,  1.0,  4.0 },
+                            new EstimatedParameter[] { p[0], p[1], p[3] },
+                            2.0),
+      new LinearMeasurement(new double[] { -4.0, -2.0,   3.0, -7.0 },
+                           new EstimatedParameter[] { p[0], p[1], p[2], p[3] },
+                           -9.0),
+      new LinearMeasurement(new double[] {  4.0,  1.0,  -2.0,  8.0 },
+                            new EstimatedParameter[] { p[0], p[1], p[2], p[3] },
+                            2.0),
+      new LinearMeasurement(new double[] { -3.0, -12.0, -1.0 },
+                           new EstimatedParameter[] { p[1], p[2], p[3] },
+                           2.0),
+
+      // 2 elements sub-problem
+      new LinearMeasurement(new double[] { epsilon, 1.0 },
+                            new EstimatedParameter[] { p[4], p[5] },
+                            1.0 + epsilon * epsilon),
+      new LinearMeasurement(new double[] {  1.0, 1.0 },
+                            new EstimatedParameter[] { p[4], p[5] },
+                            2.0)
+
+    });
+
+    GaussNewtonEstimator estimator = new GaussNewtonEstimator(100, 1.0e-6, 1.0e-6);
+    estimator.estimate(problem);
+    assertEquals(0, estimator.getRMS(problem), 1.0e-10);
+    assertEquals( 3.0, p[0].getEstimate(), 1.0e-10);
+    assertEquals( 4.0, p[1].getEstimate(), 1.0e-10);
+    assertEquals(-1.0, p[2].getEstimate(), 1.0e-10);
+    assertEquals(-2.0, p[3].getEstimate(), 1.0e-10);
+    assertEquals( 1.0 + epsilon, p[4].getEstimate(), 1.0e-10);
+    assertEquals( 1.0 - epsilon, p[5].getEstimate(), 1.0e-10);
+
+  }
+
+  public void testNonInversible() {
+
+    EstimatedParameter[] p = {
+       new EstimatedParameter("p0", 0),
+       new EstimatedParameter("p1", 0),
+       new EstimatedParameter("p2", 0)
+    };
+    LinearMeasurement[] m = new LinearMeasurement[] {
+      new LinearMeasurement(new double[] {  1.0, 2.0, -3.0 },
+                            new EstimatedParameter[] { p[0], p[1], p[2] },
+                            1.0),
+      new LinearMeasurement(new double[] {  2.0, 1.0,  3.0 },
+                            new EstimatedParameter[] { p[0], p[1], p[2] },
+                            1.0),
+      new LinearMeasurement(new double[] { -3.0, -9.0 },
+                            new EstimatedParameter[] { p[0], p[2] },
+                            1.0)
+    };
+    LinearProblem problem = new LinearProblem(m);
+
+    GaussNewtonEstimator estimator = new GaussNewtonEstimator(100, 1.0e-6, 1.0e-6);
+    try {
+      estimator.estimate(problem);
+      fail("an exception should have been caught");
+    } catch (EstimationException ee) {
+      // expected behavior
+    }
+  }
+
+  public void testIllConditioned() throws EstimationException {
+    EstimatedParameter[] p = {
+      new EstimatedParameter("p0", 0),
+      new EstimatedParameter("p1", 1),
+      new EstimatedParameter("p2", 2),
+      new EstimatedParameter("p3", 3)
+    };
+
+    LinearProblem problem1 = new LinearProblem(new LinearMeasurement[] {
+      new LinearMeasurement(new double[] { 10.0, 7.0,  8.0,  7.0 },
+                            new EstimatedParameter[] { p[0], p[1], p[2], p[3] },
+                            32.0),
+      new LinearMeasurement(new double[] {  7.0, 5.0,  6.0,  5.0 },
+                            new EstimatedParameter[] { p[0], p[1], p[2], p[3] },
+                            23.0),
+      new LinearMeasurement(new double[] {  8.0, 6.0, 10.0,  9.0 },
+                            new EstimatedParameter[] { p[0], p[1], p[2], p[3] },
+                            33.0),
+      new LinearMeasurement(new double[] {  7.0, 5.0,  9.0, 10.0 },
+                            new EstimatedParameter[] { p[0], p[1], p[2], p[3] },
+                            31.0)
+    });
+    GaussNewtonEstimator estimator1 = new GaussNewtonEstimator(100, 1.0e-6, 1.0e-6);
+    estimator1.estimate(problem1);
+    assertEquals(0, estimator1.getRMS(problem1), 1.0e-10);
+    assertEquals(1.0, p[0].getEstimate(), 1.0e-10);
+    assertEquals(1.0, p[1].getEstimate(), 1.0e-10);
+    assertEquals(1.0, p[2].getEstimate(), 1.0e-10);
+    assertEquals(1.0, p[3].getEstimate(), 1.0e-10);
+
+    LinearProblem problem2 = new LinearProblem(new LinearMeasurement[] {
+      new LinearMeasurement(new double[] { 10.0, 7.0,  8.1,  7.2 },
+                            new EstimatedParameter[] { p[0], p[1], p[2], p[3] },
+                            32.0),
+      new LinearMeasurement(new double[] {  7.08, 5.04,  6.0,  5.0 },
+                            new EstimatedParameter[] { p[0], p[1], p[2], p[3] },
+                            23.0),
+      new LinearMeasurement(new double[] {  8.0, 5.98, 9.89,  9.0 },
+                             new EstimatedParameter[] { p[0], p[1], p[2], p[3] },
+                            33.0),
+      new LinearMeasurement(new double[] {  6.99, 4.99,  9.0, 9.98 },
+                             new EstimatedParameter[] { p[0], p[1], p[2], p[3] },
+                            31.0)
+    });
+    GaussNewtonEstimator estimator2 = new GaussNewtonEstimator(100, 1.0e-6, 1.0e-6);
+    estimator2.estimate(problem2);
+    assertEquals(0, estimator2.getRMS(problem2), 1.0e-10);
+    assertEquals(-81.0, p[0].getEstimate(), 1.0e-8);
+    assertEquals(137.0, p[1].getEstimate(), 1.0e-8);
+    assertEquals(-34.0, p[2].getEstimate(), 1.0e-8);
+    assertEquals( 22.0, p[3].getEstimate(), 1.0e-8);
+
+  }
+
+  public void testMoreEstimatedParametersSimple() {
+
+    EstimatedParameter[] p = {
+       new EstimatedParameter("p0", 7),
+       new EstimatedParameter("p1", 6),
+       new EstimatedParameter("p2", 5),
+       new EstimatedParameter("p3", 4)
+     };
+    LinearProblem problem = new LinearProblem(new LinearMeasurement[] {
+      new LinearMeasurement(new double[] { 3.0, 2.0 },
+                             new EstimatedParameter[] { p[0], p[1] },
+                             7.0),
+      new LinearMeasurement(new double[] { 1.0, -1.0, 1.0 },
+                             new EstimatedParameter[] { p[1], p[2], p[3] },
+                             3.0),
+      new LinearMeasurement(new double[] { 2.0, 1.0 },
+                             new EstimatedParameter[] { p[0], p[2] },
+                             5.0)
+    });
+
+    GaussNewtonEstimator estimator = new GaussNewtonEstimator(100, 1.0e-6, 1.0e-6);
+    try {
+        estimator.estimate(problem);
+        fail("an exception should have been caught");
+    } catch (EstimationException ee) {
+        // expected behavior
+    }
+
+  }
+
+  public void testMoreEstimatedParametersUnsorted() {
+    EstimatedParameter[] p = {
+      new EstimatedParameter("p0", 2),
+      new EstimatedParameter("p1", 2),
+      new EstimatedParameter("p2", 2),
+      new EstimatedParameter("p3", 2),
+      new EstimatedParameter("p4", 2),
+      new EstimatedParameter("p5", 2)
+    };
+    LinearProblem problem = new LinearProblem(new LinearMeasurement[] {
+      new LinearMeasurement(new double[] { 1.0, 1.0 },
+                           new EstimatedParameter[] { p[0], p[1] },
+                           3.0),
+      new LinearMeasurement(new double[] { 1.0, 1.0, 1.0 },
+                           new EstimatedParameter[] { p[2], p[3], p[4] },
+                           12.0),
+      new LinearMeasurement(new double[] { 1.0, -1.0 },
+                           new EstimatedParameter[] { p[4], p[5] },
+                           -1.0),
+      new LinearMeasurement(new double[] { 1.0, -1.0, 1.0 },
+                           new EstimatedParameter[] { p[3], p[2], p[5] },
+                           7.0),
+      new LinearMeasurement(new double[] { 1.0, -1.0 },
+                           new EstimatedParameter[] { p[4], p[3] },
+                           1.0)
+    });
+
+    GaussNewtonEstimator estimator = new GaussNewtonEstimator(100, 1.0e-6, 1.0e-6);
+    try {
+        estimator.estimate(problem);
+        fail("an exception should have been caught");
+    } catch (EstimationException ee) {
+        // expected behavior
+    }
+
+  }
+
+  public void testRedundantEquations() throws EstimationException {
+    EstimatedParameter[] p = {
+      new EstimatedParameter("p0", 1),
+      new EstimatedParameter("p1", 1)
+    };
+    LinearProblem problem = new LinearProblem(new LinearMeasurement[] {
+      new LinearMeasurement(new double[] { 1.0, 1.0 },
+                             new EstimatedParameter[] { p[0], p[1] },
+                             3.0),
+      new LinearMeasurement(new double[] { 1.0, -1.0 },
+                             new EstimatedParameter[] { p[0], p[1] },
+                             1.0),
+      new LinearMeasurement(new double[] { 1.0, 3.0 },
+                             new EstimatedParameter[] { p[0], p[1] },
+                             5.0)
+    });
+
+    GaussNewtonEstimator estimator = new GaussNewtonEstimator(100, 1.0e-6, 1.0e-6);
+    estimator.estimate(problem);
+    assertEquals(0, estimator.getRMS(problem), 1.0e-10);
+    EstimatedParameter[] all = problem.getAllParameters();
+    for (int i = 0; i < all.length; ++i) {
+        assertEquals(all[i].getName().equals("p0") ? 2.0 : 1.0,
+                     all[i].getEstimate(), 1.0e-10);
+    }
+
+  }
+
+  public void testInconsistentEquations() throws EstimationException {
+    EstimatedParameter[] p = {
+      new EstimatedParameter("p0", 1),
+      new EstimatedParameter("p1", 1)
+    };
+    LinearProblem problem = new LinearProblem(new LinearMeasurement[] {
+      new LinearMeasurement(new double[] { 1.0, 1.0 },
+                            new EstimatedParameter[] { p[0], p[1] },
+                            3.0),
+      new LinearMeasurement(new double[] { 1.0, -1.0 },
+                            new EstimatedParameter[] { p[0], p[1] },
+                            1.0),
+      new LinearMeasurement(new double[] { 1.0, 3.0 },
+                            new EstimatedParameter[] { p[0], p[1] },
+                            4.0)
+    });
+
+    GaussNewtonEstimator estimator = new GaussNewtonEstimator(100, 1.0e-6, 1.0e-6);
+    estimator.estimate(problem);
+    assertTrue(estimator.getRMS(problem) > 0.1);
+
+  }
+
+  public void testBoundParameters() throws EstimationException {
+      EstimatedParameter[] p = {
+        new EstimatedParameter("unbound0", 2, false),
+        new EstimatedParameter("unbound1", 2, false),
+        new EstimatedParameter("bound",    2, true)
+      };
+      LinearProblem problem = new LinearProblem(new LinearMeasurement[] {
+        new LinearMeasurement(new double[] { 1.0, 1.0, 1.0 },
+                              new EstimatedParameter[] { p[0], p[1], p[2] },
+                              3.0),
+        new LinearMeasurement(new double[] { 1.0, -1.0, 1.0 },
+                              new EstimatedParameter[] { p[0], p[1], p[2] },
+                              1.0),
+        new LinearMeasurement(new double[] { 1.0, 3.0, 2.0 },
+                              new EstimatedParameter[] { p[0], p[1], p[2] },
+                              7.0)
+      });
+
+      GaussNewtonEstimator estimator = new GaussNewtonEstimator(100, 1.0e-6, 1.0e-6);
+      estimator.estimate(problem);
+      assertTrue(estimator.getRMS(problem) < 1.0e-10);
+      double[][] covariances = estimator.getCovariances(problem);
+      int i0 = 0, i1 = 1;
+      if (problem.getUnboundParameters()[0].getName().endsWith("1")) {
+          i0 = 1;
+          i1 = 0;
+      }
+      assertEquals(11.0 / 24, covariances[i0][i0], 1.0e-10);
+      assertEquals(-3.0 / 24, covariances[i0][i1], 1.0e-10);
+      assertEquals(-3.0 / 24, covariances[i1][i0], 1.0e-10);
+      assertEquals( 3.0 / 24, covariances[i1][i1], 1.0e-10);
+
+      double[] errors = estimator.guessParametersErrors(problem);
+      assertEquals(0, errors[i0], 1.0e-10);
+      assertEquals(0, errors[i1], 1.0e-10);
+
+  }
+
+  public void testMaxIterations() {
+      Circle circle = new Circle(98.680, 47.345);
+      circle.addPoint( 30.0,  68.0);
+      circle.addPoint( 50.0,  -6.0);
+      circle.addPoint(110.0, -20.0);
+      circle.addPoint( 35.0,  15.0);
+      circle.addPoint( 45.0,  97.0);
+      try {
+        GaussNewtonEstimator estimator = new GaussNewtonEstimator(4, 1.0e-14, 1.0e-14);
+        estimator.estimate(circle);
+        fail("an exception should have been caught");
+      } catch (EstimationException ee) {
+        // expected behavior
+      }
+    }
+
+  public void testCircleFitting() throws EstimationException {
+      Circle circle = new Circle(98.680, 47.345);
+      circle.addPoint( 30.0,  68.0);
+      circle.addPoint( 50.0,  -6.0);
+      circle.addPoint(110.0, -20.0);
+      circle.addPoint( 35.0,  15.0);
+      circle.addPoint( 45.0,  97.0);
+      GaussNewtonEstimator estimator = new GaussNewtonEstimator(100, 1.0e-10, 1.0e-10);
+      estimator.estimate(circle);
+      double rms = estimator.getRMS(circle);
+      assertEquals(1.768262623567235,  FastMath.sqrt(circle.getM()) * rms,  1.0e-10);
+      assertEquals(69.96016176931406, circle.getRadius(), 1.0e-10);
+      assertEquals(96.07590211815305, circle.getX(),      1.0e-10);
+      assertEquals(48.13516790438953, circle.getY(),      1.0e-10);
+    }
+
+  public void testCircleFittingBadInit() {
+    Circle circle = new Circle(-12, -12);
+    double[][] points = new double[][] {
+      {-0.312967,  0.072366}, {-0.339248,  0.132965}, {-0.379780,  0.202724},
+      {-0.390426,  0.260487}, {-0.361212,  0.328325}, {-0.346039,  0.392619},
+      {-0.280579,  0.444306}, {-0.216035,  0.470009}, {-0.149127,  0.493832},
+      {-0.075133,  0.483271}, {-0.007759,  0.452680}, { 0.060071,  0.410235},
+      { 0.103037,  0.341076}, { 0.118438,  0.273884}, { 0.131293,  0.192201},
+      { 0.115869,  0.129797}, { 0.072223,  0.058396}, { 0.022884,  0.000718},
+      {-0.053355, -0.020405}, {-0.123584, -0.032451}, {-0.216248, -0.032862},
+      {-0.278592, -0.005008}, {-0.337655,  0.056658}, {-0.385899,  0.112526},
+      {-0.405517,  0.186957}, {-0.415374,  0.262071}, {-0.387482,  0.343398},
+      {-0.347322,  0.397943}, {-0.287623,  0.458425}, {-0.223502,  0.475513},
+      {-0.135352,  0.478186}, {-0.061221,  0.483371}, { 0.003711,  0.422737},
+      { 0.065054,  0.375830}, { 0.108108,  0.297099}, { 0.123882,  0.222850},
+      { 0.117729,  0.134382}, { 0.085195,  0.056820}, { 0.029800, -0.019138},
+      {-0.027520, -0.072374}, {-0.102268, -0.091555}, {-0.200299, -0.106578},
+      {-0.292731, -0.091473}, {-0.356288, -0.051108}, {-0.420561,  0.014926},
+      {-0.471036,  0.074716}, {-0.488638,  0.182508}, {-0.485990,  0.254068},
+      {-0.463943,  0.338438}, {-0.406453,  0.404704}, {-0.334287,  0.466119},
+      {-0.254244,  0.503188}, {-0.161548,  0.495769}, {-0.075733,  0.495560},
+      { 0.001375,  0.434937}, { 0.082787,  0.385806}, { 0.115490,  0.323807},
+      { 0.141089,  0.223450}, { 0.138693,  0.131703}, { 0.126415,  0.049174},
+      { 0.066518, -0.010217}, {-0.005184, -0.070647}, {-0.080985, -0.103635},
+      {-0.177377, -0.116887}, {-0.260628, -0.100258}, {-0.335756, -0.056251},
+      {-0.405195, -0.000895}, {-0.444937,  0.085456}, {-0.484357,  0.175597},
+      {-0.472453,  0.248681}, {-0.438580,  0.347463}, {-0.402304,  0.422428},
+      {-0.326777,  0.479438}, {-0.247797,  0.505581}, {-0.152676,  0.519380},
+      {-0.071754,  0.516264}, { 0.015942,  0.472802}, { 0.076608,  0.419077},
+      { 0.127673,  0.330264}, { 0.159951,  0.262150}, { 0.153530,  0.172681},
+      { 0.140653,  0.089229}, { 0.078666,  0.024981}, { 0.023807, -0.037022},
+      {-0.048837, -0.077056}, {-0.127729, -0.075338}, {-0.221271, -0.067526}
+    };
+    for (int i = 0; i < points.length; ++i) {
+      circle.addPoint(points[i][0], points[i][1]);
+    }
+    GaussNewtonEstimator estimator = new GaussNewtonEstimator(100, 1.0e-6, 1.0e-6);
+    try {
+        estimator.estimate(circle);
+        fail("an exception should have been caught");
+    } catch (EstimationException ee) {
+        // expected behavior
+    }
+}
+
+  private static class LinearProblem extends SimpleEstimationProblem {
+
+    public LinearProblem(LinearMeasurement[] measurements) {
+      HashSet<EstimatedParameter> set = new HashSet<EstimatedParameter>();
+      for (int i = 0; i < measurements.length; ++i) {
+        addMeasurement(measurements[i]);
+        EstimatedParameter[] parameters = measurements[i].getParameters();
+        for (int j = 0; j < parameters.length; ++j) {
+          set.add(parameters[j]);
+        }
+      }
+      for (EstimatedParameter p : set) {
+        addParameter(p);
+      }
+    }
+
+  }
+
+  private static class LinearMeasurement extends WeightedMeasurement {
+
+    public LinearMeasurement(double[] factors, EstimatedParameter[] parameters,
+                             double setPoint) {
+      super(1.0, setPoint, true);
+      this.factors = factors;
+      this.parameters = parameters;
+      setIgnored(false);
+    }
+
+    @Override
+    public double getTheoreticalValue() {
+      double v = 0;
+      for (int i = 0; i < factors.length; ++i) {
+        v += factors[i] * parameters[i].getEstimate();
+      }
+      return v;
+    }
+
+    @Override
+    public double getPartial(EstimatedParameter parameter) {
+      for (int i = 0; i < parameters.length; ++i) {
+        if (parameters[i] == parameter) {
+          return factors[i];
+        }
+      }
+      return 0;
+    }
+
+    public EstimatedParameter[] getParameters() {
+      return parameters;
+    }
+
+    private double[] factors;
+    private EstimatedParameter[] parameters;
+    private static final long serialVersionUID = -3922448707008868580L;
+
+  }
+
+  private static class Circle implements EstimationProblem {
+
+    public Circle(double cx, double cy) {
+      this.cx = new EstimatedParameter("cx", cx);
+      this.cy = new EstimatedParameter(new EstimatedParameter("cy", cy));
+      points  = new ArrayList<PointModel>();
+    }
+
+    public void addPoint(double px, double py) {
+      points.add(new PointModel(this, px, py));
+    }
+
+    public int getM() {
+      return points.size();
+    }
+
+    public WeightedMeasurement[] getMeasurements() {
+      return points.toArray(new PointModel[points.size()]);
+    }
+
+    public EstimatedParameter[] getAllParameters() {
+      return new EstimatedParameter[] { cx, cy };
+    }
+
+    public EstimatedParameter[] getUnboundParameters() {
+      return new EstimatedParameter[] { cx, cy };
+    }
+
+    public double getPartialRadiusX() {
+      double dRdX = 0;
+      for (PointModel point : points) {
+        dRdX += point.getPartialDiX();
+      }
+      return dRdX / points.size();
+    }
+
+    public double getPartialRadiusY() {
+      double dRdY = 0;
+      for (PointModel point : points) {
+        dRdY += point.getPartialDiY();
+      }
+      return dRdY / points.size();
+    }
+
+   public double getRadius() {
+      double r = 0;
+      for (PointModel point : points) {
+        r += point.getCenterDistance();
+      }
+      return r / points.size();
+    }
+
+    public double getX() {
+      return cx.getEstimate();
+    }
+
+    public double getY() {
+      return cy.getEstimate();
+    }
+
+    private static class PointModel extends WeightedMeasurement {
+
+      public PointModel(Circle circle, double px, double py) {
+        super(1.0, 0.0);
+        this.px = px;
+        this.py = py;
+        this.circle = circle;
+      }
+
+      @Override
+      public double getPartial(EstimatedParameter parameter) {
+        if (parameter == circle.cx) {
+          return getPartialDiX() - circle.getPartialRadiusX();
+        } else if (parameter == circle.cy) {
+          return getPartialDiY() - circle.getPartialRadiusY();
+        }
+        return 0;
+      }
+
+      public double getCenterDistance() {
+        double dx = px - circle.cx.getEstimate();
+        double dy = py - circle.cy.getEstimate();
+        return FastMath.sqrt(dx * dx + dy * dy);
+      }
+
+      public double getPartialDiX() {
+        return (circle.cx.getEstimate() - px) / getCenterDistance();
+      }
+
+      public double getPartialDiY() {
+        return (circle.cy.getEstimate() - py) / getCenterDistance();
+      }
+
+      @Override
+      public double getTheoreticalValue() {
+        return getCenterDistance() - circle.getRadius();
+      }
+
+      private double px;
+      private double py;
+      private transient final Circle circle;
+      private static final long serialVersionUID = 1L;
+
+    }
+
+    private EstimatedParameter cx;
+    private EstimatedParameter cy;
+    private ArrayList<PointModel> points;
+
+  }
+
+}
diff --git a/src/test/java/org/apache/commons/math/estimation/LevenbergMarquardtEstimatorTest.java b/src/test/java/org/apache/commons/math/estimation/LevenbergMarquardtEstimatorTest.java
new file mode 100644
index 0000000..676e2cd
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/estimation/LevenbergMarquardtEstimatorTest.java
@@ -0,0 +1,832 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.HashSet;
+
+import org.apache.commons.math.util.FastMath;
+
+import junit.framework.TestCase;
+
+/**
+ * <p>Some of the unit tests are re-implementations of the MINPACK <a
+ * href="http://www.netlib.org/minpack/ex/file17">file17</a> and <a
+ * href="http://www.netlib.org/minpack/ex/file22">file22</a> test files.
+ * 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>
+
+ * @author Argonne National Laboratory. MINPACK project. March 1980 (original fortran minpack tests)
+ * @author Burton S. Garbow (original fortran minpack tests)
+ * @author Kenneth E. Hillstrom (original fortran minpack tests)
+ * @author Jorge J. More (original fortran minpack tests)
+ * @author Luc Maisonobe (non-minpack tests and minpack tests Java translation)
+ */
+ at Deprecated
+public class LevenbergMarquardtEstimatorTest
+  extends TestCase {
+
+  public LevenbergMarquardtEstimatorTest(String name) {
+    super(name);
+  }
+
+  public void testTrivial() throws EstimationException {
+    LinearProblem problem =
+      new LinearProblem(new LinearMeasurement[] {
+        new LinearMeasurement(new double[] {2},
+                              new EstimatedParameter[] {
+                                 new EstimatedParameter("p0", 0)
+                              }, 3.0)
+      });
+    LevenbergMarquardtEstimator estimator = new LevenbergMarquardtEstimator();
+    estimator.estimate(problem);
+    assertEquals(0, estimator.getRMS(problem), 1.0e-10);
+    try {
+        estimator.guessParametersErrors(problem);
+        fail("an exception should have been thrown");
+    } catch (EstimationException ee) {
+        // expected behavior
+    }
+    assertEquals(1.5,
+                 problem.getUnboundParameters()[0].getEstimate(),
+                 1.0e-10);
+   }
+
+  public void testQRColumnsPermutation() throws EstimationException {
+
+    EstimatedParameter[] x = {
+       new EstimatedParameter("p0", 0), new EstimatedParameter("p1", 0)
+    };
+    LinearProblem problem = new LinearProblem(new LinearMeasurement[] {
+      new LinearMeasurement(new double[] { 1.0, -1.0 },
+                            new EstimatedParameter[] { x[0], x[1] },
+                            4.0),
+      new LinearMeasurement(new double[] { 2.0 },
+                            new EstimatedParameter[] { x[1] },
+                            6.0),
+      new LinearMeasurement(new double[] { 1.0, -2.0 },
+                            new EstimatedParameter[] { x[0], x[1] },
+                            1.0)
+    });
+
+    LevenbergMarquardtEstimator estimator = new LevenbergMarquardtEstimator();
+    estimator.estimate(problem);
+    assertEquals(0, estimator.getRMS(problem), 1.0e-10);
+    assertEquals(7.0, x[0].getEstimate(), 1.0e-10);
+    assertEquals(3.0, x[1].getEstimate(), 1.0e-10);
+
+  }
+
+  public void testNoDependency() throws EstimationException {
+    EstimatedParameter[] p = new EstimatedParameter[] {
+      new EstimatedParameter("p0", 0),
+      new EstimatedParameter("p1", 0),
+      new EstimatedParameter("p2", 0),
+      new EstimatedParameter("p3", 0),
+      new EstimatedParameter("p4", 0),
+      new EstimatedParameter("p5", 0)
+    };
+    LinearProblem problem = new LinearProblem(new LinearMeasurement[] {
+      new LinearMeasurement(new double[] {2}, new EstimatedParameter[] { p[0] }, 0.0),
+      new LinearMeasurement(new double[] {2}, new EstimatedParameter[] { p[1] }, 1.1),
+      new LinearMeasurement(new double[] {2}, new EstimatedParameter[] { p[2] }, 2.2),
+      new LinearMeasurement(new double[] {2}, new EstimatedParameter[] { p[3] }, 3.3),
+      new LinearMeasurement(new double[] {2}, new EstimatedParameter[] { p[4] }, 4.4),
+      new LinearMeasurement(new double[] {2}, new EstimatedParameter[] { p[5] }, 5.5)
+    });
+  LevenbergMarquardtEstimator estimator = new LevenbergMarquardtEstimator();
+  estimator.estimate(problem);
+  assertEquals(0, estimator.getRMS(problem), 1.0e-10);
+  for (int i = 0; i < p.length; ++i) {
+    assertEquals(0.55 * i, p[i].getEstimate(), 1.0e-10);
+  }
+}
+
+  public void testOneSet() throws EstimationException {
+
+    EstimatedParameter[] p = {
+       new EstimatedParameter("p0", 0),
+       new EstimatedParameter("p1", 0),
+       new EstimatedParameter("p2", 0)
+    };
+    LinearProblem problem = new LinearProblem(new LinearMeasurement[] {
+      new LinearMeasurement(new double[] { 1.0 },
+                            new EstimatedParameter[] { p[0] },
+                            1.0),
+      new LinearMeasurement(new double[] { -1.0, 1.0 },
+                            new EstimatedParameter[] { p[0], p[1] },
+                            1.0),
+      new LinearMeasurement(new double[] { -1.0, 1.0 },
+                            new EstimatedParameter[] { p[1], p[2] },
+                            1.0)
+    });
+
+    LevenbergMarquardtEstimator estimator = new LevenbergMarquardtEstimator();
+    estimator.estimate(problem);
+    assertEquals(0, estimator.getRMS(problem), 1.0e-10);
+    assertEquals(1.0, p[0].getEstimate(), 1.0e-10);
+    assertEquals(2.0, p[1].getEstimate(), 1.0e-10);
+    assertEquals(3.0, p[2].getEstimate(), 1.0e-10);
+
+  }
+
+  public void testTwoSets() throws EstimationException {
+    EstimatedParameter[] p = {
+      new EstimatedParameter("p0", 0),
+      new EstimatedParameter("p1", 1),
+      new EstimatedParameter("p2", 2),
+      new EstimatedParameter("p3", 3),
+      new EstimatedParameter("p4", 4),
+      new EstimatedParameter("p5", 5)
+    };
+
+    double epsilon = 1.0e-7;
+    LinearProblem problem = new LinearProblem(new LinearMeasurement[] {
+
+      // 4 elements sub-problem
+      new LinearMeasurement(new double[] {  2.0,  1.0,  4.0 },
+                            new EstimatedParameter[] { p[0], p[1], p[3] },
+                            2.0),
+      new LinearMeasurement(new double[] { -4.0, -2.0,   3.0, -7.0 },
+                           new EstimatedParameter[] { p[0], p[1], p[2], p[3] },
+                           -9.0),
+      new LinearMeasurement(new double[] {  4.0,  1.0,  -2.0,  8.0 },
+                            new EstimatedParameter[] { p[0], p[1], p[2], p[3] },
+                            2.0),
+      new LinearMeasurement(new double[] { -3.0, -12.0, -1.0 },
+                           new EstimatedParameter[] { p[1], p[2], p[3] },
+                           2.0),
+
+      // 2 elements sub-problem
+      new LinearMeasurement(new double[] { epsilon, 1.0 },
+                            new EstimatedParameter[] { p[4], p[5] },
+                            1.0 + epsilon * epsilon),
+      new LinearMeasurement(new double[] {  1.0, 1.0 },
+                            new EstimatedParameter[] { p[4], p[5] },
+                            2.0)
+
+    });
+
+    LevenbergMarquardtEstimator estimator = new LevenbergMarquardtEstimator();
+    estimator.estimate(problem);
+    assertEquals(0, estimator.getRMS(problem), 1.0e-10);
+    assertEquals( 3.0, p[0].getEstimate(), 1.0e-10);
+    assertEquals( 4.0, p[1].getEstimate(), 1.0e-10);
+    assertEquals(-1.0, p[2].getEstimate(), 1.0e-10);
+    assertEquals(-2.0, p[3].getEstimate(), 1.0e-10);
+    assertEquals( 1.0 + epsilon, p[4].getEstimate(), 1.0e-10);
+    assertEquals( 1.0 - epsilon, p[5].getEstimate(), 1.0e-10);
+
+  }
+
+  public void testNonInversible() throws EstimationException {
+
+    EstimatedParameter[] p = {
+       new EstimatedParameter("p0", 0),
+       new EstimatedParameter("p1", 0),
+       new EstimatedParameter("p2", 0)
+    };
+    LinearMeasurement[] m = new LinearMeasurement[] {
+      new LinearMeasurement(new double[] {  1.0, 2.0, -3.0 },
+                            new EstimatedParameter[] { p[0], p[1], p[2] },
+                            1.0),
+      new LinearMeasurement(new double[] {  2.0, 1.0,  3.0 },
+                            new EstimatedParameter[] { p[0], p[1], p[2] },
+                            1.0),
+      new LinearMeasurement(new double[] { -3.0, -9.0 },
+                            new EstimatedParameter[] { p[0], p[2] },
+                            1.0)
+    };
+    LinearProblem problem = new LinearProblem(m);
+
+    LevenbergMarquardtEstimator estimator = new LevenbergMarquardtEstimator();
+    double initialCost = estimator.getRMS(problem);
+    estimator.estimate(problem);
+    assertTrue(estimator.getRMS(problem) < initialCost);
+    assertTrue(FastMath.sqrt(m.length) * estimator.getRMS(problem) > 0.6);
+    try {
+        estimator.getCovariances(problem);
+        fail("an exception should have been thrown");
+    } catch (EstimationException ee) {
+        // expected behavior
+    }
+   double dJ0 = 2 * (m[0].getResidual() * m[0].getPartial(p[0])
+                    + m[1].getResidual() * m[1].getPartial(p[0])
+                    + m[2].getResidual() * m[2].getPartial(p[0]));
+    double dJ1 = 2 * (m[0].getResidual() * m[0].getPartial(p[1])
+                    + m[1].getResidual() * m[1].getPartial(p[1]));
+    double dJ2 = 2 * (m[0].getResidual() * m[0].getPartial(p[2])
+                    + m[1].getResidual() * m[1].getPartial(p[2])
+                    + m[2].getResidual() * m[2].getPartial(p[2]));
+    assertEquals(0, dJ0, 1.0e-10);
+    assertEquals(0, dJ1, 1.0e-10);
+    assertEquals(0, dJ2, 1.0e-10);
+
+  }
+
+  public void testIllConditioned() throws EstimationException {
+    EstimatedParameter[] p = {
+      new EstimatedParameter("p0", 0),
+      new EstimatedParameter("p1", 1),
+      new EstimatedParameter("p2", 2),
+      new EstimatedParameter("p3", 3)
+    };
+
+    LinearProblem problem1 = new LinearProblem(new LinearMeasurement[] {
+      new LinearMeasurement(new double[] { 10.0, 7.0,  8.0,  7.0 },
+                            new EstimatedParameter[] { p[0], p[1], p[2], p[3] },
+                            32.0),
+      new LinearMeasurement(new double[] {  7.0, 5.0,  6.0,  5.0 },
+                            new EstimatedParameter[] { p[0], p[1], p[2], p[3] },
+                            23.0),
+      new LinearMeasurement(new double[] {  8.0, 6.0, 10.0,  9.0 },
+                            new EstimatedParameter[] { p[0], p[1], p[2], p[3] },
+                            33.0),
+      new LinearMeasurement(new double[] {  7.0, 5.0,  9.0, 10.0 },
+                            new EstimatedParameter[] { p[0], p[1], p[2], p[3] },
+                            31.0)
+    });
+    LevenbergMarquardtEstimator estimator1 = new LevenbergMarquardtEstimator();
+    estimator1.estimate(problem1);
+    assertEquals(0, estimator1.getRMS(problem1), 1.0e-10);
+    assertEquals(1.0, p[0].getEstimate(), 1.0e-10);
+    assertEquals(1.0, p[1].getEstimate(), 1.0e-10);
+    assertEquals(1.0, p[2].getEstimate(), 1.0e-10);
+    assertEquals(1.0, p[3].getEstimate(), 1.0e-10);
+
+    LinearProblem problem2 = new LinearProblem(new LinearMeasurement[] {
+      new LinearMeasurement(new double[] { 10.0, 7.0,  8.1,  7.2 },
+                            new EstimatedParameter[] { p[0], p[1], p[2], p[3] },
+                            32.0),
+      new LinearMeasurement(new double[] {  7.08, 5.04,  6.0,  5.0 },
+                            new EstimatedParameter[] { p[0], p[1], p[2], p[3] },
+                            23.0),
+      new LinearMeasurement(new double[] {  8.0, 5.98, 9.89,  9.0 },
+                             new EstimatedParameter[] { p[0], p[1], p[2], p[3] },
+                            33.0),
+      new LinearMeasurement(new double[] {  6.99, 4.99,  9.0, 9.98 },
+                             new EstimatedParameter[] { p[0], p[1], p[2], p[3] },
+                            31.0)
+    });
+    LevenbergMarquardtEstimator estimator2 = new LevenbergMarquardtEstimator();
+    estimator2.estimate(problem2);
+    assertEquals(0, estimator2.getRMS(problem2), 1.0e-10);
+    assertEquals(-81.0, p[0].getEstimate(), 1.0e-8);
+    assertEquals(137.0, p[1].getEstimate(), 1.0e-8);
+    assertEquals(-34.0, p[2].getEstimate(), 1.0e-8);
+    assertEquals( 22.0, p[3].getEstimate(), 1.0e-8);
+
+  }
+
+  public void testMoreEstimatedParametersSimple() throws EstimationException {
+
+    EstimatedParameter[] p = {
+       new EstimatedParameter("p0", 7),
+       new EstimatedParameter("p1", 6),
+       new EstimatedParameter("p2", 5),
+       new EstimatedParameter("p3", 4)
+     };
+    LinearProblem problem = new LinearProblem(new LinearMeasurement[] {
+      new LinearMeasurement(new double[] { 3.0, 2.0 },
+                             new EstimatedParameter[] { p[0], p[1] },
+                             7.0),
+      new LinearMeasurement(new double[] { 1.0, -1.0, 1.0 },
+                             new EstimatedParameter[] { p[1], p[2], p[3] },
+                             3.0),
+      new LinearMeasurement(new double[] { 2.0, 1.0 },
+                             new EstimatedParameter[] { p[0], p[2] },
+                             5.0)
+    });
+
+    LevenbergMarquardtEstimator estimator = new LevenbergMarquardtEstimator();
+    estimator.estimate(problem);
+    assertEquals(0, estimator.getRMS(problem), 1.0e-10);
+
+  }
+
+  public void testMoreEstimatedParametersUnsorted() throws EstimationException {
+    EstimatedParameter[] p = {
+      new EstimatedParameter("p0", 2),
+      new EstimatedParameter("p1", 2),
+      new EstimatedParameter("p2", 2),
+      new EstimatedParameter("p3", 2),
+      new EstimatedParameter("p4", 2),
+      new EstimatedParameter("p5", 2)
+    };
+    LinearProblem problem = new LinearProblem(new LinearMeasurement[] {
+      new LinearMeasurement(new double[] { 1.0, 1.0 },
+                           new EstimatedParameter[] { p[0], p[1] },
+                           3.0),
+      new LinearMeasurement(new double[] { 1.0, 1.0, 1.0 },
+                           new EstimatedParameter[] { p[2], p[3], p[4] },
+                           12.0),
+      new LinearMeasurement(new double[] { 1.0, -1.0 },
+                           new EstimatedParameter[] { p[4], p[5] },
+                           -1.0),
+      new LinearMeasurement(new double[] { 1.0, -1.0, 1.0 },
+                           new EstimatedParameter[] { p[3], p[2], p[5] },
+                           7.0),
+      new LinearMeasurement(new double[] { 1.0, -1.0 },
+                           new EstimatedParameter[] { p[4], p[3] },
+                           1.0)
+    });
+
+    LevenbergMarquardtEstimator estimator = new LevenbergMarquardtEstimator();
+    estimator.estimate(problem);
+    assertEquals(0, estimator.getRMS(problem), 1.0e-10);
+    assertEquals(3.0, p[2].getEstimate(), 1.0e-10);
+    assertEquals(4.0, p[3].getEstimate(), 1.0e-10);
+    assertEquals(5.0, p[4].getEstimate(), 1.0e-10);
+    assertEquals(6.0, p[5].getEstimate(), 1.0e-10);
+
+  }
+
+  public void testRedundantEquations() throws EstimationException {
+    EstimatedParameter[] p = {
+      new EstimatedParameter("p0", 1),
+      new EstimatedParameter("p1", 1)
+    };
+    LinearProblem problem = new LinearProblem(new LinearMeasurement[] {
+      new LinearMeasurement(new double[] { 1.0, 1.0 },
+                             new EstimatedParameter[] { p[0], p[1] },
+                             3.0),
+      new LinearMeasurement(new double[] { 1.0, -1.0 },
+                             new EstimatedParameter[] { p[0], p[1] },
+                             1.0),
+      new LinearMeasurement(new double[] { 1.0, 3.0 },
+                             new EstimatedParameter[] { p[0], p[1] },
+                             5.0)
+    });
+
+    LevenbergMarquardtEstimator estimator = new LevenbergMarquardtEstimator();
+    estimator.estimate(problem);
+    assertEquals(0, estimator.getRMS(problem), 1.0e-10);
+    assertEquals(2.0, p[0].getEstimate(), 1.0e-10);
+    assertEquals(1.0, p[1].getEstimate(), 1.0e-10);
+
+  }
+
+  public void testInconsistentEquations() throws EstimationException {
+    EstimatedParameter[] p = {
+      new EstimatedParameter("p0", 1),
+      new EstimatedParameter("p1", 1)
+    };
+    LinearProblem problem = new LinearProblem(new LinearMeasurement[] {
+      new LinearMeasurement(new double[] { 1.0, 1.0 },
+                            new EstimatedParameter[] { p[0], p[1] },
+                            3.0),
+      new LinearMeasurement(new double[] { 1.0, -1.0 },
+                            new EstimatedParameter[] { p[0], p[1] },
+                            1.0),
+      new LinearMeasurement(new double[] { 1.0, 3.0 },
+                            new EstimatedParameter[] { p[0], p[1] },
+                            4.0)
+    });
+
+    LevenbergMarquardtEstimator estimator = new LevenbergMarquardtEstimator();
+    estimator.estimate(problem);
+    assertTrue(estimator.getRMS(problem) > 0.1);
+
+  }
+
+  public void testControlParameters() {
+      Circle circle = new Circle(98.680, 47.345);
+      circle.addPoint( 30.0,  68.0);
+      circle.addPoint( 50.0,  -6.0);
+      circle.addPoint(110.0, -20.0);
+      circle.addPoint( 35.0,  15.0);
+      circle.addPoint( 45.0,  97.0);
+      checkEstimate(circle, 0.1, 10, 1.0e-14, 1.0e-16, 1.0e-10, false);
+      checkEstimate(circle, 0.1, 10, 1.0e-15, 1.0e-17, 1.0e-10, true);
+      checkEstimate(circle, 0.1,  5, 1.0e-15, 1.0e-16, 1.0e-10, true);
+      circle.addPoint(300, -300);
+      checkEstimate(circle, 0.1, 20, 1.0e-18, 1.0e-16, 1.0e-10, true);
+  }
+
+  private void checkEstimate(EstimationProblem problem,
+                             double initialStepBoundFactor, int maxCostEval,
+                             double costRelativeTolerance, double parRelativeTolerance,
+                             double orthoTolerance, boolean shouldFail) {
+      try {
+        LevenbergMarquardtEstimator estimator = new LevenbergMarquardtEstimator();
+        estimator.setInitialStepBoundFactor(initialStepBoundFactor);
+        estimator.setMaxCostEval(maxCostEval);
+        estimator.setCostRelativeTolerance(costRelativeTolerance);
+        estimator.setParRelativeTolerance(parRelativeTolerance);
+        estimator.setOrthoTolerance(orthoTolerance);
+        estimator.estimate(problem);
+        assertTrue(! shouldFail);
+      } catch (EstimationException ee) {
+        assertTrue(shouldFail);
+      }
+    }
+
+  public void testCircleFitting() throws EstimationException {
+      Circle circle = new Circle(98.680, 47.345);
+      circle.addPoint( 30.0,  68.0);
+      circle.addPoint( 50.0,  -6.0);
+      circle.addPoint(110.0, -20.0);
+      circle.addPoint( 35.0,  15.0);
+      circle.addPoint( 45.0,  97.0);
+      LevenbergMarquardtEstimator estimator = new LevenbergMarquardtEstimator();
+      estimator.estimate(circle);
+      assertTrue(estimator.getCostEvaluations() < 10);
+      assertTrue(estimator.getJacobianEvaluations() < 10);
+      double rms = estimator.getRMS(circle);
+      assertEquals(1.768262623567235,  FastMath.sqrt(circle.getM()) * rms,  1.0e-10);
+      assertEquals(69.96016176931406, circle.getRadius(), 1.0e-10);
+      assertEquals(96.07590211815305, circle.getX(),      1.0e-10);
+      assertEquals(48.13516790438953, circle.getY(),      1.0e-10);
+      double[][] cov = estimator.getCovariances(circle);
+      assertEquals(1.839, cov[0][0], 0.001);
+      assertEquals(0.731, cov[0][1], 0.001);
+      assertEquals(cov[0][1], cov[1][0], 1.0e-14);
+      assertEquals(0.786, cov[1][1], 0.001);
+      double[] errors = estimator.guessParametersErrors(circle);
+      assertEquals(1.384, errors[0], 0.001);
+      assertEquals(0.905, errors[1], 0.001);
+
+      // add perfect measurements and check errors are reduced
+      double cx = circle.getX();
+      double cy = circle.getY();
+      double  r = circle.getRadius();
+      for (double d= 0; d < 2 * FastMath.PI; d += 0.01) {
+          circle.addPoint(cx + r * FastMath.cos(d), cy + r * FastMath.sin(d));
+      }
+      estimator = new LevenbergMarquardtEstimator();
+      estimator.estimate(circle);
+      cov = estimator.getCovariances(circle);
+      assertEquals(0.004, cov[0][0], 0.001);
+      assertEquals(6.40e-7, cov[0][1], 1.0e-9);
+      assertEquals(cov[0][1], cov[1][0], 1.0e-14);
+      assertEquals(0.003, cov[1][1], 0.001);
+      errors = estimator.guessParametersErrors(circle);
+      assertEquals(0.004, errors[0], 0.001);
+      assertEquals(0.004, errors[1], 0.001);
+
+  }
+
+  public void testCircleFittingBadInit() throws EstimationException {
+    Circle circle = new Circle(-12, -12);
+    double[][] points = new double[][] {
+      {-0.312967,  0.072366}, {-0.339248,  0.132965}, {-0.379780,  0.202724},
+      {-0.390426,  0.260487}, {-0.361212,  0.328325}, {-0.346039,  0.392619},
+      {-0.280579,  0.444306}, {-0.216035,  0.470009}, {-0.149127,  0.493832},
+      {-0.075133,  0.483271}, {-0.007759,  0.452680}, { 0.060071,  0.410235},
+      { 0.103037,  0.341076}, { 0.118438,  0.273884}, { 0.131293,  0.192201},
+      { 0.115869,  0.129797}, { 0.072223,  0.058396}, { 0.022884,  0.000718},
+      {-0.053355, -0.020405}, {-0.123584, -0.032451}, {-0.216248, -0.032862},
+      {-0.278592, -0.005008}, {-0.337655,  0.056658}, {-0.385899,  0.112526},
+      {-0.405517,  0.186957}, {-0.415374,  0.262071}, {-0.387482,  0.343398},
+      {-0.347322,  0.397943}, {-0.287623,  0.458425}, {-0.223502,  0.475513},
+      {-0.135352,  0.478186}, {-0.061221,  0.483371}, { 0.003711,  0.422737},
+      { 0.065054,  0.375830}, { 0.108108,  0.297099}, { 0.123882,  0.222850},
+      { 0.117729,  0.134382}, { 0.085195,  0.056820}, { 0.029800, -0.019138},
+      {-0.027520, -0.072374}, {-0.102268, -0.091555}, {-0.200299, -0.106578},
+      {-0.292731, -0.091473}, {-0.356288, -0.051108}, {-0.420561,  0.014926},
+      {-0.471036,  0.074716}, {-0.488638,  0.182508}, {-0.485990,  0.254068},
+      {-0.463943,  0.338438}, {-0.406453,  0.404704}, {-0.334287,  0.466119},
+      {-0.254244,  0.503188}, {-0.161548,  0.495769}, {-0.075733,  0.495560},
+      { 0.001375,  0.434937}, { 0.082787,  0.385806}, { 0.115490,  0.323807},
+      { 0.141089,  0.223450}, { 0.138693,  0.131703}, { 0.126415,  0.049174},
+      { 0.066518, -0.010217}, {-0.005184, -0.070647}, {-0.080985, -0.103635},
+      {-0.177377, -0.116887}, {-0.260628, -0.100258}, {-0.335756, -0.056251},
+      {-0.405195, -0.000895}, {-0.444937,  0.085456}, {-0.484357,  0.175597},
+      {-0.472453,  0.248681}, {-0.438580,  0.347463}, {-0.402304,  0.422428},
+      {-0.326777,  0.479438}, {-0.247797,  0.505581}, {-0.152676,  0.519380},
+      {-0.071754,  0.516264}, { 0.015942,  0.472802}, { 0.076608,  0.419077},
+      { 0.127673,  0.330264}, { 0.159951,  0.262150}, { 0.153530,  0.172681},
+      { 0.140653,  0.089229}, { 0.078666,  0.024981}, { 0.023807, -0.037022},
+      {-0.048837, -0.077056}, {-0.127729, -0.075338}, {-0.221271, -0.067526}
+    };
+    for (int i = 0; i < points.length; ++i) {
+      circle.addPoint(points[i][0], points[i][1]);
+    }
+    LevenbergMarquardtEstimator estimator = new LevenbergMarquardtEstimator();
+    estimator.estimate(circle);
+    assertTrue(estimator.getCostEvaluations() < 15);
+    assertTrue(estimator.getJacobianEvaluations() < 10);
+    assertEquals( 0.030184491196225207, estimator.getRMS(circle), 1.0e-9);
+    assertEquals( 0.2922350065939634,   circle.getRadius(), 1.0e-9);
+    assertEquals(-0.15173845023862165,  circle.getX(),      1.0e-8);
+    assertEquals( 0.20750021499570379,  circle.getY(),      1.0e-8);
+  }
+
+  public void testMath199() {
+      try {
+          QuadraticProblem problem = new QuadraticProblem();
+          problem.addPoint (0, -3.182591015485607, 0.0);
+          problem.addPoint (1, -2.5581184967730577, 4.4E-323);
+          problem.addPoint (2, -2.1488478161387325, 1.0);
+          problem.addPoint (3, -1.9122489313410047, 4.4E-323);
+          problem.addPoint (4, 1.7785661310051026, 0.0);
+          new LevenbergMarquardtEstimator().estimate(problem);
+          fail("an exception should have been thrown");
+      } catch (EstimationException ee) {
+          // expected behavior
+      }
+
+  }
+
+  private static class LinearProblem implements EstimationProblem {
+
+    public LinearProblem(LinearMeasurement[] measurements) {
+      this.measurements = measurements;
+    }
+
+    public WeightedMeasurement[] getMeasurements() {
+      return measurements;
+    }
+
+    public EstimatedParameter[] getUnboundParameters() {
+      return getAllParameters();
+    }
+
+    public EstimatedParameter[] getAllParameters() {
+      HashSet<EstimatedParameter> set = new HashSet<EstimatedParameter>();
+      for (int i = 0; i < measurements.length; ++i) {
+        EstimatedParameter[] parameters = measurements[i].getParameters();
+        for (int j = 0; j < parameters.length; ++j) {
+          set.add(parameters[j]);
+        }
+      }
+      return set.toArray(new EstimatedParameter[set.size()]);
+    }
+
+    private LinearMeasurement[] measurements;
+
+  }
+
+  private static class LinearMeasurement extends WeightedMeasurement {
+
+    public LinearMeasurement(double[] factors, EstimatedParameter[] parameters,
+                             double setPoint) {
+      super(1.0, setPoint);
+      this.factors = factors;
+      this.parameters = parameters;
+    }
+
+    @Override
+    public double getTheoreticalValue() {
+      double v = 0;
+      for (int i = 0; i < factors.length; ++i) {
+        v += factors[i] * parameters[i].getEstimate();
+      }
+      return v;
+    }
+
+    @Override
+    public double getPartial(EstimatedParameter parameter) {
+      for (int i = 0; i < parameters.length; ++i) {
+        if (parameters[i] == parameter) {
+          return factors[i];
+        }
+      }
+      return 0;
+    }
+
+    public EstimatedParameter[] getParameters() {
+      return parameters;
+    }
+
+    private double[] factors;
+    private EstimatedParameter[] parameters;
+    private static final long serialVersionUID = -3922448707008868580L;
+
+  }
+
+  private static class Circle implements EstimationProblem {
+
+    public Circle(double cx, double cy) {
+      this.cx = new EstimatedParameter("cx", cx);
+      this.cy = new EstimatedParameter("cy", cy);
+      points  = new ArrayList<PointModel>();
+    }
+
+    public void addPoint(double px, double py) {
+      points.add(new PointModel(this, px, py));
+    }
+
+    public int getM() {
+      return points.size();
+    }
+
+    public WeightedMeasurement[] getMeasurements() {
+      return points.toArray(new PointModel[points.size()]);
+    }
+
+    public EstimatedParameter[] getAllParameters() {
+      return new EstimatedParameter[] { cx, cy };
+    }
+
+    public EstimatedParameter[] getUnboundParameters() {
+      return new EstimatedParameter[] { cx, cy };
+    }
+
+    public double getPartialRadiusX() {
+      double dRdX = 0;
+      for (PointModel point : points) {
+        dRdX += point.getPartialDiX();
+      }
+      return dRdX / points.size();
+    }
+
+    public double getPartialRadiusY() {
+      double dRdY = 0;
+      for (PointModel point : points) {
+        dRdY += point.getPartialDiY();
+      }
+      return dRdY / points.size();
+    }
+
+   public double getRadius() {
+      double r = 0;
+      for (PointModel point : points) {
+        r += point.getCenterDistance();
+      }
+      return r / points.size();
+    }
+
+    public double getX() {
+      return cx.getEstimate();
+    }
+
+    public double getY() {
+      return cy.getEstimate();
+    }
+
+    private static class PointModel extends WeightedMeasurement {
+
+      public PointModel(Circle circle, double px, double py) {
+        super(1.0, 0.0);
+        this.px = px;
+        this.py = py;
+        this.circle = circle;
+      }
+
+      @Override
+      public double getPartial(EstimatedParameter parameter) {
+        if (parameter == circle.cx) {
+          return getPartialDiX() - circle.getPartialRadiusX();
+        } else if (parameter == circle.cy) {
+          return getPartialDiY() - circle.getPartialRadiusY();
+        }
+        return 0;
+      }
+
+      public double getCenterDistance() {
+        double dx = px - circle.cx.getEstimate();
+        double dy = py - circle.cy.getEstimate();
+        return FastMath.sqrt(dx * dx + dy * dy);
+      }
+
+      public double getPartialDiX() {
+        return (circle.cx.getEstimate() - px) / getCenterDistance();
+      }
+
+      public double getPartialDiY() {
+        return (circle.cy.getEstimate() - py) / getCenterDistance();
+      }
+
+      @Override
+      public double getTheoreticalValue() {
+        return getCenterDistance() - circle.getRadius();
+      }
+
+      private double px;
+      private double py;
+      private transient final Circle circle;
+      private static final long serialVersionUID = 1L;
+
+    }
+
+    private EstimatedParameter cx;
+    private EstimatedParameter cy;
+    private ArrayList<PointModel> points;
+
+  }
+
+  private static class QuadraticProblem extends SimpleEstimationProblem {
+
+      private EstimatedParameter a;
+      private EstimatedParameter b;
+      private EstimatedParameter c;
+
+      public QuadraticProblem() {
+          a = new EstimatedParameter("a", 0.0);
+          b = new EstimatedParameter("b", 0.0);
+          c = new EstimatedParameter("c", 0.0);
+          addParameter(a);
+          addParameter(b);
+          addParameter(c);
+      }
+
+      public void addPoint(double x, double y, double w) {
+          addMeasurement(new LocalMeasurement(this, x, y, w));
+      }
+
+      public double theoreticalValue(double x) {
+          return ( (a.getEstimate() * x + b.getEstimate() ) * x + c.getEstimate());
+      }
+
+      private double partial(double x, EstimatedParameter parameter) {
+          if (parameter == a) {
+              return x * x;
+          } else if (parameter == b) {
+              return x;
+          } else {
+              return 1.0;
+          }
+      }
+
+      private static class LocalMeasurement extends WeightedMeasurement {
+
+        private static final long serialVersionUID = 1555043155023729130L;
+        private final double x;
+        private transient final QuadraticProblem pb;
+
+          // constructor
+          public LocalMeasurement(QuadraticProblem pb, double x, double y, double w) {
+              super(w, y);
+              this.x = x;
+              this.pb = pb;
+          }
+
+          @Override
+          public double getTheoreticalValue() {
+              return pb.theoreticalValue(x);
+          }
+
+          @Override
+          public double getPartial(EstimatedParameter parameter) {
+              return pb.partial(x, parameter);
+          }
+
+      }
+  }
+
+}
diff --git a/src/test/java/org/apache/commons/math/estimation/MinpackTest.java b/src/test/java/org/apache/commons/math/estimation/MinpackTest.java
new file mode 100644
index 0000000..c66dafb
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/estimation/MinpackTest.java
@@ -0,0 +1,1538 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.estimation.EstimatedParameter;
+import org.apache.commons.math.estimation.EstimationException;
+import org.apache.commons.math.estimation.EstimationProblem;
+import org.apache.commons.math.estimation.LevenbergMarquardtEstimator;
+import org.apache.commons.math.estimation.WeightedMeasurement;
+import org.apache.commons.math.util.FastMath;
+
+import junit.framework.*;
+
+/**
+ * <p>Some of the unit tests are re-implementations of the MINPACK <a
+ * href="http://www.netlib.org/minpack/ex/file17">file17</a> and <a
+ * href="http://www.netlib.org/minpack/ex/file22">file22</a> test files.
+ * 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>
+
+ * @author Argonne National Laboratory. MINPACK project. March 1980 (original fortran minpack tests)
+ * @author Burton S. Garbow (original fortran minpack tests)
+ * @author Kenneth E. Hillstrom (original fortran minpack tests)
+ * @author Jorge J. More (original fortran minpack tests)
+ * @author Luc Maisonobe (non-minpack tests and minpack tests Java translation)
+ */
+ at Deprecated
+public class MinpackTest
+  extends TestCase {
+
+  public MinpackTest(String name) {
+    super(name);
+  }
+
+  public void testMinpackLinearFullRank() {
+    minpackTest(new LinearFullRankFunction(10, 5, 1.0,
+                                           5.0, 2.23606797749979), false);
+    minpackTest(new LinearFullRankFunction(50, 5, 1.0,
+                                           8.06225774829855, 6.70820393249937), false);
+  }
+
+  public void testMinpackLinearRank1() {
+    minpackTest(new LinearRank1Function(10, 5, 1.0,
+                                        291.521868819476, 1.4638501094228), false);
+    minpackTest(new LinearRank1Function(50, 5, 1.0,
+                                        3101.60039334535, 3.48263016573496), false);
+  }
+
+  public void testMinpackLinearRank1ZeroColsAndRows() {
+    minpackTest(new LinearRank1ZeroColsAndRowsFunction(10, 5, 1.0), false);
+    minpackTest(new LinearRank1ZeroColsAndRowsFunction(50, 5, 1.0), false);
+  }
+
+  public void testMinpackRosenbrok() {
+    minpackTest(new RosenbrockFunction(new double[] { -1.2, 1.0 },
+                                       FastMath.sqrt(24.2)), false);
+    minpackTest(new RosenbrockFunction(new double[] { -12.0, 10.0 },
+                                       FastMath.sqrt(1795769.0)), false);
+    minpackTest(new RosenbrockFunction(new double[] { -120.0, 100.0 },
+                                       11.0 * FastMath.sqrt(169000121.0)), false);
+  }
+
+  public void testMinpackHelicalValley() {
+    minpackTest(new HelicalValleyFunction(new double[] { -1.0, 0.0, 0.0 },
+                                          50.0), false);
+    minpackTest(new HelicalValleyFunction(new double[] { -10.0, 0.0, 0.0 },
+                                          102.95630140987), false);
+    minpackTest(new HelicalValleyFunction(new double[] { -100.0, 0.0, 0.0},
+                                          991.261822123701), false);
+  }
+
+  public void testMinpackPowellSingular() {
+    minpackTest(new PowellSingularFunction(new double[] { 3.0, -1.0, 0.0, 1.0 },
+                                           14.6628782986152), false);
+    minpackTest(new PowellSingularFunction(new double[] { 30.0, -10.0, 0.0, 10.0 },
+                                           1270.9838708654), false);
+    minpackTest(new PowellSingularFunction(new double[] { 300.0, -100.0, 0.0, 100.0 },
+                                           126887.903284750), false);
+  }
+
+  public void testMinpackFreudensteinRoth() {
+    minpackTest(new FreudensteinRothFunction(new double[] { 0.5, -2.0 },
+                                             20.0124960961895, 6.99887517584575,
+                                             new double[] {
+                                               11.4124844654993,
+                                               -0.896827913731509
+                                             }), false);
+    minpackTest(new FreudensteinRothFunction(new double[] { 5.0, -20.0 },
+                                             12432.833948863, 6.9988751744895,
+                                             new double[] {
+                                               11.4130046614746,
+                                               -0.896796038685958
+                                             }), false);
+    minpackTest(new FreudensteinRothFunction(new double[] { 50.0, -200.0 },
+                                             11426454.595762, 6.99887517242903,
+                                             new double[] {
+                                               11.4127817857886,
+                                               -0.89680510749204
+                                             }), false);
+  }
+
+  public void testMinpackBard() {
+    minpackTest(new BardFunction(1.0, 6.45613629515967, 0.0906359603390466,
+                                 new double[] {
+                                   0.0824105765758334,
+                                   1.1330366534715,
+                                   2.34369463894115
+                                 }), false);
+    minpackTest(new BardFunction(10.0, 36.1418531596785, 4.17476870138539,
+                                 new double[] {
+                                   0.840666673818329,
+                                   -158848033.259565,
+                                   -164378671.653535
+                                 }), false);
+    minpackTest(new BardFunction(100.0, 384.114678637399, 4.17476870135969,
+                                 new double[] {
+                                   0.840666673867645,
+                                   -158946167.205518,
+                                   -164464906.857771
+                                 }), false);
+  }
+
+  public void testMinpackKowalikOsborne() {
+    minpackTest(new KowalikOsborneFunction(new double[] { 0.25, 0.39, 0.415, 0.39 },
+                                           0.0728915102882945,
+                                           0.017535837721129,
+                                           new double[] {
+                                             0.192807810476249,
+                                             0.191262653354071,
+                                             0.123052801046931,
+                                             0.136053221150517
+                                           }), false);
+    minpackTest(new KowalikOsborneFunction(new double[] { 2.5, 3.9, 4.15, 3.9 },
+                                           2.97937007555202,
+                                           0.032052192917937,
+                                           new double[] {
+                                             728675.473768287,
+                                             -14.0758803129393,
+                                             -32977797.7841797,
+                                             -20571594.1977912
+                                           }), false);
+    minpackTest(new KowalikOsborneFunction(new double[] { 25.0, 39.0, 41.5, 39.0 },
+                                           29.9590617016037,
+                                           0.0175364017658228,
+                                           new double[] {
+                                             0.192948328597594,
+                                             0.188053165007911,
+                                             0.122430604321144,
+                                             0.134575665392506
+                                           }), true);
+  }
+
+  public void testMinpackMeyer() {
+    minpackTest(new MeyerFunction(new double[] { 0.02, 4000.0, 250.0 },
+                                  41153.4665543031, 9.37794514651874,
+                                  new double[] {
+                                    0.00560963647102661,
+                                    6181.34634628659,
+                                    345.223634624144
+                                  }), false);
+    minpackTest(new MeyerFunction(new double[] { 0.2, 40000.0, 2500.0 },
+                                  4168216.89130846, 792.917871779501,
+                                  new double[] {
+                                    1.42367074157994e-11,
+                                    33695.7133432541,
+                                    901.268527953801
+                                  }), true);
+  }
+
+  public void testMinpackWatson() {
+
+    minpackTest(new WatsonFunction(6, 0.0,
+                                   5.47722557505166, 0.0478295939097601,
+                                   new double[] {
+                                     -0.0157249615083782, 1.01243488232965,
+                                     -0.232991722387673,  1.26043101102818,
+                                     -1.51373031394421,   0.99299727291842
+                                   }), false);
+    minpackTest(new WatsonFunction(6, 10.0,
+                                   6433.12578950026, 0.0478295939096951,
+                                   new double[] {
+                                     -0.0157251901386677, 1.01243485860105,
+                                     -0.232991545843829,  1.26042932089163,
+                                     -1.51372776706575,   0.99299573426328
+                                   }), false);
+    minpackTest(new WatsonFunction(6, 100.0,
+                                   674256.040605213, 0.047829593911544,
+                                   new double[] {
+                                    -0.0157247019712586, 1.01243490925658,
+                                    -0.232991922761641,  1.26043292929555,
+                                    -1.51373320452707,   0.99299901922322
+                                   }), false);
+
+    minpackTest(new WatsonFunction(9, 0.0,
+                                   5.47722557505166, 0.00118311459212420,
+                                   new double[] {
+                                    -0.153070644166722e-4, 0.999789703934597,
+                                     0.0147639634910978,   0.146342330145992,
+                                     1.00082109454817,    -2.61773112070507,
+                                     4.10440313943354,    -3.14361226236241,
+                                     1.05262640378759
+                                   }), false);
+    minpackTest(new WatsonFunction(9, 10.0,
+                                   12088.127069307, 0.00118311459212513,
+                                   new double[] {
+                                   -0.153071334849279e-4, 0.999789703941234,
+                                    0.0147639629786217,   0.146342334818836,
+                                    1.00082107321386,    -2.61773107084722,
+                                    4.10440307655564,    -3.14361222178686,
+                                    1.05262639322589
+                                   }), false);
+    minpackTest(new WatsonFunction(9, 100.0,
+                                   1269109.29043834, 0.00118311459212384,
+                                   new double[] {
+                                    -0.153069523352176e-4, 0.999789703958371,
+                                     0.0147639625185392,   0.146342341096326,
+                                     1.00082104729164,    -2.61773101573645,
+                                     4.10440301427286,    -3.14361218602503,
+                                     1.05262638516774
+                                   }), false);
+
+    minpackTest(new WatsonFunction(12, 0.0,
+                                   5.47722557505166, 0.217310402535861e-4,
+                                   new double[] {
+                                    -0.660266001396382e-8, 1.00000164411833,
+                                    -0.000563932146980154, 0.347820540050756,
+                                    -0.156731500244233,    1.05281515825593,
+                                    -3.24727109519451,     7.2884347837505,
+                                   -10.271848098614,       9.07411353715783,
+                                    -4.54137541918194,     1.01201187975044
+                                   }), false);
+    minpackTest(new WatsonFunction(12, 10.0,
+                                   19220.7589790951, 0.217310402518509e-4,
+                                   new double[] {
+                                    -0.663710223017410e-8, 1.00000164411787,
+                                    -0.000563932208347327, 0.347820540486998,
+                                    -0.156731503955652,    1.05281517654573,
+                                    -3.2472711515214,      7.28843489430665,
+                                   -10.2718482369638,      9.07411364383733,
+                                    -4.54137546533666,     1.01201188830857
+                                   }), false);
+    minpackTest(new WatsonFunction(12, 100.0,
+                                   2018918.04462367, 0.217310402539845e-4,
+                                   new double[] {
+                                    -0.663806046485249e-8, 1.00000164411786,
+                                    -0.000563932210324959, 0.347820540503588,
+                                    -0.156731504091375,    1.05281517718031,
+                                    -3.24727115337025,     7.28843489775302,
+                                   -10.2718482410813,      9.07411364688464,
+                                    -4.54137546660822,     1.0120118885369
+                                   }), false);
+
+  }
+
+  public void testMinpackBox3Dimensional() {
+    minpackTest(new Box3DimensionalFunction(10, new double[] { 0.0, 10.0, 20.0 },
+                                            32.1115837449572), false);
+  }
+
+  public void testMinpackJennrichSampson() {
+    minpackTest(new JennrichSampsonFunction(10, new double[] { 0.3, 0.4 },
+                                            64.5856498144943, 11.1517793413499,
+                                            new double[] {
+                                             0.257819926636811, 0.257829976764542
+                                            }), false);
+  }
+
+  public void testMinpackBrownDennis() {
+    minpackTest(new BrownDennisFunction(20,
+                                        new double[] { 25.0, 5.0, -5.0, -1.0 },
+                                        2815.43839161816, 292.954288244866,
+                                        new double[] {
+                                         -11.59125141003, 13.2024883984741,
+                                         -0.403574643314272, 0.236736269844604
+                                        }), false);
+    minpackTest(new BrownDennisFunction(20,
+                                        new double[] { 250.0, 50.0, -50.0, -10.0 },
+                                        555073.354173069, 292.954270581415,
+                                        new double[] {
+                                         -11.5959274272203, 13.2041866926242,
+                                         -0.403417362841545, 0.236771143410386
+                                       }), false);
+    minpackTest(new BrownDennisFunction(20,
+                                        new double[] { 2500.0, 500.0, -500.0, -100.0 },
+                                        61211252.2338581, 292.954306151134,
+                                        new double[] {
+                                         -11.5902596937374, 13.2020628854665,
+                                         -0.403688070279258, 0.236665033746463
+                                        }), false);
+  }
+
+  public void testMinpackChebyquad() {
+    minpackTest(new ChebyquadFunction(1, 8, 1.0,
+                                      1.88623796907732, 1.88623796907732,
+                                      new double[] { 0.5 }), false);
+    minpackTest(new ChebyquadFunction(1, 8, 10.0,
+                                      5383344372.34005, 1.88424820499951,
+                                      new double[] { 0.9817314924684 }), false);
+    minpackTest(new ChebyquadFunction(1, 8, 100.0,
+                                      0.118088726698392e19, 1.88424820499347,
+                                      new double[] { 0.9817314852934 }), false);
+    minpackTest(new ChebyquadFunction(8, 8, 1.0,
+                                      0.196513862833975, 0.0593032355046727,
+                                      new double[] {
+                                        0.0431536648587336, 0.193091637843267,
+                                        0.266328593812698,  0.499999334628884,
+                                        0.500000665371116,  0.733671406187302,
+                                        0.806908362156733,  0.956846335141266
+                                      }), false);
+    minpackTest(new ChebyquadFunction(9, 9, 1.0,
+                                      0.16994993465202, 0.0,
+                                      new double[] {
+                                        0.0442053461357828, 0.199490672309881,
+                                        0.23561910847106,   0.416046907892598,
+                                        0.5,                0.583953092107402,
+                                        0.764380891528940,  0.800509327690119,
+                                        0.955794653864217
+                                      }), false);
+    minpackTest(new ChebyquadFunction(10, 10, 1.0,
+                                      0.183747831178711, 0.0806471004038253,
+                                      new double[] {
+                                        0.0596202671753563, 0.166708783805937,
+                                        0.239171018813509,  0.398885290346268,
+                                        0.398883667870681,  0.601116332129320,
+                                        0.60111470965373,   0.760828981186491,
+                                        0.833291216194063,  0.940379732824644
+                                      }), false);
+  }
+
+  public void testMinpackBrownAlmostLinear() {
+    minpackTest(new BrownAlmostLinearFunction(10, 0.5,
+                                              16.5302162063499, 0.0,
+                                              new double[] {
+                                                0.979430303349862, 0.979430303349862,
+                                                0.979430303349862, 0.979430303349862,
+                                                0.979430303349862, 0.979430303349862,
+                                                0.979430303349862, 0.979430303349862,
+                                                0.979430303349862, 1.20569696650138
+                                              }), false);
+    minpackTest(new BrownAlmostLinearFunction(10, 5.0,
+                                              9765624.00089211, 0.0,
+                                              new double[] {
+                                               0.979430303349865, 0.979430303349865,
+                                               0.979430303349865, 0.979430303349865,
+                                               0.979430303349865, 0.979430303349865,
+                                               0.979430303349865, 0.979430303349865,
+                                               0.979430303349865, 1.20569696650135
+                                              }), false);
+    minpackTest(new BrownAlmostLinearFunction(10, 50.0,
+                                              0.9765625e17, 0.0,
+                                              new double[] {
+                                                1.0, 1.0, 1.0, 1.0, 1.0,
+                                                1.0, 1.0, 1.0, 1.0, 1.0
+                                              }), false);
+    minpackTest(new BrownAlmostLinearFunction(30, 0.5,
+                                              83.476044467848, 0.0,
+                                              new double[] {
+                                                0.997754216442807, 0.997754216442807,
+                                                0.997754216442807, 0.997754216442807,
+                                                0.997754216442807, 0.997754216442807,
+                                                0.997754216442807, 0.997754216442807,
+                                                0.997754216442807, 0.997754216442807,
+                                                0.997754216442807, 0.997754216442807,
+                                                0.997754216442807, 0.997754216442807,
+                                                0.997754216442807, 0.997754216442807,
+                                                0.997754216442807, 0.997754216442807,
+                                                0.997754216442807, 0.997754216442807,
+                                                0.997754216442807, 0.997754216442807,
+                                                0.997754216442807, 0.997754216442807,
+                                                0.997754216442807, 0.997754216442807,
+                                                0.997754216442807, 0.997754216442807,
+                                                0.997754216442807, 1.06737350671578
+                                              }), false);
+    minpackTest(new BrownAlmostLinearFunction(40, 0.5,
+                                              128.026364472323, 0.0,
+                                              new double[] {
+                                                1.00000000000002, 1.00000000000002,
+                                                1.00000000000002, 1.00000000000002,
+                                                1.00000000000002, 1.00000000000002,
+                                                1.00000000000002, 1.00000000000002,
+                                                1.00000000000002, 1.00000000000002,
+                                                1.00000000000002, 1.00000000000002,
+                                                1.00000000000002, 1.00000000000002,
+                                                1.00000000000002, 1.00000000000002,
+                                                1.00000000000002, 1.00000000000002,
+                                                1.00000000000002, 1.00000000000002,
+                                                1.00000000000002, 1.00000000000002,
+                                                1.00000000000002, 1.00000000000002,
+                                                1.00000000000002, 1.00000000000002,
+                                                1.00000000000002, 1.00000000000002,
+                                                1.00000000000002, 1.00000000000002,
+                                                1.00000000000002, 1.00000000000002,
+                                                1.00000000000002, 1.00000000000002,
+                                                0.999999999999121
+                                              }), false);
+    }
+
+  public void testMinpackOsborne1() {
+      minpackTest(new Osborne1Function(new double[] { 0.5, 1.5, -1.0, 0.01, 0.02, },
+                                       0.937564021037838, 0.00739249260904843,
+                                       new double[] {
+                                         0.375410049244025, 1.93584654543108,
+                                        -1.46468676748716, 0.0128675339110439,
+                                         0.0221227011813076
+                                       }), false);
+    }
+
+  public void testMinpackOsborne2() {
+
+    minpackTest(new Osborne2Function(new double[] {
+                                       1.3, 0.65, 0.65, 0.7, 0.6,
+                                       3.0, 5.0, 7.0, 2.0, 4.5, 5.5
+                                     },
+                                     1.44686540984712, 0.20034404483314,
+                                     new double[] {
+                                       1.30997663810096,  0.43155248076,
+                                       0.633661261602859, 0.599428560991695,
+                                       0.754179768272449, 0.904300082378518,
+                                       1.36579949521007, 4.82373199748107,
+                                       2.39868475104871, 4.56887554791452,
+                                       5.67534206273052
+                                     }), false);
+  }
+
+  private void minpackTest(MinpackFunction function, boolean exceptionExpected) {
+    LevenbergMarquardtEstimator estimator = new LevenbergMarquardtEstimator();
+    estimator.setMaxCostEval(100 * (function.getN() + 1));
+    estimator.setCostRelativeTolerance(FastMath.sqrt(2.22044604926e-16));
+    estimator.setParRelativeTolerance(FastMath.sqrt(2.22044604926e-16));
+    estimator.setOrthoTolerance(2.22044604926e-16);
+    assertTrue(function.checkTheoreticalStartCost(estimator.getRMS(function)));
+    try {
+      estimator.estimate(function);
+      assertFalse(exceptionExpected);
+    } catch (EstimationException lsse) {
+      assertTrue(exceptionExpected);
+    }
+    assertTrue(function.checkTheoreticalMinCost(estimator.getRMS(function)));
+    assertTrue(function.checkTheoreticalMinParams());
+  }
+
+  private static abstract class MinpackFunction implements EstimationProblem {
+
+    protected MinpackFunction(int m,
+                              double[] startParams,
+                              double   theoreticalStartCost,
+                              double   theoreticalMinCost,
+                              double[] theoreticalMinParams) {
+      this.m = m;
+      this.n = startParams.length;
+      parameters = new EstimatedParameter[n];
+      for (int i = 0; i < n; ++i) {
+        parameters[i] = new EstimatedParameter("p" + i, startParams[i]);
+      }
+      this.theoreticalStartCost = theoreticalStartCost;
+      this.theoreticalMinCost   = theoreticalMinCost;
+      this.theoreticalMinParams = theoreticalMinParams;
+      this.costAccuracy         = 1.0e-8;
+      this.paramsAccuracy       = 1.0e-5;
+    }
+
+    protected static double[] buildArray(int n, double x) {
+      double[] array = new double[n];
+      Arrays.fill(array, x);
+      return array;
+    }
+
+    protected void setCostAccuracy(double costAccuracy) {
+      this.costAccuracy = costAccuracy;
+    }
+
+    protected void setParamsAccuracy(double paramsAccuracy) {
+      this.paramsAccuracy = paramsAccuracy;
+    }
+
+    public int getN() {
+      return parameters.length;
+    }
+
+    public boolean checkTheoreticalStartCost(double rms) {
+      double threshold = costAccuracy * (1.0 + theoreticalStartCost);
+      return FastMath.abs(FastMath.sqrt(m) * rms - theoreticalStartCost) <= threshold;
+    }
+
+    public boolean checkTheoreticalMinCost(double rms) {
+      double threshold = costAccuracy * (1.0 + theoreticalMinCost);
+     return FastMath.abs(FastMath.sqrt(m) * rms - theoreticalMinCost) <= threshold;
+    }
+
+    public boolean checkTheoreticalMinParams() {
+      if (theoreticalMinParams != null) {
+        for (int i = 0; i < theoreticalMinParams.length; ++i) {
+          double mi = theoreticalMinParams[i];
+          double vi = parameters[i].getEstimate();
+          if (FastMath.abs(mi - vi) > (paramsAccuracy * (1.0 + FastMath.abs(mi)))) {
+            return false;
+          }
+        }
+      }
+      return true;
+    }
+
+    public WeightedMeasurement[] getMeasurements() {
+      WeightedMeasurement[] measurements = new WeightedMeasurement[m];
+      for (int i = 0; i < m; ++i) {
+        measurements[i] = new MinpackMeasurement(this, i);
+      }
+      return measurements;
+    }
+
+    public EstimatedParameter[] getUnboundParameters() {
+      return parameters;
+    }
+
+    public EstimatedParameter[] getAllParameters() {
+      return parameters;
+    }
+
+    protected abstract double[][] getJacobian();
+
+    protected abstract double[] getResiduals();
+
+    private static class MinpackMeasurement extends WeightedMeasurement {
+
+      public MinpackMeasurement(MinpackFunction f, int index) {
+        super(1.0, 0.0);
+        this.index = index;
+        this.f = f;
+      }
+
+      @Override
+      public double getTheoreticalValue() {
+        // this is obviously NOT efficient as we recompute the whole vector
+        // each time we need only one element, but it is only for test
+        // purposes and is simpler to check.
+        // This implementation should NOT be taken as an example, it is ugly!
+        return f.getResiduals()[index];
+      }
+
+      @Override
+      public double getPartial(EstimatedParameter parameter) {
+        // this is obviously NOT efficient as we recompute the whole jacobian
+        // each time we need only one element, but it is only for test
+        // purposes and is simpler to check.
+        // This implementation should NOT be taken as an example, it is ugly!
+        for (int j = 0; j < f.n; ++j) {
+          if (parameter == f.parameters[j]) {
+            return f.getJacobian()[index][j];
+          }
+        }
+        return 0;
+      }
+
+      private int index;
+      private transient final MinpackFunction f;
+      private static final long serialVersionUID = 1L;
+
+    }
+
+    protected int                  n;
+    protected int                  m;
+    protected EstimatedParameter[] parameters;
+    protected double               theoreticalStartCost;
+    protected double               theoreticalMinCost;
+    protected double[]             theoreticalMinParams;
+    protected double               costAccuracy;
+    protected double               paramsAccuracy;
+
+  }
+
+  private static class LinearFullRankFunction extends MinpackFunction {
+
+    public LinearFullRankFunction(int m, int n, double x0,
+                                  double theoreticalStartCost,
+                                  double theoreticalMinCost) {
+      super(m, buildArray(n, x0), theoreticalStartCost,
+            theoreticalMinCost, buildArray(n, -1.0));
+    }
+
+    @Override
+    protected double[][] getJacobian() {
+      double t = 2.0 / m;
+      double[][] jacobian = new double[m][];
+      for (int i = 0; i < m; ++i) {
+        jacobian[i] = new double[n];
+        for (int j = 0; j < n; ++j) {
+          jacobian[i][j] = (i == j) ? (1 - t) : -t;
+        }
+      }
+      return jacobian;
+    }
+
+    @Override
+    protected double[] getResiduals() {
+      double sum = 0;
+      for (int i = 0; i < n; ++i) {
+        sum += parameters[i].getEstimate();
+      }
+      double t  = 1 + 2 * sum / m;
+      double[] f = new double[m];
+      for (int i = 0; i < n; ++i) {
+        f[i] = parameters[i].getEstimate() - t;
+      }
+      Arrays.fill(f, n, m, -t);
+      return f;
+    }
+
+  }
+
+  private static class LinearRank1Function extends MinpackFunction {
+
+    public LinearRank1Function(int m, int n, double x0,
+                                  double theoreticalStartCost,
+                                  double theoreticalMinCost) {
+      super(m, buildArray(n, x0), theoreticalStartCost, theoreticalMinCost, null);
+    }
+
+    @Override
+    protected double[][] getJacobian() {
+      double[][] jacobian = new double[m][];
+      for (int i = 0; i < m; ++i) {
+        jacobian[i] = new double[n];
+        for (int j = 0; j < n; ++j) {
+          jacobian[i][j] = (i + 1) * (j + 1);
+        }
+      }
+      return jacobian;
+    }
+
+    @Override
+    protected double[] getResiduals() {
+      double[] f = new double[m];
+      double sum = 0;
+      for (int i = 0; i < n; ++i) {
+        sum += (i + 1) * parameters[i].getEstimate();
+      }
+      for (int i = 0; i < m; ++i) {
+        f[i] = (i + 1) * sum - 1;
+      }
+      return f;
+    }
+
+  }
+
+  private static class LinearRank1ZeroColsAndRowsFunction extends MinpackFunction {
+
+    public LinearRank1ZeroColsAndRowsFunction(int m, int n, double x0) {
+      super(m, buildArray(n, x0),
+            FastMath.sqrt(m + (n+1)*(n-2)*(m-2)*(m-1) * ((n+1)*(n-2)*(2*m-3) - 12) / 24.0),
+            FastMath.sqrt((m * (m + 3) - 6) / (2.0 * (2 * m - 3))),
+            null);
+    }
+
+    @Override
+    protected double[][] getJacobian() {
+      double[][] jacobian = new double[m][];
+      for (int i = 0; i < m; ++i) {
+        jacobian[i] = new double[n];
+        jacobian[i][0] = 0;
+        for (int j = 1; j < (n - 1); ++j) {
+          if (i == 0) {
+            jacobian[i][j] = 0;
+          } else if (i != (m - 1)) {
+            jacobian[i][j] = i * (j + 1);
+          } else {
+            jacobian[i][j] = 0;
+          }
+        }
+        jacobian[i][n - 1] = 0;
+      }
+      return jacobian;
+    }
+
+    @Override
+    protected double[] getResiduals() {
+      double[] f = new double[m];
+      double sum = 0;
+      for (int i = 1; i < (n - 1); ++i) {
+        sum += (i + 1) * parameters[i].getEstimate();
+      }
+      for (int i = 0; i < (m - 1); ++i) {
+        f[i] = i * sum - 1;
+      }
+      f[m - 1] = -1;
+      return f;
+    }
+
+  }
+
+  private static class RosenbrockFunction extends MinpackFunction {
+
+    public RosenbrockFunction(double[] startParams, double theoreticalStartCost) {
+      super(2, startParams, theoreticalStartCost, 0.0, buildArray(2, 1.0));
+    }
+
+    @Override
+    protected double[][] getJacobian() {
+      double x1 = parameters[0].getEstimate();
+      return new double[][] { { -20 * x1, 10 }, { -1, 0 } };
+    }
+
+    @Override
+    protected double[] getResiduals() {
+      double x1 = parameters[0].getEstimate();
+      double x2 = parameters[1].getEstimate();
+      return new double[] { 10 * (x2 - x1 * x1), 1 - x1 };
+    }
+
+  }
+
+  private static class HelicalValleyFunction extends MinpackFunction {
+
+    public HelicalValleyFunction(double[] startParams,
+                                 double theoreticalStartCost) {
+      super(3, startParams, theoreticalStartCost, 0.0,
+            new double[] { 1.0, 0.0, 0.0 });
+    }
+
+    @Override
+    protected double[][] getJacobian() {
+      double x1 = parameters[0].getEstimate();
+      double x2 = parameters[1].getEstimate();
+      double tmpSquare = x1 * x1 + x2 * x2;
+      double tmp1 = twoPi * tmpSquare;
+      double tmp2 = FastMath.sqrt(tmpSquare);
+      return new double[][] {
+        {  100 * x2 / tmp1, -100 * x1 / tmp1, 10 },
+        { 10 * x1 / tmp2, 10 * x2 / tmp2, 0 },
+        { 0, 0, 1 }
+      };
+    }
+
+    @Override
+    protected double[] getResiduals() {
+      double x1 = parameters[0].getEstimate();
+      double x2 = parameters[1].getEstimate();
+      double x3 = parameters[2].getEstimate();
+      double tmp1;
+      if (x1 == 0) {
+        tmp1 = (x2 >= 0) ? 0.25 : -0.25;
+      } else {
+        tmp1 = FastMath.atan(x2 / x1) / twoPi;
+        if (x1 < 0) {
+          tmp1 += 0.5;
+        }
+      }
+      double tmp2 = FastMath.sqrt(x1 * x1 + x2 * x2);
+      return new double[] {
+        10.0 * (x3 - 10 * tmp1),
+        10.0 * (tmp2 - 1),
+        x3
+      };
+    }
+
+    private static final double twoPi = 2.0 * FastMath.PI;
+
+  }
+
+  private static class PowellSingularFunction extends MinpackFunction {
+
+    public PowellSingularFunction(double[] startParams,
+                                  double theoreticalStartCost) {
+      super(4, startParams, theoreticalStartCost, 0.0, buildArray(4, 0.0));
+    }
+
+    @Override
+    protected double[][] getJacobian() {
+      double x1 = parameters[0].getEstimate();
+      double x2 = parameters[1].getEstimate();
+      double x3 = parameters[2].getEstimate();
+      double x4 = parameters[3].getEstimate();
+      return new double[][] {
+        { 1, 10, 0, 0 },
+        { 0, 0, sqrt5, -sqrt5 },
+        { 0, 2 * (x2 - 2 * x3), -4 * (x2 - 2 * x3), 0 },
+        { 2 * sqrt10 * (x1 - x4), 0, 0, -2 * sqrt10 * (x1 - x4) }
+      };
+    }
+
+    @Override
+    protected double[] getResiduals() {
+      double x1 = parameters[0].getEstimate();
+      double x2 = parameters[1].getEstimate();
+      double x3 = parameters[2].getEstimate();
+      double x4 = parameters[3].getEstimate();
+      return new double[] {
+        x1 + 10 * x2,
+        sqrt5 * (x3 - x4),
+        (x2 - 2 * x3) * (x2 - 2 * x3),
+        sqrt10 * (x1 - x4) * (x1 - x4)
+      };
+    }
+
+    private static final double sqrt5  = FastMath.sqrt( 5.0);
+    private static final double sqrt10 = FastMath.sqrt(10.0);
+
+  }
+
+  private static class FreudensteinRothFunction extends MinpackFunction {
+
+    public FreudensteinRothFunction(double[] startParams,
+                                    double theoreticalStartCost,
+                                    double theoreticalMinCost,
+                                    double[] theoreticalMinParams) {
+      super(2, startParams, theoreticalStartCost,
+            theoreticalMinCost, theoreticalMinParams);
+    }
+
+    @Override
+    protected double[][] getJacobian() {
+      double x2 = parameters[1].getEstimate();
+      return new double[][] {
+        { 1, x2 * (10 - 3 * x2) -  2 },
+        { 1, x2 * ( 2 + 3 * x2) - 14, }
+      };
+    }
+
+    @Override
+    protected double[] getResiduals() {
+      double x1 = parameters[0].getEstimate();
+      double x2 = parameters[1].getEstimate();
+      return new double[] {
+       -13.0 + x1 + ((5.0 - x2) * x2 -  2.0) * x2,
+       -29.0 + x1 + ((1.0 + x2) * x2 - 14.0) * x2
+      };
+    }
+
+  }
+
+  private static class BardFunction extends MinpackFunction {
+
+    public BardFunction(double x0,
+                        double theoreticalStartCost,
+                        double theoreticalMinCost,
+                        double[] theoreticalMinParams) {
+      super(15, buildArray(3, x0), theoreticalStartCost,
+            theoreticalMinCost, theoreticalMinParams);
+    }
+
+    @Override
+    protected double[][] getJacobian() {
+      double   x2 = parameters[1].getEstimate();
+      double   x3 = parameters[2].getEstimate();
+      double[][] jacobian = new double[m][];
+      for (int i = 0; i < m; ++i) {
+        double tmp1 = i  + 1;
+        double tmp2 = 15 - i;
+        double tmp3 = (i <= 7) ? tmp1 : tmp2;
+        double tmp4 = x2 * tmp2 + x3 * tmp3;
+        tmp4 *= tmp4;
+        jacobian[i] = new double[] { -1, tmp1 * tmp2 / tmp4, tmp1 * tmp3 / tmp4 };
+      }
+      return jacobian;
+    }
+
+    @Override
+    protected double[] getResiduals() {
+      double   x1 = parameters[0].getEstimate();
+      double   x2 = parameters[1].getEstimate();
+      double   x3 = parameters[2].getEstimate();
+      double[] f = new double[m];
+      for (int i = 0; i < m; ++i) {
+        double tmp1 = i + 1;
+        double tmp2 = 15 - i;
+        double tmp3 = (i <= 7) ? tmp1 : tmp2;
+        f[i] = y[i] - (x1 + tmp1 / (x2 * tmp2 + x3 * tmp3));
+      }
+      return f;
+    }
+
+    private static final double[] y = {
+      0.14, 0.18, 0.22, 0.25, 0.29,
+      0.32, 0.35, 0.39, 0.37, 0.58,
+      0.73, 0.96, 1.34, 2.10, 4.39
+    };
+
+  }
+
+  private static class KowalikOsborneFunction extends MinpackFunction {
+
+    public KowalikOsborneFunction(double[] startParams,
+                                  double theoreticalStartCost,
+                                  double theoreticalMinCost,
+                                  double[] theoreticalMinParams) {
+      super(11, startParams, theoreticalStartCost,
+            theoreticalMinCost, theoreticalMinParams);
+      if (theoreticalStartCost > 20.0) {
+        setCostAccuracy(2.0e-4);
+        setParamsAccuracy(5.0e-3);
+      }
+    }
+
+    @Override
+    protected double[][] getJacobian() {
+      double   x1 = parameters[0].getEstimate();
+      double   x2 = parameters[1].getEstimate();
+      double   x3 = parameters[2].getEstimate();
+      double   x4 = parameters[3].getEstimate();
+      double[][] jacobian = new double[m][];
+      for (int i = 0; i < m; ++i) {
+        double tmp = v[i] * (v[i] + x3) + x4;
+        double j1  = -v[i] * (v[i] + x2) / tmp;
+        double j2  = -v[i] * x1 / tmp;
+        double j3  = j1 * j2;
+        double j4  = j3 / v[i];
+        jacobian[i] = new double[] { j1, j2, j3, j4 };
+      }
+      return jacobian;
+    }
+
+    @Override
+    protected double[] getResiduals() {
+      double x1 = parameters[0].getEstimate();
+      double x2 = parameters[1].getEstimate();
+      double x3 = parameters[2].getEstimate();
+      double x4 = parameters[3].getEstimate();
+      double[] f = new double[m];
+      for (int i = 0; i < m; ++i) {
+        f[i] = y[i] - x1 * (v[i] * (v[i] + x2)) / (v[i] * (v[i] + x3) + x4);
+      }
+      return f;
+    }
+
+    private static final double[] v = {
+      4.0, 2.0, 1.0, 0.5, 0.25, 0.167, 0.125, 0.1, 0.0833, 0.0714, 0.0625
+    };
+
+    private static final double[] y = {
+      0.1957, 0.1947, 0.1735, 0.1600, 0.0844, 0.0627,
+      0.0456, 0.0342, 0.0323, 0.0235, 0.0246
+    };
+
+  }
+
+  private static class MeyerFunction extends MinpackFunction {
+
+    public MeyerFunction(double[] startParams,
+                         double theoreticalStartCost,
+                         double theoreticalMinCost,
+                         double[] theoreticalMinParams) {
+      super(16, startParams, theoreticalStartCost,
+            theoreticalMinCost, theoreticalMinParams);
+      if (theoreticalStartCost > 1.0e6) {
+        setCostAccuracy(7.0e-3);
+        setParamsAccuracy(2.0e-2);
+      }
+    }
+
+    @Override
+    protected double[][] getJacobian() {
+      double   x1 = parameters[0].getEstimate();
+      double   x2 = parameters[1].getEstimate();
+      double   x3 = parameters[2].getEstimate();
+      double[][] jacobian = new double[m][];
+      for (int i = 0; i < m; ++i) {
+        double temp = 5.0 * (i + 1) + 45.0 + x3;
+        double tmp1 = x2 / temp;
+        double tmp2 = FastMath.exp(tmp1);
+        double tmp3 = x1 * tmp2 / temp;
+        jacobian[i] = new double[] { tmp2, tmp3, -tmp1 * tmp3 };
+      }
+      return jacobian;
+    }
+
+    @Override
+    protected double[] getResiduals() {
+      double x1 = parameters[0].getEstimate();
+      double x2 = parameters[1].getEstimate();
+      double x3 = parameters[2].getEstimate();
+      double[] f = new double[m];
+      for (int i = 0; i < m; ++i) {
+        f[i] = x1 * FastMath.exp(x2 / (5.0 * (i + 1) + 45.0 + x3)) - y[i];
+      }
+     return f;
+    }
+
+    private static final double[] y = {
+      34780.0, 28610.0, 23650.0, 19630.0,
+      16370.0, 13720.0, 11540.0,  9744.0,
+       8261.0,  7030.0,  6005.0,  5147.0,
+       4427.0,  3820.0,  3307.0,  2872.0
+    };
+
+  }
+
+  private static class WatsonFunction extends MinpackFunction {
+
+    public WatsonFunction(int n, double x0,
+                          double theoreticalStartCost,
+                          double theoreticalMinCost,
+                          double[] theoreticalMinParams) {
+      super(31, buildArray(n, x0), theoreticalStartCost,
+            theoreticalMinCost, theoreticalMinParams);
+    }
+
+    @Override
+    protected double[][] getJacobian() {
+
+      double[][] jacobian = new double[m][];
+
+      for (int i = 0; i < (m - 2); ++i) {
+        double div = (i + 1) / 29.0;
+        double s2  = 0.0;
+        double dx  = 1.0;
+        for (int j = 0; j < n; ++j) {
+          s2 += dx * parameters[j].getEstimate();
+          dx *= div;
+        }
+        double temp= 2 * div * s2;
+        dx = 1.0 / div;
+        jacobian[i] = new double[n];
+        for (int j = 0; j < n; ++j) {
+          jacobian[i][j] = dx * (j - temp);
+          dx *= div;
+        }
+      }
+
+      jacobian[m - 2]    = new double[n];
+      jacobian[m - 2][0] = 1;
+
+      jacobian[m - 1]   = new double[n];
+      jacobian[m - 1][0]= -2 * parameters[0].getEstimate();
+      jacobian[m - 1][1]= 1;
+
+      return jacobian;
+
+    }
+
+    @Override
+    protected double[] getResiduals() {
+     double[] f = new double[m];
+     for (int i = 0; i < (m - 2); ++i) {
+       double div = (i + 1) / 29.0;
+       double s1 = 0;
+       double dx = 1;
+       for (int j = 1; j < n; ++j) {
+         s1 += j * dx * parameters[j].getEstimate();
+         dx *= div;
+       }
+       double s2 =0;
+       dx =1;
+       for (int j = 0; j < n; ++j) {
+         s2 += dx * parameters[j].getEstimate();
+         dx *= div;
+       }
+       f[i] = s1 - s2 * s2 - 1;
+     }
+
+     double x1 = parameters[0].getEstimate();
+     double x2 = parameters[1].getEstimate();
+     f[m - 2] = x1;
+     f[m - 1] = x2 - x1 * x1 - 1;
+
+     return f;
+
+    }
+
+  }
+
+  private static class Box3DimensionalFunction extends MinpackFunction {
+
+    public Box3DimensionalFunction(int m, double[] startParams,
+                                   double theoreticalStartCost) {
+      super(m, startParams, theoreticalStartCost,
+            0.0, new double[] { 1.0, 10.0, 1.0 });
+   }
+
+    @Override
+    protected double[][] getJacobian() {
+      double   x1 = parameters[0].getEstimate();
+      double   x2 = parameters[1].getEstimate();
+      double[][] jacobian = new double[m][];
+      for (int i = 0; i < m; ++i) {
+        double tmp = (i + 1) / 10.0;
+        jacobian[i] = new double[] {
+          -tmp * FastMath.exp(-tmp * x1),
+           tmp * FastMath.exp(-tmp * x2),
+          FastMath.exp(-i - 1) - FastMath.exp(-tmp)
+        };
+      }
+      return jacobian;
+    }
+
+    @Override
+    protected double[] getResiduals() {
+      double x1 = parameters[0].getEstimate();
+      double x2 = parameters[1].getEstimate();
+      double x3 = parameters[2].getEstimate();
+      double[] f = new double[m];
+      for (int i = 0; i < m; ++i) {
+        double tmp = (i + 1) / 10.0;
+        f[i] = FastMath.exp(-tmp * x1) - FastMath.exp(-tmp * x2)
+             + (FastMath.exp(-i - 1) - FastMath.exp(-tmp)) * x3;
+      }
+      return f;
+    }
+
+  }
+
+  private static class JennrichSampsonFunction extends MinpackFunction {
+
+    public JennrichSampsonFunction(int m, double[] startParams,
+                                   double theoreticalStartCost,
+                                   double theoreticalMinCost,
+                                   double[] theoreticalMinParams) {
+      super(m, startParams, theoreticalStartCost,
+            theoreticalMinCost, theoreticalMinParams);
+    }
+
+    @Override
+    protected double[][] getJacobian() {
+      double   x1 = parameters[0].getEstimate();
+      double   x2 = parameters[1].getEstimate();
+      double[][] jacobian = new double[m][];
+      for (int i = 0; i < m; ++i) {
+        double t = i + 1;
+        jacobian[i] = new double[] { -t * FastMath.exp(t * x1), -t * FastMath.exp(t * x2) };
+      }
+      return jacobian;
+    }
+
+    @Override
+    protected double[] getResiduals() {
+      double x1 = parameters[0].getEstimate();
+      double x2 = parameters[1].getEstimate();
+      double[] f = new double[m];
+      for (int i = 0; i < m; ++i) {
+        double temp = i + 1;
+        f[i] = 2 + 2 * temp - FastMath.exp(temp * x1) - FastMath.exp(temp * x2);
+      }
+      return f;
+    }
+
+  }
+
+  private static class BrownDennisFunction extends MinpackFunction {
+
+    public BrownDennisFunction(int m, double[] startParams,
+                               double theoreticalStartCost,
+                               double theoreticalMinCost,
+                               double[] theoreticalMinParams) {
+      super(m, startParams, theoreticalStartCost,
+            theoreticalMinCost, theoreticalMinParams);
+      setCostAccuracy(2.5e-8);
+    }
+
+    @Override
+    protected double[][] getJacobian() {
+      double   x1 = parameters[0].getEstimate();
+      double   x2 = parameters[1].getEstimate();
+      double   x3 = parameters[2].getEstimate();
+      double   x4 = parameters[3].getEstimate();
+      double[][] jacobian = new double[m][];
+      for (int i = 0; i < m; ++i) {
+        double temp = (i + 1) / 5.0;
+        double ti   = FastMath.sin(temp);
+        double tmp1 = x1 + temp * x2 - FastMath.exp(temp);
+        double tmp2 = x3 + ti   * x4 - FastMath.cos(temp);
+        jacobian[i] = new double[] {
+          2 * tmp1, 2 * temp * tmp1, 2 * tmp2, 2 * ti * tmp2
+        };
+      }
+      return jacobian;
+    }
+
+    @Override
+    protected double[] getResiduals() {
+      double x1 = parameters[0].getEstimate();
+      double x2 = parameters[1].getEstimate();
+      double x3 = parameters[2].getEstimate();
+      double x4 = parameters[3].getEstimate();
+      double[] f = new double[m];
+      for (int i = 0; i < m; ++i) {
+        double temp = (i + 1) / 5.0;
+        double tmp1 = x1 + temp * x2 - FastMath.exp(temp);
+        double tmp2 = x3 + FastMath.sin(temp) * x4 - FastMath.cos(temp);
+        f[i] = tmp1 * tmp1 + tmp2 * tmp2;
+      }
+      return f;
+    }
+
+  }
+
+  private static class ChebyquadFunction extends MinpackFunction {
+
+    private static double[] buildChebyquadArray(int n, double factor) {
+      double[] array = new double[n];
+      double inv = factor / (n + 1);
+      for (int i = 0; i < n; ++i) {
+        array[i] = (i + 1) * inv;
+      }
+      return array;
+    }
+
+    public ChebyquadFunction(int n, int m, double factor,
+                             double theoreticalStartCost,
+                             double theoreticalMinCost,
+                             double[] theoreticalMinParams) {
+      super(m, buildChebyquadArray(n, factor), theoreticalStartCost,
+            theoreticalMinCost, theoreticalMinParams);
+    }
+
+    @Override
+    protected double[][] getJacobian() {
+
+      double[][] jacobian = new double[m][];
+      for (int i = 0; i < m; ++i) {
+        jacobian[i] = new double[n];
+      }
+
+      double dx = 1.0 / n;
+      for (int j = 0; j < n; ++j) {
+        double tmp1 = 1;
+        double tmp2 = 2 * parameters[j].getEstimate() - 1;
+        double temp = 2 * tmp2;
+        double tmp3 = 0;
+        double tmp4 = 2;
+        for (int i = 0; i < m; ++i) {
+          jacobian[i][j] = dx * tmp4;
+          double ti = 4 * tmp2 + temp * tmp4 - tmp3;
+          tmp3 = tmp4;
+          tmp4 = ti;
+          ti   = temp * tmp2 - tmp1;
+          tmp1 = tmp2;
+          tmp2 = ti;
+        }
+      }
+
+      return jacobian;
+
+    }
+
+    @Override
+    protected double[] getResiduals() {
+
+      double[] f = new double[m];
+
+      for (int j = 0; j < n; ++j) {
+        double tmp1 = 1;
+        double tmp2 = 2 * parameters[j].getEstimate() - 1;
+        double temp = 2 * tmp2;
+        for (int i = 0; i < m; ++i) {
+          f[i] += tmp2;
+          double ti = temp * tmp2 - tmp1;
+          tmp1 = tmp2;
+          tmp2 = ti;
+        }
+      }
+
+      double dx = 1.0 / n;
+      boolean iev = false;
+      for (int i = 0; i < m; ++i) {
+        f[i] *= dx;
+        if (iev) {
+          f[i] += 1.0 / (i * (i + 2));
+        }
+        iev = ! iev;
+      }
+
+      return f;
+
+    }
+
+  }
+
+  private static class BrownAlmostLinearFunction extends MinpackFunction {
+
+    public BrownAlmostLinearFunction(int m, double factor,
+                                     double theoreticalStartCost,
+                                     double theoreticalMinCost,
+                                     double[] theoreticalMinParams) {
+      super(m, buildArray(m, factor), theoreticalStartCost,
+            theoreticalMinCost, theoreticalMinParams);
+    }
+
+    @Override
+    protected double[][] getJacobian() {
+      double[][] jacobian = new double[m][];
+      for (int i = 0; i < m; ++i) {
+        jacobian[i] = new double[n];
+      }
+
+      double prod = 1;
+      for (int j = 0; j < n; ++j) {
+        prod *= parameters[j].getEstimate();
+        for (int i = 0; i < n; ++i) {
+          jacobian[i][j] = 1;
+        }
+        jacobian[j][j] = 2;
+      }
+
+      for (int j = 0; j < n; ++j) {
+        EstimatedParameter vj = parameters[j];
+        double temp = vj.getEstimate();
+        if (temp == 0) {
+          temp = 1;
+          prod = 1;
+          for (int k = 0; k < n; ++k) {
+            if (k != j) {
+              prod *= parameters[k].getEstimate();
+            }
+          }
+        }
+        jacobian[n - 1][j] = prod / temp;
+      }
+
+      return jacobian;
+
+    }
+
+    @Override
+    protected double[] getResiduals() {
+      double[] f = new double[m];
+      double sum  = -(n + 1);
+      double prod = 1;
+      for (int j = 0; j < n; ++j) {
+        sum  += parameters[j].getEstimate();
+        prod *= parameters[j].getEstimate();
+      }
+      for (int i = 0; i < n; ++i) {
+        f[i] = parameters[i].getEstimate() + sum;
+      }
+      f[n - 1] = prod - 1;
+      return f;
+    }
+
+  }
+
+  private static class Osborne1Function extends MinpackFunction {
+
+    public Osborne1Function(double[] startParams,
+                            double theoreticalStartCost,
+                            double theoreticalMinCost,
+                            double[] theoreticalMinParams) {
+      super(33, startParams, theoreticalStartCost,
+            theoreticalMinCost, theoreticalMinParams);
+    }
+
+    @Override
+    protected double[][] getJacobian() {
+      double   x2 = parameters[1].getEstimate();
+      double   x3 = parameters[2].getEstimate();
+      double   x4 = parameters[3].getEstimate();
+      double   x5 = parameters[4].getEstimate();
+      double[][] jacobian = new double[m][];
+      for (int i = 0; i < m; ++i) {
+        double temp = 10.0 * i;
+        double tmp1 = FastMath.exp(-temp * x4);
+        double tmp2 = FastMath.exp(-temp * x5);
+        jacobian[i] = new double[] {
+          -1, -tmp1, -tmp2, temp * x2 * tmp1, temp * x3 * tmp2
+        };
+      }
+      return jacobian;
+    }
+
+    @Override
+    protected double[] getResiduals() {
+      double x1 = parameters[0].getEstimate();
+      double x2 = parameters[1].getEstimate();
+      double x3 = parameters[2].getEstimate();
+      double x4 = parameters[3].getEstimate();
+      double x5 = parameters[4].getEstimate();
+      double[] f = new double[m];
+      for (int i = 0; i < m; ++i) {
+        double temp = 10.0 * i;
+        double tmp1 = FastMath.exp(-temp * x4);
+        double tmp2 = FastMath.exp(-temp * x5);
+        f[i] = y[i] - (x1 + x2 * tmp1 + x3 * tmp2);
+      }
+      return f;
+    }
+
+    private static final double[] y = {
+      0.844, 0.908, 0.932, 0.936, 0.925, 0.908, 0.881, 0.850, 0.818, 0.784, 0.751,
+      0.718, 0.685, 0.658, 0.628, 0.603, 0.580, 0.558, 0.538, 0.522, 0.506, 0.490,
+      0.478, 0.467, 0.457, 0.448, 0.438, 0.431, 0.424, 0.420, 0.414, 0.411, 0.406
+    };
+
+  }
+
+  private static class Osborne2Function extends MinpackFunction {
+
+    public Osborne2Function(double[] startParams,
+                            double theoreticalStartCost,
+                            double theoreticalMinCost,
+                            double[] theoreticalMinParams) {
+      super(65, startParams, theoreticalStartCost,
+            theoreticalMinCost, theoreticalMinParams);
+    }
+
+    @Override
+    protected double[][] getJacobian() {
+      double   x01 = parameters[0].getEstimate();
+      double   x02 = parameters[1].getEstimate();
+      double   x03 = parameters[2].getEstimate();
+      double   x04 = parameters[3].getEstimate();
+      double   x05 = parameters[4].getEstimate();
+      double   x06 = parameters[5].getEstimate();
+      double   x07 = parameters[6].getEstimate();
+      double   x08 = parameters[7].getEstimate();
+      double   x09 = parameters[8].getEstimate();
+      double   x10 = parameters[9].getEstimate();
+      double   x11 = parameters[10].getEstimate();
+      double[][] jacobian = new double[m][];
+      for (int i = 0; i < m; ++i) {
+        double temp = i / 10.0;
+        double tmp1 = FastMath.exp(-x05 * temp);
+        double tmp2 = FastMath.exp(-x06 * (temp - x09) * (temp - x09));
+        double tmp3 = FastMath.exp(-x07 * (temp - x10) * (temp - x10));
+        double tmp4 = FastMath.exp(-x08 * (temp - x11) * (temp - x11));
+        jacobian[i] = new double[] {
+          -tmp1,
+          -tmp2,
+          -tmp3,
+          -tmp4,
+          temp * x01 * tmp1,
+          x02 * (temp - x09) * (temp - x09) * tmp2,
+          x03 * (temp - x10) * (temp - x10) * tmp3,
+          x04 * (temp - x11) * (temp - x11) * tmp4,
+          -2 * x02 * x06 * (temp - x09) * tmp2,
+          -2 * x03 * x07 * (temp - x10) * tmp3,
+          -2 * x04 * x08 * (temp - x11) * tmp4
+        };
+      }
+      return jacobian;
+    }
+
+    @Override
+    protected double[] getResiduals() {
+      double x01 = parameters[0].getEstimate();
+      double x02 = parameters[1].getEstimate();
+      double x03 = parameters[2].getEstimate();
+      double x04 = parameters[3].getEstimate();
+      double x05 = parameters[4].getEstimate();
+      double x06 = parameters[5].getEstimate();
+      double x07 = parameters[6].getEstimate();
+      double x08 = parameters[7].getEstimate();
+      double x09 = parameters[8].getEstimate();
+      double x10 = parameters[9].getEstimate();
+      double x11 = parameters[10].getEstimate();
+      double[] f = new double[m];
+      for (int i = 0; i < m; ++i) {
+        double temp = i / 10.0;
+        double tmp1 = FastMath.exp(-x05 * temp);
+        double tmp2 = FastMath.exp(-x06 * (temp - x09) * (temp - x09));
+        double tmp3 = FastMath.exp(-x07 * (temp - x10) * (temp - x10));
+        double tmp4 = FastMath.exp(-x08 * (temp - x11) * (temp - x11));
+        f[i] = y[i] - (x01 * tmp1 + x02 * tmp2 + x03 * tmp3 + x04 * tmp4);
+      }
+      return f;
+    }
+
+    private static final double[] y = {
+      1.366, 1.191, 1.112, 1.013, 0.991,
+      0.885, 0.831, 0.847, 0.786, 0.725,
+      0.746, 0.679, 0.608, 0.655, 0.616,
+      0.606, 0.602, 0.626, 0.651, 0.724,
+      0.649, 0.649, 0.694, 0.644, 0.624,
+      0.661, 0.612, 0.558, 0.533, 0.495,
+      0.500, 0.423, 0.395, 0.375, 0.372,
+      0.391, 0.396, 0.405, 0.428, 0.429,
+      0.523, 0.562, 0.607, 0.653, 0.672,
+      0.708, 0.633, 0.668, 0.645, 0.632,
+      0.591, 0.559, 0.597, 0.625, 0.739,
+      0.710, 0.729, 0.720, 0.636, 0.581,
+      0.428, 0.292, 0.162, 0.098, 0.054
+    };
+
+  }
+
+}
diff --git a/src/test/java/org/apache/commons/math/estimation/WeightedMeasurementTest.java b/src/test/java/org/apache/commons/math/estimation/WeightedMeasurementTest.java
new file mode 100644
index 0000000..c20b0ce
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/estimation/WeightedMeasurementTest.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.estimation;
+
+import org.apache.commons.math.estimation.EstimatedParameter;
+import org.apache.commons.math.estimation.WeightedMeasurement;
+import org.apache.commons.math.util.FastMath;
+
+import junit.framework.*;
+
+ at Deprecated
+public class WeightedMeasurementTest
+  extends TestCase {
+
+  public WeightedMeasurementTest(String name) {
+    super(name);
+    p1 = null;
+    p2 = null;
+  }
+
+  public void testConstruction() {
+    WeightedMeasurement m = new MyMeasurement(3.0, theoretical() + 0.1, this);
+    checkValue(m.getWeight(), 3.0);
+    checkValue(m.getMeasuredValue(), theoretical() + 0.1);
+  }
+
+  public void testIgnored() {
+    WeightedMeasurement m = new MyMeasurement(3.0, theoretical() + 0.1, this);
+    assertTrue(!m.isIgnored());
+    m.setIgnored(true);
+    assertTrue(m.isIgnored());
+    m.setIgnored(false);
+    assertTrue(!m.isIgnored());
+  }
+
+  public void testTheory() {
+    WeightedMeasurement m = new MyMeasurement(3.0, theoretical() + 0.1, this);
+    checkValue(m.getTheoreticalValue(), theoretical());
+    checkValue(m.getResidual(), 0.1);
+
+    double oldP1 = p1.getEstimate();
+    p1.setEstimate(oldP1 + m.getResidual() / m.getPartial(p1));
+    checkValue(m.getResidual(), 0.0);
+    p1.setEstimate(oldP1);
+    checkValue(m.getResidual(), 0.1);
+
+    double oldP2 = p2.getEstimate();
+    p2.setEstimate(oldP2 + m.getResidual() / m.getPartial(p2));
+    checkValue(m.getResidual(), 0.0);
+    p2.setEstimate(oldP2);
+    checkValue(m.getResidual(), 0.1);
+
+  }
+
+  @Override
+  public void setUp() {
+    p1 = new EstimatedParameter("p1", 1.0);
+    p2 = new EstimatedParameter("p2", 2.0);
+  }
+
+  @Override
+  public void tearDown() {
+    p1 = null;
+    p2 = null;
+  }
+
+  private void checkValue(double value, double expected) {
+   assertTrue(FastMath.abs(value - expected) < 1.0e-10);
+  }
+
+  private double theoretical() {
+   return 3 * p1.getEstimate() - p2.getEstimate();
+  }
+
+  private double partial(EstimatedParameter p) {
+    if (p == p1) {
+      return 3.0;
+    } else if (p == p2) {
+      return -1.0;
+    } else {
+      return 0.0;
+    }
+  }
+
+  private static class MyMeasurement
+    extends WeightedMeasurement {
+
+    public MyMeasurement(double weight, double measuredValue,
+                         WeightedMeasurementTest testInstance) {
+      super(weight, measuredValue);
+      this.testInstance = testInstance;
+    }
+
+    @Override
+    public double getTheoreticalValue() {
+      return testInstance.theoretical();
+    }
+
+    @Override
+    public double getPartial(EstimatedParameter p) {
+      return testInstance.partial(p);
+    }
+
+    private transient WeightedMeasurementTest testInstance;
+
+    private static final long serialVersionUID = -246712922500792332L;
+
+  }
+
+  private EstimatedParameter p1;
+  private EstimatedParameter p2;
+
+}
diff --git a/src/test/java/org/apache/commons/math/exception/DimensionMismatchExceptionTest.java b/src/test/java/org/apache/commons/math/exception/DimensionMismatchExceptionTest.java
new file mode 100644
index 0000000..5ba51f9
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/exception/DimensionMismatchExceptionTest.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.exception;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Test for {@link DimensionMismatchException}.
+ * 
+ * @version $Revision$ $Date$ 
+ */
+public class DimensionMismatchExceptionTest {
+    @Test
+    public void testAccessors() {
+        final DimensionMismatchException e = new DimensionMismatchException(1, 2);
+        Assert.assertEquals(1, e.getArgument());
+        Assert.assertEquals(2, e.getDimension());
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/exception/NonMonotonousSequenceExceptionTest.java b/src/test/java/org/apache/commons/math/exception/NonMonotonousSequenceExceptionTest.java
new file mode 100644
index 0000000..374c4bf
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/exception/NonMonotonousSequenceExceptionTest.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.util.MathUtils;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Test for {@link NonMonotonousSequenceException}.
+ * 
+ * @version $Revision$ $Date$ 
+ */
+public class NonMonotonousSequenceExceptionTest {
+    @Test
+    public void testAccessors() {
+        NonMonotonousSequenceException e
+            = new NonMonotonousSequenceException(0, -1, 1, MathUtils.OrderDirection.DECREASING, false);
+        Assert.assertEquals(0, e.getArgument());
+        Assert.assertEquals(-1, e.getPrevious());
+        Assert.assertEquals(1, e.getIndex());
+        Assert.assertTrue(e.getDirection() == MathUtils.OrderDirection.DECREASING);
+        Assert.assertFalse(e.getStrict());
+
+        e = new NonMonotonousSequenceException(-1, 0, 1);
+        Assert.assertEquals(-1, e.getArgument());
+        Assert.assertEquals(0, e.getPrevious());
+        Assert.assertEquals(1, e.getIndex());
+        Assert.assertTrue(e.getDirection() == MathUtils.OrderDirection.INCREASING);
+        Assert.assertTrue(e.getStrict());
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/exception/NotPositiveExceptionTest.java b/src/test/java/org/apache/commons/math/exception/NotPositiveExceptionTest.java
new file mode 100644
index 0000000..f8a7373
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/exception/NotPositiveExceptionTest.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.exception;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Test for {@link NotPositiveException}.
+ * 
+ * @version $Revision$ $Date$ 
+ */
+public class NotPositiveExceptionTest {
+    @Test
+    public void testAccessors() {
+        final NotPositiveException e = new NotPositiveException(-1);
+        Assert.assertEquals(-1, e.getArgument());
+        Assert.assertEquals(0, e.getMin());
+        Assert.assertTrue(e.getBoundIsAllowed());
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/exception/NotStrictlyPositiveExceptionTest.java b/src/test/java/org/apache/commons/math/exception/NotStrictlyPositiveExceptionTest.java
new file mode 100644
index 0000000..9f48e4d
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/exception/NotStrictlyPositiveExceptionTest.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.exception;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Test for {@link NotStrictlyPositiveException}.
+ * 
+ * @version $Revision$ $Date$ 
+ */
+public class NotStrictlyPositiveExceptionTest {
+    @Test
+    public void testAccessors() {
+        final NotStrictlyPositiveException e = new NotStrictlyPositiveException(0);
+        Assert.assertEquals(0, e.getArgument());
+        Assert.assertEquals(0, e.getMin());
+        Assert.assertFalse(e.getBoundIsAllowed());
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/exception/NumberIsTooLargeExceptionTest.java b/src/test/java/org/apache/commons/math/exception/NumberIsTooLargeExceptionTest.java
new file mode 100644
index 0000000..f199fca
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/exception/NumberIsTooLargeExceptionTest.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.exception;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Test for {@link NumberIsTooLargeException}.
+ * 
+ * @version $Revision$ $Date$ 
+ */
+public class NumberIsTooLargeExceptionTest {
+    @Test
+    public void testAccessors() {
+        final NumberIsTooLargeException e = new NumberIsTooLargeException(1, 0, true);
+        Assert.assertEquals(1, e.getArgument());
+        Assert.assertEquals(0, e.getMax());
+        Assert.assertTrue(e.getBoundIsAllowed());
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/exception/NumberIsTooSmallExceptionTest.java b/src/test/java/org/apache/commons/math/exception/NumberIsTooSmallExceptionTest.java
new file mode 100644
index 0000000..95c6575
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/exception/NumberIsTooSmallExceptionTest.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.exception;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Test for {@link NumberIsTooSmallException}.
+ * 
+ * @version $Revision$ $Date$ 
+ */
+public class NumberIsTooSmallExceptionTest {
+    @Test
+    public void testAccessors() {
+        final NumberIsTooSmallException e = new NumberIsTooSmallException(0, 0, false);
+        Assert.assertEquals(0, e.getArgument());
+        Assert.assertEquals(0, e.getMin());
+        Assert.assertFalse(e.getBoundIsAllowed());
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/exception/OutOfRangeExceptionTest.java b/src/test/java/org/apache/commons/math/exception/OutOfRangeExceptionTest.java
new file mode 100644
index 0000000..5d6153c
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/exception/OutOfRangeExceptionTest.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.exception;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Test for {@link OutOfRangeException}.
+ * 
+ * @version $Revision$ $Date$ 
+ */
+public class OutOfRangeExceptionTest {
+    @Test
+    public void testAccessors() {
+        final OutOfRangeException e = new OutOfRangeException(-1, 0, 2);
+        Assert.assertEquals(-1, e.getArgument());
+        Assert.assertEquals(0, e.getLo());
+        Assert.assertEquals(2, e.getHi());
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/exception/util/ArgUtilsTest.java b/src/test/java/org/apache/commons/math/exception/util/ArgUtilsTest.java
new file mode 100644
index 0000000..34c4d85
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/exception/util/ArgUtilsTest.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.exception.util;
+
+import java.util.List;
+import java.util.ArrayList;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Test for {@link ArgUtils}.
+ * 
+ * @version $Revision$ $Date$ 
+ */
+public class ArgUtilsTest {
+    @Test
+    public void testFlatten() {
+        final List<Object> orig = new ArrayList<Object>();
+
+        final Object[] struct = new Object[] {
+            new Object[] {
+                new Object[] {
+                    create(orig),
+                    create(orig),
+                },
+                create(orig),
+                new Object[] {
+                    create(orig),
+                }
+            },
+            create(orig),
+            new Object[] {
+                create(orig),
+                new Object[] {
+                    create(orig),
+                    create(orig),
+                }
+            },
+            create(orig),
+        };
+
+        Object[] flat = ArgUtils.flatten(struct);
+        Assert.assertEquals(flat.length, orig.size());
+
+        for (int i = 0, max = orig.size(); i < max; i++) {
+            Assert.assertEquals(orig.get(i), flat[i]);
+        }
+    }
+
+    /**
+     * Create and store an {@code Object}.
+     *
+     * @param list List to store to.
+     * @return the stored object.
+     */
+    private Object create(List<Object> list) {
+        final Object o = new Object();
+        list.add(o);
+        return o;
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/exception/util/MessageFactoryTest.java b/src/test/java/org/apache/commons/math/exception/util/MessageFactoryTest.java
new file mode 100644
index 0000000..d0d147c
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/exception/util/MessageFactoryTest.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.exception.util;
+
+import java.util.Locale;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class MessageFactoryTest {
+
+    @Test
+    public void testSpecificGeneral() {
+        Localizable specific = new DummyLocalizable("specific {0} - {1} - {2}");
+        Localizable general  = new DummyLocalizable("general  {0} / {1}");
+        String message = MessageFactory.buildMessage(Locale.FRENCH, specific, general,
+                                                     0, 1, 2, 'a', 'b');
+        Assert.assertEquals("general  0 / 1: specific 0 - 1 - 2", message);
+    }
+
+    @Test
+    public void testNullSpecific() {
+        Localizable general  = new DummyLocalizable("general  {0} / {1}");
+        String message = MessageFactory.buildMessage(Locale.FRENCH, null, general,
+                                                     0, 1, 2, 'a', 'b');
+        Assert.assertEquals("general  0 / 1", message);
+    }
+
+    @Test
+    public void testNullGeneral() {
+        Localizable specific = new DummyLocalizable("specific {0} - {1} - {2}");
+        String message = MessageFactory.buildMessage(Locale.FRENCH, specific, null,
+                                                     0, 1, 2, 'a', 'b');
+        Assert.assertEquals("specific 0 - 1 - 2", message);
+    }
+
+    @Test
+    public void testNull() {
+        String message = MessageFactory.buildMessage(Locale.FRENCH, null, null, "nothing");
+        Assert.assertEquals("", message);
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/fraction/BigFractionFieldTest.java b/src/test/java/org/apache/commons/math/fraction/BigFractionFieldTest.java
new file mode 100644
index 0000000..02c10e8
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/fraction/BigFractionFieldTest.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.fraction;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.commons.math.TestUtils;
+import org.junit.Test;
+
+public class BigFractionFieldTest {
+
+    @Test
+    public void testZero() {
+        assertEquals(BigFraction.ZERO, BigFractionField.getInstance().getZero());
+    }
+
+    @Test
+    public void testOne() {
+        assertEquals(BigFraction.ONE, BigFractionField.getInstance().getOne());
+    }
+
+    @Test
+    public void testSerial() {
+        // deserializing the singleton should give the singleton itself back
+        BigFractionField field = BigFractionField.getInstance();
+        assertTrue(field == TestUtils.serializeAndRecover(field));
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/fraction/BigFractionFormatTest.java b/src/test/java/org/apache/commons/math/fraction/BigFractionFormatTest.java
new file mode 100644
index 0000000..391b758
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/fraction/BigFractionFormatTest.java
@@ -0,0 +1,323 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.BigDecimal;
+import java.math.BigInteger;
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.util.Locale;
+
+import org.apache.commons.math.util.FastMath;
+
+import junit.framework.TestCase;
+
+public class BigFractionFormatTest extends TestCase {
+
+    BigFractionFormat properFormat = null;
+    BigFractionFormat improperFormat = null;
+
+    protected Locale getLocale() {
+        return Locale.getDefault();
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        properFormat = BigFractionFormat.getProperInstance(getLocale());
+        improperFormat = BigFractionFormat.getImproperInstance(getLocale());
+    }
+
+    public void testFormat() {
+        BigFraction c = new BigFraction(1, 2);
+        String expected = "1 / 2";
+
+        String actual = properFormat.format(c);
+        assertEquals(expected, actual);
+
+        actual = improperFormat.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void testFormatNegative() {
+        BigFraction c = new BigFraction(-1, 2);
+        String expected = "-1 / 2";
+
+        String actual = properFormat.format(c);
+        assertEquals(expected, actual);
+
+        actual = improperFormat.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void testFormatZero() {
+        BigFraction c = new BigFraction(0, 1);
+        String expected = "0 / 1";
+
+        String actual = properFormat.format(c);
+        assertEquals(expected, actual);
+
+        actual = improperFormat.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void testFormatImproper() {
+        BigFraction c = new BigFraction(5, 3);
+
+        String actual = properFormat.format(c);
+        assertEquals("1 2 / 3", actual);
+
+        actual = improperFormat.format(c);
+        assertEquals("5 / 3", actual);
+    }
+
+    public void testFormatImproperNegative() {
+        BigFraction c = new BigFraction(-5, 3);
+
+        String actual = properFormat.format(c);
+        assertEquals("-1 2 / 3", actual);
+
+        actual = improperFormat.format(c);
+        assertEquals("-5 / 3", actual);
+    }
+
+    public void testParse() {
+        String source = "1 / 2";
+
+        try {
+            BigFraction c = properFormat.parse(source);
+            assertNotNull(c);
+            assertEquals(BigInteger.ONE, c.getNumerator());
+            assertEquals(BigInteger.valueOf(2l), c.getDenominator());
+
+            c = improperFormat.parse(source);
+            assertNotNull(c);
+            assertEquals(BigInteger.ONE, c.getNumerator());
+            assertEquals(BigInteger.valueOf(2l), c.getDenominator());
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParseInteger() {
+        String source = "10";
+        try {
+            BigFraction c = properFormat.parse(source);
+            assertNotNull(c);
+            assertEquals(BigInteger.TEN, c.getNumerator());
+            assertEquals(BigInteger.ONE, c.getDenominator());
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+        try {
+            BigFraction c = improperFormat.parse(source);
+            assertNotNull(c);
+            assertEquals(BigInteger.TEN, c.getNumerator());
+            assertEquals(BigInteger.ONE, c.getDenominator());
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParseInvalid() {
+        String source = "a";
+        String msg = "should not be able to parse '10 / a'.";
+        try {
+            properFormat.parse(source);
+            fail(msg);
+        } catch (ParseException ex) {
+            // success
+        }
+        try {
+            improperFormat.parse(source);
+            fail(msg);
+        } catch (ParseException ex) {
+            // success
+        }
+    }
+
+    public void testParseInvalidDenominator() {
+        String source = "10 / a";
+        String msg = "should not be able to parse '10 / a'.";
+        try {
+            properFormat.parse(source);
+            fail(msg);
+        } catch (ParseException ex) {
+            // success
+        }
+        try {
+            improperFormat.parse(source);
+            fail(msg);
+        } catch (ParseException ex) {
+            // success
+        }
+    }
+
+    public void testParseNegative() {
+
+        try {
+            String source = "-1 / 2";
+            BigFraction c = properFormat.parse(source);
+            assertNotNull(c);
+            assertEquals(-1, c.getNumeratorAsInt());
+            assertEquals(2, c.getDenominatorAsInt());
+
+            c = improperFormat.parse(source);
+            assertNotNull(c);
+            assertEquals(-1, c.getNumeratorAsInt());
+            assertEquals(2, c.getDenominatorAsInt());
+
+            source = "1 / -2";
+            c = properFormat.parse(source);
+            assertNotNull(c);
+            assertEquals(-1, c.getNumeratorAsInt());
+            assertEquals(2, c.getDenominatorAsInt());
+
+            c = improperFormat.parse(source);
+            assertNotNull(c);
+            assertEquals(-1, c.getNumeratorAsInt());
+            assertEquals(2, c.getDenominatorAsInt());
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParseProper() {
+        String source = "1 2 / 3";
+
+        try {
+            BigFraction c = properFormat.parse(source);
+            assertNotNull(c);
+            assertEquals(5, c.getNumeratorAsInt());
+            assertEquals(3, c.getDenominatorAsInt());
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+
+        try {
+            improperFormat.parse(source);
+            fail("invalid improper fraction.");
+        } catch (ParseException ex) {
+            // success
+        }
+    }
+
+    public void testParseProperNegative() {
+        String source = "-1 2 / 3";
+        try {
+            BigFraction c = properFormat.parse(source);
+            assertNotNull(c);
+            assertEquals(-5, c.getNumeratorAsInt());
+            assertEquals(3, c.getDenominatorAsInt());
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+
+        try {
+            improperFormat.parse(source);
+            fail("invalid improper fraction.");
+        } catch (ParseException ex) {
+            // success
+        }
+    }
+
+    public void testParseProperInvalidMinus() {
+        String source = "2 -2 / 3";
+        try {
+            properFormat.parse(source);
+            fail("invalid minus in improper fraction.");
+        } catch (ParseException ex) {
+            // expected
+        }
+        source = "2 2 / -3";
+        try {
+            properFormat.parse(source);
+            fail("invalid minus in improper fraction.");
+        } catch (ParseException ex) {
+            // expected
+        }
+    }
+
+    public void testParseBig() throws ParseException {
+        BigFraction f1 =
+            improperFormat.parse("167213075789791382630275400487886041651764456874403" +
+                                 " / " +
+                                 "53225575123090058458126718248444563466137046489291");
+        assertEquals(FastMath.PI, f1.doubleValue(), 0.0);
+        BigFraction f2 =
+            properFormat.parse("3 " +
+                               "7536350420521207255895245742552351253353317406530" +
+                               " / " +
+                               "53225575123090058458126718248444563466137046489291");
+        assertEquals(FastMath.PI, f2.doubleValue(), 0.0);
+        assertEquals(f1, f2);
+        BigDecimal pi =
+            new BigDecimal("3.141592653589793238462643383279502884197169399375105820974944592307816406286208998628034825342117068");
+        assertEquals(pi, f1.bigDecimalValue(99, BigDecimal.ROUND_HALF_EVEN));
+    }
+
+    public void testNumeratorFormat() {
+        NumberFormat old = properFormat.getNumeratorFormat();
+        NumberFormat nf = NumberFormat.getInstance();
+        nf.setParseIntegerOnly(true);
+        properFormat.setNumeratorFormat(nf);
+        assertEquals(nf, properFormat.getNumeratorFormat());
+        properFormat.setNumeratorFormat(old);
+
+        old = improperFormat.getNumeratorFormat();
+        nf = NumberFormat.getInstance();
+        nf.setParseIntegerOnly(true);
+        improperFormat.setNumeratorFormat(nf);
+        assertEquals(nf, improperFormat.getNumeratorFormat());
+        improperFormat.setNumeratorFormat(old);
+    }
+
+    public void testDenominatorFormat() {
+        NumberFormat old = properFormat.getDenominatorFormat();
+        NumberFormat nf = NumberFormat.getInstance();
+        nf.setParseIntegerOnly(true);
+        properFormat.setDenominatorFormat(nf);
+        assertEquals(nf, properFormat.getDenominatorFormat());
+        properFormat.setDenominatorFormat(old);
+
+        old = improperFormat.getDenominatorFormat();
+        nf = NumberFormat.getInstance();
+        nf.setParseIntegerOnly(true);
+        improperFormat.setDenominatorFormat(nf);
+        assertEquals(nf, improperFormat.getDenominatorFormat());
+        improperFormat.setDenominatorFormat(old);
+    }
+
+    public void testWholeFormat() {
+        ProperBigFractionFormat format = (ProperBigFractionFormat)properFormat;
+
+        NumberFormat old = format.getWholeFormat();
+        NumberFormat nf = NumberFormat.getInstance();
+        nf.setParseIntegerOnly(true);
+        format.setWholeFormat(nf);
+        assertEquals(nf, format.getWholeFormat());
+        format.setWholeFormat(old);
+    }
+
+    public void testLongFormat() {
+        assertEquals("10 / 1", improperFormat.format(10l));
+    }
+
+    public void testDoubleFormat() {
+        assertEquals("1 / 16", improperFormat.format(0.0625));
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/fraction/BigFractionTest.java b/src/test/java/org/apache/commons/math/fraction/BigFractionTest.java
new file mode 100644
index 0000000..70096e2
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/fraction/BigFractionTest.java
@@ -0,0 +1,577 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.fraction;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import org.apache.commons.math.ConvergenceException;
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.util.FastMath;
+
+import junit.framework.TestCase;
+
+public class BigFractionTest extends TestCase {
+
+    private void assertFraction(int expectedNumerator, int expectedDenominator, BigFraction actual) {
+        assertEquals(expectedNumerator, actual.getNumeratorAsInt());
+        assertEquals(expectedDenominator, actual.getDenominatorAsInt());
+    }
+
+    private void assertFraction(long expectedNumerator, long expectedDenominator, BigFraction actual) {
+        assertEquals(expectedNumerator, actual.getNumeratorAsLong());
+        assertEquals(expectedDenominator, actual.getDenominatorAsLong());
+    }
+
+    public void testConstructor() {
+        assertFraction(0, 1, new BigFraction(0, 1));
+        assertFraction(0, 1, new BigFraction(0l, 2l));
+        assertFraction(0, 1, new BigFraction(0, -1));
+        assertFraction(1, 2, new BigFraction(1, 2));
+        assertFraction(1, 2, new BigFraction(2, 4));
+        assertFraction(-1, 2, new BigFraction(-1, 2));
+        assertFraction(-1, 2, new BigFraction(1, -2));
+        assertFraction(-1, 2, new BigFraction(-2, 4));
+        assertFraction(-1, 2, new BigFraction(2, -4));
+        assertFraction(11, 1, new BigFraction(11));
+        assertFraction(11, 1, new BigFraction(11l));
+        assertFraction(11, 1, new BigFraction(new BigInteger("11")));
+
+        try {
+            assertFraction(0, 1, new BigFraction(0.00000000000001, 1.0e-5, 100));
+            assertFraction(2, 5, new BigFraction(0.40000000000001, 1.0e-5, 100));
+            assertFraction(15, 1, new BigFraction(15.0000000000001, 1.0e-5, 100));
+        } catch (ConvergenceException ex) {
+            fail(ex.getMessage());
+        }
+        assertEquals(0.00000000000001, new BigFraction(0.00000000000001).doubleValue(), 0.0);
+        assertEquals(0.40000000000001, new BigFraction(0.40000000000001).doubleValue(), 0.0);
+        assertEquals(15.0000000000001, new BigFraction(15.0000000000001).doubleValue(), 0.0);
+        assertFraction(3602879701896487l, 9007199254740992l, new BigFraction(0.40000000000001));
+        assertFraction(1055531162664967l, 70368744177664l, new BigFraction(15.0000000000001));
+        try {
+            new BigFraction(null, BigInteger.ONE);
+            fail("Expecting NullPointerException");
+        } catch (NullPointerException npe) {
+            // expected
+        }
+        try {
+            new BigFraction(BigInteger.ONE, null);
+            fail("Expecting NullPointerException");
+        } catch (NullPointerException npe) {
+            // expected
+        }
+        try {
+            new BigFraction(BigInteger.ONE, BigInteger.ZERO);
+            fail("Expecting ArithmeticException");
+        } catch (ArithmeticException npe) {
+            // expected
+        }
+        try {
+            new BigFraction(2.0 * Integer.MAX_VALUE, 1.0e-5, 100000);
+            fail("Expecting FractionConversionException");
+        } catch (FractionConversionException fce) {
+            // expected
+        }
+    }
+
+    public void testGoldenRatio() {
+        try {
+            // the golden ratio is notoriously a difficult number for continuous
+            // fraction
+            new BigFraction((1 + FastMath.sqrt(5)) / 2, 1.0e-12, 25);
+            fail("an exception should have been thrown");
+        } catch (ConvergenceException ce) {
+            // expected behavior
+        }
+    }
+
+    // MATH-179
+    public void testDoubleConstructor() throws ConvergenceException {
+        assertFraction(1, 2, new BigFraction((double) 1 / (double) 2, 1.0e-5, 100));
+        assertFraction(1, 3, new BigFraction((double) 1 / (double) 3, 1.0e-5, 100));
+        assertFraction(2, 3, new BigFraction((double) 2 / (double) 3, 1.0e-5, 100));
+        assertFraction(1, 4, new BigFraction((double) 1 / (double) 4, 1.0e-5, 100));
+        assertFraction(3, 4, new BigFraction((double) 3 / (double) 4, 1.0e-5, 100));
+        assertFraction(1, 5, new BigFraction((double) 1 / (double) 5, 1.0e-5, 100));
+        assertFraction(2, 5, new BigFraction((double) 2 / (double) 5, 1.0e-5, 100));
+        assertFraction(3, 5, new BigFraction((double) 3 / (double) 5, 1.0e-5, 100));
+        assertFraction(4, 5, new BigFraction((double) 4 / (double) 5, 1.0e-5, 100));
+        assertFraction(1, 6, new BigFraction((double) 1 / (double) 6, 1.0e-5, 100));
+        assertFraction(5, 6, new BigFraction((double) 5 / (double) 6, 1.0e-5, 100));
+        assertFraction(1, 7, new BigFraction((double) 1 / (double) 7, 1.0e-5, 100));
+        assertFraction(2, 7, new BigFraction((double) 2 / (double) 7, 1.0e-5, 100));
+        assertFraction(3, 7, new BigFraction((double) 3 / (double) 7, 1.0e-5, 100));
+        assertFraction(4, 7, new BigFraction((double) 4 / (double) 7, 1.0e-5, 100));
+        assertFraction(5, 7, new BigFraction((double) 5 / (double) 7, 1.0e-5, 100));
+        assertFraction(6, 7, new BigFraction((double) 6 / (double) 7, 1.0e-5, 100));
+        assertFraction(1, 8, new BigFraction((double) 1 / (double) 8, 1.0e-5, 100));
+        assertFraction(3, 8, new BigFraction((double) 3 / (double) 8, 1.0e-5, 100));
+        assertFraction(5, 8, new BigFraction((double) 5 / (double) 8, 1.0e-5, 100));
+        assertFraction(7, 8, new BigFraction((double) 7 / (double) 8, 1.0e-5, 100));
+        assertFraction(1, 9, new BigFraction((double) 1 / (double) 9, 1.0e-5, 100));
+        assertFraction(2, 9, new BigFraction((double) 2 / (double) 9, 1.0e-5, 100));
+        assertFraction(4, 9, new BigFraction((double) 4 / (double) 9, 1.0e-5, 100));
+        assertFraction(5, 9, new BigFraction((double) 5 / (double) 9, 1.0e-5, 100));
+        assertFraction(7, 9, new BigFraction((double) 7 / (double) 9, 1.0e-5, 100));
+        assertFraction(8, 9, new BigFraction((double) 8 / (double) 9, 1.0e-5, 100));
+        assertFraction(1, 10, new BigFraction((double) 1 / (double) 10, 1.0e-5, 100));
+        assertFraction(3, 10, new BigFraction((double) 3 / (double) 10, 1.0e-5, 100));
+        assertFraction(7, 10, new BigFraction((double) 7 / (double) 10, 1.0e-5, 100));
+        assertFraction(9, 10, new BigFraction((double) 9 / (double) 10, 1.0e-5, 100));
+        assertFraction(1, 11, new BigFraction((double) 1 / (double) 11, 1.0e-5, 100));
+        assertFraction(2, 11, new BigFraction((double) 2 / (double) 11, 1.0e-5, 100));
+        assertFraction(3, 11, new BigFraction((double) 3 / (double) 11, 1.0e-5, 100));
+        assertFraction(4, 11, new BigFraction((double) 4 / (double) 11, 1.0e-5, 100));
+        assertFraction(5, 11, new BigFraction((double) 5 / (double) 11, 1.0e-5, 100));
+        assertFraction(6, 11, new BigFraction((double) 6 / (double) 11, 1.0e-5, 100));
+        assertFraction(7, 11, new BigFraction((double) 7 / (double) 11, 1.0e-5, 100));
+        assertFraction(8, 11, new BigFraction((double) 8 / (double) 11, 1.0e-5, 100));
+        assertFraction(9, 11, new BigFraction((double) 9 / (double) 11, 1.0e-5, 100));
+        assertFraction(10, 11, new BigFraction((double) 10 / (double) 11, 1.0e-5, 100));
+    }
+
+    // MATH-181
+    public void testDigitLimitConstructor() throws ConvergenceException {
+        assertFraction(2, 5, new BigFraction(0.4, 9));
+        assertFraction(2, 5, new BigFraction(0.4, 99));
+        assertFraction(2, 5, new BigFraction(0.4, 999));
+
+        assertFraction(3, 5, new BigFraction(0.6152, 9));
+        assertFraction(8, 13, new BigFraction(0.6152, 99));
+        assertFraction(510, 829, new BigFraction(0.6152, 999));
+        assertFraction(769, 1250, new BigFraction(0.6152, 9999));
+    }
+
+    public void testEpsilonLimitConstructor() throws ConvergenceException {
+        assertFraction(2, 5, new BigFraction(0.4, 1.0e-5, 100));
+
+        assertFraction(3, 5, new BigFraction(0.6152, 0.02, 100));
+        assertFraction(8, 13, new BigFraction(0.6152, 1.0e-3, 100));
+        assertFraction(251, 408, new BigFraction(0.6152, 1.0e-4, 100));
+        assertFraction(251, 408, new BigFraction(0.6152, 1.0e-5, 100));
+        assertFraction(510, 829, new BigFraction(0.6152, 1.0e-6, 100));
+        assertFraction(769, 1250, new BigFraction(0.6152, 1.0e-7, 100));
+    }
+
+    public void testCompareTo() {
+        BigFraction first = new BigFraction(1, 2);
+        BigFraction second = new BigFraction(1, 3);
+        BigFraction third = new BigFraction(1, 2);
+
+        assertEquals(0, first.compareTo(first));
+        assertEquals(0, first.compareTo(third));
+        assertEquals(1, first.compareTo(second));
+        assertEquals(-1, second.compareTo(first));
+
+        // these two values are different approximations of PI
+        // the first  one is approximately PI - 3.07e-18
+        // the second one is approximately PI + 1.936e-17
+        BigFraction pi1 = new BigFraction(1068966896, 340262731);
+        BigFraction pi2 = new BigFraction( 411557987, 131002976);
+        assertEquals(-1, pi1.compareTo(pi2));
+        assertEquals( 1, pi2.compareTo(pi1));
+        assertEquals(0.0, pi1.doubleValue() - pi2.doubleValue(), 1.0e-20);
+
+    }
+
+    public void testDoubleValue() {
+        BigFraction first = new BigFraction(1, 2);
+        BigFraction second = new BigFraction(1, 3);
+
+        assertEquals(0.5, first.doubleValue(), 0.0);
+        assertEquals(1.0 / 3.0, second.doubleValue(), 0.0);
+    }
+
+    public void testFloatValue() {
+        BigFraction first = new BigFraction(1, 2);
+        BigFraction second = new BigFraction(1, 3);
+
+        assertEquals(0.5f, first.floatValue(), 0.0f);
+        assertEquals((float) (1.0 / 3.0), second.floatValue(), 0.0f);
+    }
+
+    public void testIntValue() {
+        BigFraction first = new BigFraction(1, 2);
+        BigFraction second = new BigFraction(3, 2);
+
+        assertEquals(0, first.intValue());
+        assertEquals(1, second.intValue());
+    }
+
+    public void testLongValue() {
+        BigFraction first = new BigFraction(1, 2);
+        BigFraction second = new BigFraction(3, 2);
+
+        assertEquals(0L, first.longValue());
+        assertEquals(1L, second.longValue());
+    }
+
+    public void testConstructorDouble() {
+        assertFraction(1, 2, new BigFraction(0.5));
+        assertFraction(6004799503160661l, 18014398509481984l, new BigFraction(1.0 / 3.0));
+        assertFraction(6124895493223875l, 36028797018963968l, new BigFraction(17.0 / 100.0));
+        assertFraction(1784551352345559l, 562949953421312l, new BigFraction(317.0 / 100.0));
+        assertFraction(-1, 2, new BigFraction(-0.5));
+        assertFraction(-6004799503160661l, 18014398509481984l, new BigFraction(-1.0 / 3.0));
+        assertFraction(-6124895493223875l, 36028797018963968l, new BigFraction(17.0 / -100.0));
+        assertFraction(-1784551352345559l, 562949953421312l, new BigFraction(-317.0 / 100.0));
+        for (double v : new double[] { Double.NaN, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY}) {
+            try {
+                new BigFraction(v);
+                fail("Expecting IllegalArgumentException");
+            } catch (IllegalArgumentException iae) {
+                // expected
+            }
+        }
+        assertEquals(1l, new BigFraction(Double.MAX_VALUE).getDenominatorAsLong());
+        assertEquals(1l, new BigFraction(Double.longBitsToDouble(0x0010000000000000L)).getNumeratorAsLong());
+        assertEquals(1l, new BigFraction(Double.MIN_VALUE).getNumeratorAsLong());
+    }
+
+    public void testAbs() {
+        BigFraction a = new BigFraction(10, 21);
+        BigFraction b = new BigFraction(-10, 21);
+        BigFraction c = new BigFraction(10, -21);
+
+        assertFraction(10, 21, a.abs());
+        assertFraction(10, 21, b.abs());
+        assertFraction(10, 21, c.abs());
+    }
+
+    public void testReciprocal() {
+        BigFraction f = null;
+
+        f = new BigFraction(50, 75);
+        f = f.reciprocal();
+        assertEquals(3, f.getNumeratorAsInt());
+        assertEquals(2, f.getDenominatorAsInt());
+
+        f = new BigFraction(4, 3);
+        f = f.reciprocal();
+        assertEquals(3, f.getNumeratorAsInt());
+        assertEquals(4, f.getDenominatorAsInt());
+
+        f = new BigFraction(-15, 47);
+        f = f.reciprocal();
+        assertEquals(-47, f.getNumeratorAsInt());
+        assertEquals(15, f.getDenominatorAsInt());
+
+        f = new BigFraction(0, 3);
+        try {
+            f = f.reciprocal();
+            fail("expecting ArithmeticException");
+        } catch (ArithmeticException ex) {
+        }
+
+        // large values
+        f = new BigFraction(Integer.MAX_VALUE, 1);
+        f = f.reciprocal();
+        assertEquals(1, f.getNumeratorAsInt());
+        assertEquals(Integer.MAX_VALUE, f.getDenominatorAsInt());
+    }
+
+    public void testNegate() {
+        BigFraction f = null;
+
+        f = new BigFraction(50, 75);
+        f = f.negate();
+        assertEquals(-2, f.getNumeratorAsInt());
+        assertEquals(3, f.getDenominatorAsInt());
+
+        f = new BigFraction(-50, 75);
+        f = f.negate();
+        assertEquals(2, f.getNumeratorAsInt());
+        assertEquals(3, f.getDenominatorAsInt());
+
+        // large values
+        f = new BigFraction(Integer.MAX_VALUE - 1, Integer.MAX_VALUE);
+        f = f.negate();
+        assertEquals(Integer.MIN_VALUE + 2, f.getNumeratorAsInt());
+        assertEquals(Integer.MAX_VALUE, f.getDenominatorAsInt());
+
+    }
+
+    public void testAdd() {
+        BigFraction a = new BigFraction(1, 2);
+        BigFraction b = new BigFraction(2, 3);
+
+        assertFraction(1, 1, a.add(a));
+        assertFraction(7, 6, a.add(b));
+        assertFraction(7, 6, b.add(a));
+        assertFraction(4, 3, b.add(b));
+
+        BigFraction f1 = new BigFraction(Integer.MAX_VALUE - 1, 1);
+        BigFraction f2 = BigFraction.ONE;
+        BigFraction f = f1.add(f2);
+        assertEquals(Integer.MAX_VALUE, f.getNumeratorAsInt());
+        assertEquals(1, f.getDenominatorAsInt());
+
+        f1 = new BigFraction(-1, 13 * 13 * 2 * 2);
+        f2 = new BigFraction(-2, 13 * 17 * 2);
+        f = f1.add(f2);
+        assertEquals(13 * 13 * 17 * 2 * 2, f.getDenominatorAsInt());
+        assertEquals(-17 - 2 * 13 * 2, f.getNumeratorAsInt());
+
+        try {
+            f.add((BigFraction) null);
+            fail("expecting NullPointerException");
+        } catch (NullPointerException ex) {
+        }
+
+        // if this fraction is added naively, it will overflow.
+        // check that it doesn't.
+        f1 = new BigFraction(1, 32768 * 3);
+        f2 = new BigFraction(1, 59049);
+        f = f1.add(f2);
+        assertEquals(52451, f.getNumeratorAsInt());
+        assertEquals(1934917632, f.getDenominatorAsInt());
+
+        f1 = new BigFraction(Integer.MIN_VALUE, 3);
+        f2 = new BigFraction(1, 3);
+        f = f1.add(f2);
+        assertEquals(Integer.MIN_VALUE + 1, f.getNumeratorAsInt());
+        assertEquals(3, f.getDenominatorAsInt());
+
+        f1 = new BigFraction(Integer.MAX_VALUE - 1, 1);
+        f = f1.add(BigInteger.ONE);
+        assertEquals(Integer.MAX_VALUE, f.getNumeratorAsInt());
+        assertEquals(1, f.getDenominatorAsInt());
+
+        f = f.add(BigInteger.ZERO);
+        assertEquals(Integer.MAX_VALUE, f.getNumeratorAsInt());
+        assertEquals(1, f.getDenominatorAsInt());
+
+        f1 = new BigFraction(Integer.MAX_VALUE - 1, 1);
+        f = f1.add(1);
+        assertEquals(Integer.MAX_VALUE, f.getNumeratorAsInt());
+        assertEquals(1, f.getDenominatorAsInt());
+
+        f = f.add(0);
+        assertEquals(Integer.MAX_VALUE, f.getNumeratorAsInt());
+        assertEquals(1, f.getDenominatorAsInt());
+
+        f1 = new BigFraction(Integer.MAX_VALUE - 1, 1);
+        f = f1.add(1l);
+        assertEquals(Integer.MAX_VALUE, f.getNumeratorAsInt());
+        assertEquals(1, f.getDenominatorAsInt());
+
+        f = f.add(0l);
+        assertEquals(Integer.MAX_VALUE, f.getNumeratorAsInt());
+        assertEquals(1, f.getDenominatorAsInt());
+
+    }
+
+    public void testDivide() {
+        BigFraction a = new BigFraction(1, 2);
+        BigFraction b = new BigFraction(2, 3);
+
+        assertFraction(1, 1, a.divide(a));
+        assertFraction(3, 4, a.divide(b));
+        assertFraction(4, 3, b.divide(a));
+        assertFraction(1, 1, b.divide(b));
+
+        BigFraction f1 = new BigFraction(3, 5);
+        BigFraction f2 = BigFraction.ZERO;
+        try {
+            f1.divide(f2);
+            fail("expecting ArithmeticException");
+        } catch (ArithmeticException ex) {
+        }
+
+        f1 = new BigFraction(0, 5);
+        f2 = new BigFraction(2, 7);
+        BigFraction f = f1.divide(f2);
+        assertSame(BigFraction.ZERO, f);
+
+        f1 = new BigFraction(2, 7);
+        f2 = BigFraction.ONE;
+        f = f1.divide(f2);
+        assertEquals(2, f.getNumeratorAsInt());
+        assertEquals(7, f.getDenominatorAsInt());
+
+        f1 = new BigFraction(1, Integer.MAX_VALUE);
+        f = f1.divide(f1);
+        assertEquals(1, f.getNumeratorAsInt());
+        assertEquals(1, f.getDenominatorAsInt());
+
+        f1 = new BigFraction(Integer.MIN_VALUE, Integer.MAX_VALUE);
+        f2 = new BigFraction(1, Integer.MAX_VALUE);
+        f = f1.divide(f2);
+        assertEquals(Integer.MIN_VALUE, f.getNumeratorAsInt());
+        assertEquals(1, f.getDenominatorAsInt());
+
+        try {
+            f.divide((BigFraction) null);
+            fail("expecting NullPointerException");
+        } catch (NullPointerException ex) {
+        }
+
+        f1 = new BigFraction(Integer.MIN_VALUE, Integer.MAX_VALUE);
+        f = f1.divide(BigInteger.valueOf(Integer.MIN_VALUE));
+        assertEquals(Integer.MAX_VALUE, f.getDenominatorAsInt());
+        assertEquals(1, f.getNumeratorAsInt());
+
+        f1 = new BigFraction(Integer.MIN_VALUE, Integer.MAX_VALUE);
+        f = f1.divide(Integer.MIN_VALUE);
+        assertEquals(Integer.MAX_VALUE, f.getDenominatorAsInt());
+        assertEquals(1, f.getNumeratorAsInt());
+
+        f1 = new BigFraction(Integer.MIN_VALUE, Integer.MAX_VALUE);
+        f = f1.divide((long) Integer.MIN_VALUE);
+        assertEquals(Integer.MAX_VALUE, f.getDenominatorAsInt());
+        assertEquals(1, f.getNumeratorAsInt());
+
+    }
+
+    public void testMultiply() {
+        BigFraction a = new BigFraction(1, 2);
+        BigFraction b = new BigFraction(2, 3);
+
+        assertFraction(1, 4, a.multiply(a));
+        assertFraction(1, 3, a.multiply(b));
+        assertFraction(1, 3, b.multiply(a));
+        assertFraction(4, 9, b.multiply(b));
+
+        BigFraction f1 = new BigFraction(Integer.MAX_VALUE, 1);
+        BigFraction f2 = new BigFraction(Integer.MIN_VALUE, Integer.MAX_VALUE);
+        BigFraction f = f1.multiply(f2);
+        assertEquals(Integer.MIN_VALUE, f.getNumeratorAsInt());
+        assertEquals(1, f.getDenominatorAsInt());
+
+        f = f2.multiply(Integer.MAX_VALUE);
+        assertEquals(Integer.MIN_VALUE, f.getNumeratorAsInt());
+        assertEquals(1, f.getDenominatorAsInt());
+
+        f = f2.multiply((long) Integer.MAX_VALUE);
+        assertEquals(Integer.MIN_VALUE, f.getNumeratorAsInt());
+        assertEquals(1, f.getDenominatorAsInt());
+
+        try {
+            f.multiply((BigFraction) null);
+            fail("expecting NullPointerException");
+        } catch (NullPointerException ex) {
+        }
+
+    }
+
+    public void testSubtract() {
+        BigFraction a = new BigFraction(1, 2);
+        BigFraction b = new BigFraction(2, 3);
+
+        assertFraction(0, 1, a.subtract(a));
+        assertFraction(-1, 6, a.subtract(b));
+        assertFraction(1, 6, b.subtract(a));
+        assertFraction(0, 1, b.subtract(b));
+
+        BigFraction f = new BigFraction(1, 1);
+        try {
+            f.subtract((BigFraction) null);
+            fail("expecting NullPointerException");
+        } catch (NullPointerException ex) {
+        }
+
+        // if this fraction is subtracted naively, it will overflow.
+        // check that it doesn't.
+        BigFraction f1 = new BigFraction(1, 32768 * 3);
+        BigFraction f2 = new BigFraction(1, 59049);
+        f = f1.subtract(f2);
+        assertEquals(-13085, f.getNumeratorAsInt());
+        assertEquals(1934917632, f.getDenominatorAsInt());
+
+        f1 = new BigFraction(Integer.MIN_VALUE, 3);
+        f2 = new BigFraction(1, 3).negate();
+        f = f1.subtract(f2);
+        assertEquals(Integer.MIN_VALUE + 1, f.getNumeratorAsInt());
+        assertEquals(3, f.getDenominatorAsInt());
+
+        f1 = new BigFraction(Integer.MAX_VALUE, 1);
+        f2 = BigFraction.ONE;
+        f = f1.subtract(f2);
+        assertEquals(Integer.MAX_VALUE - 1, f.getNumeratorAsInt());
+        assertEquals(1, f.getDenominatorAsInt());
+
+    }
+
+    public void testBigDecimalValue() {
+        assertEquals(new BigDecimal(0.5), new BigFraction(1, 2).bigDecimalValue());
+        assertEquals(new BigDecimal("0.0003"), new BigFraction(3, 10000).bigDecimalValue());
+        assertEquals(new BigDecimal("0"), new BigFraction(1, 3).bigDecimalValue(BigDecimal.ROUND_DOWN));
+        assertEquals(new BigDecimal("0.333"), new BigFraction(1, 3).bigDecimalValue(3, BigDecimal.ROUND_DOWN));
+    }
+
+    public void testEqualsAndHashCode() {
+        BigFraction zero = new BigFraction(0, 1);
+        BigFraction nullFraction = null;
+        assertTrue(zero.equals(zero));
+        assertFalse(zero.equals(nullFraction));
+        assertFalse(zero.equals(Double.valueOf(0)));
+        BigFraction zero2 = new BigFraction(0, 2);
+        assertTrue(zero.equals(zero2));
+        assertEquals(zero.hashCode(), zero2.hashCode());
+        BigFraction one = new BigFraction(1, 1);
+        assertFalse((one.equals(zero) || zero.equals(one)));
+        assertTrue(one.equals(BigFraction.ONE));
+    }
+
+    public void testGetReducedFraction() {
+        BigFraction threeFourths = new BigFraction(3, 4);
+        assertTrue(threeFourths.equals(BigFraction.getReducedFraction(6, 8)));
+        assertTrue(BigFraction.ZERO.equals(BigFraction.getReducedFraction(0, -1)));
+        try {
+            BigFraction.getReducedFraction(1, 0);
+            fail("expecting ArithmeticException");
+        } catch (ArithmeticException ex) {
+            // expected
+        }
+        assertEquals(BigFraction.getReducedFraction(2, Integer.MIN_VALUE).getNumeratorAsInt(), -1);
+        assertEquals(BigFraction.getReducedFraction(1, -1).getNumeratorAsInt(), -1);
+    }
+
+    public void testPow() {
+        assertEquals(new BigFraction(8192, 1594323), new BigFraction(2, 3).pow(13));
+        assertEquals(new BigFraction(8192, 1594323), new BigFraction(2, 3).pow(13l));
+        assertEquals(new BigFraction(8192, 1594323), new BigFraction(2, 3).pow(BigInteger.valueOf(13l)));
+        assertEquals(BigFraction.ONE, new BigFraction(2, 3).pow(0));
+        assertEquals(BigFraction.ONE, new BigFraction(2, 3).pow(0l));
+        assertEquals(BigFraction.ONE, new BigFraction(2, 3).pow(BigInteger.valueOf(0l)));
+        assertEquals(new BigFraction(1594323, 8192), new BigFraction(2, 3).pow(-13));
+        assertEquals(new BigFraction(1594323, 8192), new BigFraction(2, 3).pow(-13l));
+        assertEquals(new BigFraction(1594323, 8192), new BigFraction(2, 3).pow(BigInteger.valueOf(-13l)));
+    }
+
+    public void testMath340() {
+        BigFraction fractionA = new BigFraction(0.00131);
+        BigFraction fractionB = new BigFraction(.37).reciprocal();
+        BigFraction errorResult = fractionA.multiply(fractionB);
+        BigFraction correctResult = new BigFraction(fractionA.getNumerator().multiply(fractionB.getNumerator()),
+                                                    fractionA.getDenominator().multiply(fractionB.getDenominator()));
+        assertEquals(correctResult, errorResult);
+    }
+
+    public void testSerial() throws FractionConversionException {
+        BigFraction[] fractions = {
+            new BigFraction(3, 4), BigFraction.ONE, BigFraction.ZERO,
+            new BigFraction(17), new BigFraction(FastMath.PI, 1000),
+            new BigFraction(-5, 2)
+        };
+        for (BigFraction fraction : fractions) {
+            assertEquals(fraction, TestUtils.serializeAndRecover(fraction));
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/fraction/FractionFieldTest.java b/src/test/java/org/apache/commons/math/fraction/FractionFieldTest.java
new file mode 100644
index 0000000..37ece32
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/fraction/FractionFieldTest.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.fraction;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.commons.math.TestUtils;
+import org.junit.Test;
+
+public class FractionFieldTest {
+
+    @Test
+    public void testZero() {
+        assertEquals(Fraction.ZERO, FractionField.getInstance().getZero());
+    }
+
+    @Test
+    public void testOne() {
+        assertEquals(Fraction.ONE, FractionField.getInstance().getOne());
+    }
+
+    @Test
+    public void testSerial() {
+        // deserializing the singleton should give the singleton itself back
+        FractionField field = FractionField.getInstance();
+        assertTrue(field == TestUtils.serializeAndRecover(field));
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/fraction/FractionFormatTest.java b/src/test/java/org/apache/commons/math/fraction/FractionFormatTest.java
new file mode 100644
index 0000000..21f64f5
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/fraction/FractionFormatTest.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.fraction;
+
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.util.Locale;
+
+import org.apache.commons.math.util.FastMath;
+
+import junit.framework.TestCase;
+
+public class FractionFormatTest extends TestCase {
+
+    FractionFormat properFormat = null;
+    FractionFormat improperFormat = null;
+
+    protected Locale getLocale() {
+        return Locale.getDefault();
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        properFormat = FractionFormat.getProperInstance(getLocale());
+        improperFormat = FractionFormat.getImproperInstance(getLocale());
+    }
+
+    public void testFormat() {
+        Fraction c = new Fraction(1, 2);
+        String expected = "1 / 2";
+
+        String actual = properFormat.format(c);
+        assertEquals(expected, actual);
+
+        actual = improperFormat.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void testFormatNegative() {
+        Fraction c = new Fraction(-1, 2);
+        String expected = "-1 / 2";
+
+        String actual = properFormat.format(c);
+        assertEquals(expected, actual);
+
+        actual = improperFormat.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void testFormatZero() {
+        Fraction c = new Fraction(0, 1);
+        String expected = "0 / 1";
+
+        String actual = properFormat.format(c);
+        assertEquals(expected, actual);
+
+        actual = improperFormat.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void testFormatImproper() {
+        Fraction c = new Fraction(5, 3);
+
+        String actual = properFormat.format(c);
+        assertEquals("1 2 / 3", actual);
+
+        actual = improperFormat.format(c);
+        assertEquals("5 / 3", actual);
+    }
+
+    public void testFormatImproperNegative() {
+        Fraction c = new Fraction(-5, 3);
+
+        String actual = properFormat.format(c);
+        assertEquals("-1 2 / 3", actual);
+
+        actual = improperFormat.format(c);
+        assertEquals("-5 / 3", actual);
+    }
+
+    public void testParse() {
+        String source = "1 / 2";
+
+        try {
+            Fraction c = properFormat.parse(source);
+            assertNotNull(c);
+            assertEquals(1, c.getNumerator());
+            assertEquals(2, c.getDenominator());
+
+            c = improperFormat.parse(source);
+            assertNotNull(c);
+            assertEquals(1, c.getNumerator());
+            assertEquals(2, c.getDenominator());
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParseInteger() {
+        String source = "10";
+        try {
+            Fraction c = properFormat.parse(source);
+            assertNotNull(c);
+            assertEquals(10, c.getNumerator());
+            assertEquals(1, c.getDenominator());
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+        try {
+            Fraction c = improperFormat.parse(source);
+            assertNotNull(c);
+            assertEquals(10, c.getNumerator());
+            assertEquals(1, c.getDenominator());
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParseInvalid() {
+        String source = "a";
+        String msg = "should not be able to parse '10 / a'.";
+        try {
+            properFormat.parse(source);
+            fail(msg);
+        } catch (ParseException ex) {
+            // success
+        }
+        try {
+            improperFormat.parse(source);
+            fail(msg);
+        } catch (ParseException ex) {
+            // success
+        }
+    }
+
+    public void testParseInvalidDenominator() {
+        String source = "10 / a";
+        String msg = "should not be able to parse '10 / a'.";
+        try {
+            properFormat.parse(source);
+            fail(msg);
+        } catch (ParseException ex) {
+            // success
+        }
+        try {
+            improperFormat.parse(source);
+            fail(msg);
+        } catch (ParseException ex) {
+            // success
+        }
+    }
+
+    public void testParseNegative() {
+
+        try {
+            String source = "-1 / 2";
+            Fraction c = properFormat.parse(source);
+            assertNotNull(c);
+            assertEquals(-1, c.getNumerator());
+            assertEquals(2, c.getDenominator());
+
+            c = improperFormat.parse(source);
+            assertNotNull(c);
+            assertEquals(-1, c.getNumerator());
+            assertEquals(2, c.getDenominator());
+
+            source = "1 / -2";
+            c = properFormat.parse(source);
+            assertNotNull(c);
+            assertEquals(-1, c.getNumerator());
+            assertEquals(2, c.getDenominator());
+
+            c = improperFormat.parse(source);
+            assertNotNull(c);
+            assertEquals(-1, c.getNumerator());
+            assertEquals(2, c.getDenominator());
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParseProper() {
+        String source = "1 2 / 3";
+
+        try {
+            Fraction c = properFormat.parse(source);
+            assertNotNull(c);
+            assertEquals(5, c.getNumerator());
+            assertEquals(3, c.getDenominator());
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+
+        try {
+            improperFormat.parse(source);
+            fail("invalid improper fraction.");
+        } catch (ParseException ex) {
+            // success
+        }
+    }
+
+    public void testParseProperNegative() {
+        String source = "-1 2 / 3";
+        try {
+            Fraction c = properFormat.parse(source);
+            assertNotNull(c);
+            assertEquals(-5, c.getNumerator());
+            assertEquals(3, c.getDenominator());
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+
+        try {
+            improperFormat.parse(source);
+            fail("invalid improper fraction.");
+        } catch (ParseException ex) {
+            // success
+        }
+    }
+
+    public void testParseProperInvalidMinus() {
+        String source = "2 -2 / 3";
+        try {
+            properFormat.parse(source);
+            fail("invalid minus in improper fraction.");
+        } catch (ParseException ex) {
+            // expected
+        }
+        source = "2 2 / -3";
+        try {
+            properFormat.parse(source);
+            fail("invalid minus in improper fraction.");
+        } catch (ParseException ex) {
+            // expected
+        }
+    }
+
+    public void testNumeratorFormat() {
+        NumberFormat old = properFormat.getNumeratorFormat();
+        NumberFormat nf = NumberFormat.getInstance();
+        nf.setParseIntegerOnly(true);
+        properFormat.setNumeratorFormat(nf);
+        assertEquals(nf, properFormat.getNumeratorFormat());
+        properFormat.setNumeratorFormat(old);
+
+        old = improperFormat.getNumeratorFormat();
+        nf = NumberFormat.getInstance();
+        nf.setParseIntegerOnly(true);
+        improperFormat.setNumeratorFormat(nf);
+        assertEquals(nf, improperFormat.getNumeratorFormat());
+        improperFormat.setNumeratorFormat(old);
+    }
+
+    public void testDenominatorFormat() {
+        NumberFormat old = properFormat.getDenominatorFormat();
+        NumberFormat nf = NumberFormat.getInstance();
+        nf.setParseIntegerOnly(true);
+        properFormat.setDenominatorFormat(nf);
+        assertEquals(nf, properFormat.getDenominatorFormat());
+        properFormat.setDenominatorFormat(old);
+
+        old = improperFormat.getDenominatorFormat();
+        nf = NumberFormat.getInstance();
+        nf.setParseIntegerOnly(true);
+        improperFormat.setDenominatorFormat(nf);
+        assertEquals(nf, improperFormat.getDenominatorFormat());
+        improperFormat.setDenominatorFormat(old);
+    }
+
+    public void testWholeFormat() {
+        ProperFractionFormat format = (ProperFractionFormat)properFormat;
+
+        NumberFormat old = format.getWholeFormat();
+        NumberFormat nf = NumberFormat.getInstance();
+        nf.setParseIntegerOnly(true);
+        format.setWholeFormat(nf);
+        assertEquals(nf, format.getWholeFormat());
+        format.setWholeFormat(old);
+    }
+
+    public void testLongFormat() {
+        assertEquals("10 / 1", improperFormat.format(10l));
+    }
+
+    public void testDoubleFormat() {
+        assertEquals("355 / 113", improperFormat.format(FastMath.PI));
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/fraction/FractionTest.java b/src/test/java/org/apache/commons/math/fraction/FractionTest.java
new file mode 100644
index 0000000..23fe9e2
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/fraction/FractionTest.java
@@ -0,0 +1,583 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.TestUtils;
+import org.apache.commons.math.util.FastMath;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Revision: 1003907 $ $Date: 2010-10-03 00:23:34 +0200 (dim. 03 oct. 2010) $
+ */
+public class FractionTest extends TestCase {
+
+    private void assertFraction(int expectedNumerator, int expectedDenominator, Fraction actual) {
+        assertEquals(expectedNumerator, actual.getNumerator());
+        assertEquals(expectedDenominator, actual.getDenominator());
+    }
+
+    public void testConstructor() {
+        assertFraction(0, 1, new Fraction(0, 1));
+        assertFraction(0, 1, new Fraction(0, 2));
+        assertFraction(0, 1, new Fraction(0, -1));
+        assertFraction(1, 2, new Fraction(1, 2));
+        assertFraction(1, 2, new Fraction(2, 4));
+        assertFraction(-1, 2, new Fraction(-1, 2));
+        assertFraction(-1, 2, new Fraction(1, -2));
+        assertFraction(-1, 2, new Fraction(-2, 4));
+        assertFraction(-1, 2, new Fraction(2, -4));
+
+        // overflow
+        try {
+            new Fraction(Integer.MIN_VALUE, -1);
+            fail();
+        } catch (ArithmeticException ex) {
+            // success
+        }
+        try {
+            new Fraction(1, Integer.MIN_VALUE);
+            fail();
+        } catch (ArithmeticException ex) {
+            // success
+        }
+        try {
+            assertFraction(0, 1, new Fraction(0.00000000000001));
+            assertFraction(2, 5, new Fraction(0.40000000000001));
+            assertFraction(15, 1, new Fraction(15.0000000000001));
+
+        } catch (ConvergenceException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testGoldenRatio() {
+        try {
+            // the golden ratio is notoriously a difficult number for continuous fraction
+            new Fraction((1 + FastMath.sqrt(5)) / 2, 1.0e-12, 25);
+            fail("an exception should have been thrown");
+        } catch (ConvergenceException ce) {
+            // expected behavior
+        }
+    }
+
+    // MATH-179
+    public void testDoubleConstructor() throws ConvergenceException  {
+        assertFraction(1, 2, new Fraction((double)1 / (double)2));
+        assertFraction(1, 3, new Fraction((double)1 / (double)3));
+        assertFraction(2, 3, new Fraction((double)2 / (double)3));
+        assertFraction(1, 4, new Fraction((double)1 / (double)4));
+        assertFraction(3, 4, new Fraction((double)3 / (double)4));
+        assertFraction(1, 5, new Fraction((double)1 / (double)5));
+        assertFraction(2, 5, new Fraction((double)2 / (double)5));
+        assertFraction(3, 5, new Fraction((double)3 / (double)5));
+        assertFraction(4, 5, new Fraction((double)4 / (double)5));
+        assertFraction(1, 6, new Fraction((double)1 / (double)6));
+        assertFraction(5, 6, new Fraction((double)5 / (double)6));
+        assertFraction(1, 7, new Fraction((double)1 / (double)7));
+        assertFraction(2, 7, new Fraction((double)2 / (double)7));
+        assertFraction(3, 7, new Fraction((double)3 / (double)7));
+        assertFraction(4, 7, new Fraction((double)4 / (double)7));
+        assertFraction(5, 7, new Fraction((double)5 / (double)7));
+        assertFraction(6, 7, new Fraction((double)6 / (double)7));
+        assertFraction(1, 8, new Fraction((double)1 / (double)8));
+        assertFraction(3, 8, new Fraction((double)3 / (double)8));
+        assertFraction(5, 8, new Fraction((double)5 / (double)8));
+        assertFraction(7, 8, new Fraction((double)7 / (double)8));
+        assertFraction(1, 9, new Fraction((double)1 / (double)9));
+        assertFraction(2, 9, new Fraction((double)2 / (double)9));
+        assertFraction(4, 9, new Fraction((double)4 / (double)9));
+        assertFraction(5, 9, new Fraction((double)5 / (double)9));
+        assertFraction(7, 9, new Fraction((double)7 / (double)9));
+        assertFraction(8, 9, new Fraction((double)8 / (double)9));
+        assertFraction(1, 10, new Fraction((double)1 / (double)10));
+        assertFraction(3, 10, new Fraction((double)3 / (double)10));
+        assertFraction(7, 10, new Fraction((double)7 / (double)10));
+        assertFraction(9, 10, new Fraction((double)9 / (double)10));
+        assertFraction(1, 11, new Fraction((double)1 / (double)11));
+        assertFraction(2, 11, new Fraction((double)2 / (double)11));
+        assertFraction(3, 11, new Fraction((double)3 / (double)11));
+        assertFraction(4, 11, new Fraction((double)4 / (double)11));
+        assertFraction(5, 11, new Fraction((double)5 / (double)11));
+        assertFraction(6, 11, new Fraction((double)6 / (double)11));
+        assertFraction(7, 11, new Fraction((double)7 / (double)11));
+        assertFraction(8, 11, new Fraction((double)8 / (double)11));
+        assertFraction(9, 11, new Fraction((double)9 / (double)11));
+        assertFraction(10, 11, new Fraction((double)10 / (double)11));
+    }
+
+    // MATH-181
+    public void testDigitLimitConstructor() throws ConvergenceException  {
+        assertFraction(2, 5, new Fraction(0.4,   9));
+        assertFraction(2, 5, new Fraction(0.4,  99));
+        assertFraction(2, 5, new Fraction(0.4, 999));
+
+        assertFraction(3, 5,      new Fraction(0.6152,    9));
+        assertFraction(8, 13,     new Fraction(0.6152,   99));
+        assertFraction(510, 829,  new Fraction(0.6152,  999));
+        assertFraction(769, 1250, new Fraction(0.6152, 9999));
+    }
+
+    public void testIntegerOverflow() {
+        checkIntegerOverflow(0.75000000001455192);
+        checkIntegerOverflow(1.0e10);
+    }
+
+    private void checkIntegerOverflow(double a) {
+        try {
+            new Fraction(a, 1.0e-12, 1000);
+            fail("an exception should have been thrown");
+        } catch (ConvergenceException ce) {
+            // expected behavior
+        }
+    }
+
+    public void testEpsilonLimitConstructor() throws ConvergenceException  {
+        assertFraction(2, 5, new Fraction(0.4, 1.0e-5, 100));
+
+        assertFraction(3, 5,      new Fraction(0.6152, 0.02, 100));
+        assertFraction(8, 13,     new Fraction(0.6152, 1.0e-3, 100));
+        assertFraction(251, 408,  new Fraction(0.6152, 1.0e-4, 100));
+        assertFraction(251, 408,  new Fraction(0.6152, 1.0e-5, 100));
+        assertFraction(510, 829,  new Fraction(0.6152, 1.0e-6, 100));
+        assertFraction(769, 1250, new Fraction(0.6152, 1.0e-7, 100));
+    }
+
+    public void testCompareTo() {
+        Fraction first = new Fraction(1, 2);
+        Fraction second = new Fraction(1, 3);
+        Fraction third = new Fraction(1, 2);
+
+        assertEquals(0, first.compareTo(first));
+        assertEquals(0, first.compareTo(third));
+        assertEquals(1, first.compareTo(second));
+        assertEquals(-1, second.compareTo(first));
+
+        // these two values are different approximations of PI
+        // the first  one is approximately PI - 3.07e-18
+        // the second one is approximately PI + 1.936e-17
+        Fraction pi1 = new Fraction(1068966896, 340262731);
+        Fraction pi2 = new Fraction( 411557987, 131002976);
+        assertEquals(-1, pi1.compareTo(pi2));
+        assertEquals( 1, pi2.compareTo(pi1));
+        assertEquals(0.0, pi1.doubleValue() - pi2.doubleValue(), 1.0e-20);
+    }
+
+    public void testDoubleValue() {
+        Fraction first = new Fraction(1, 2);
+        Fraction second = new Fraction(1, 3);
+
+        assertEquals(0.5, first.doubleValue(), 0.0);
+        assertEquals(1.0 / 3.0, second.doubleValue(), 0.0);
+    }
+
+    public void testFloatValue() {
+        Fraction first = new Fraction(1, 2);
+        Fraction second = new Fraction(1, 3);
+
+        assertEquals(0.5f, first.floatValue(), 0.0f);
+        assertEquals((float)(1.0 / 3.0), second.floatValue(), 0.0f);
+    }
+
+    public void testIntValue() {
+        Fraction first = new Fraction(1, 2);
+        Fraction second = new Fraction(3, 2);
+
+        assertEquals(0, first.intValue());
+        assertEquals(1, second.intValue());
+    }
+
+    public void testLongValue() {
+        Fraction first = new Fraction(1, 2);
+        Fraction second = new Fraction(3, 2);
+
+        assertEquals(0L, first.longValue());
+        assertEquals(1L, second.longValue());
+    }
+
+    public void testConstructorDouble() {
+        try {
+            assertFraction(1, 2, new Fraction(0.5));
+            assertFraction(1, 3, new Fraction(1.0 / 3.0));
+            assertFraction(17, 100, new Fraction(17.0 / 100.0));
+            assertFraction(317, 100, new Fraction(317.0 / 100.0));
+            assertFraction(-1, 2, new Fraction(-0.5));
+            assertFraction(-1, 3, new Fraction(-1.0 / 3.0));
+            assertFraction(-17, 100, new Fraction(17.0 / -100.0));
+            assertFraction(-317, 100, new Fraction(-317.0 / 100.0));
+        } catch (ConvergenceException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testAbs() {
+        Fraction a = new Fraction(10, 21);
+        Fraction b = new Fraction(-10, 21);
+        Fraction c = new Fraction(10, -21);
+
+        assertFraction(10, 21, a.abs());
+        assertFraction(10, 21, b.abs());
+        assertFraction(10, 21, c.abs());
+    }
+
+    public void testReciprocal() {
+        Fraction f = null;
+
+        f = new Fraction(50, 75);
+        f = f.reciprocal();
+        assertEquals(3, f.getNumerator());
+        assertEquals(2, f.getDenominator());
+
+        f = new Fraction(4, 3);
+        f = f.reciprocal();
+        assertEquals(3, f.getNumerator());
+        assertEquals(4, f.getDenominator());
+
+        f = new Fraction(-15, 47);
+        f = f.reciprocal();
+        assertEquals(-47, f.getNumerator());
+        assertEquals(15, f.getDenominator());
+
+        f = new Fraction(0, 3);
+        try {
+            f = f.reciprocal();
+            fail("expecting ArithmeticException");
+        } catch (ArithmeticException ex) {}
+
+        // large values
+        f = new Fraction(Integer.MAX_VALUE, 1);
+        f = f.reciprocal();
+        assertEquals(1, f.getNumerator());
+        assertEquals(Integer.MAX_VALUE, f.getDenominator());
+    }
+
+    public void testNegate() {
+        Fraction f = null;
+
+        f = new Fraction(50, 75);
+        f = f.negate();
+        assertEquals(-2, f.getNumerator());
+        assertEquals(3, f.getDenominator());
+
+        f = new Fraction(-50, 75);
+        f = f.negate();
+        assertEquals(2, f.getNumerator());
+        assertEquals(3, f.getDenominator());
+
+        // large values
+        f = new Fraction(Integer.MAX_VALUE-1, Integer.MAX_VALUE);
+        f = f.negate();
+        assertEquals(Integer.MIN_VALUE+2, f.getNumerator());
+        assertEquals(Integer.MAX_VALUE, f.getDenominator());
+
+        f = new Fraction(Integer.MIN_VALUE, 1);
+        try {
+            f = f.negate();
+            fail("expecting ArithmeticException");
+        } catch (ArithmeticException ex) {}
+    }
+
+    public void testAdd() {
+        Fraction a = new Fraction(1, 2);
+        Fraction b = new Fraction(2, 3);
+
+        assertFraction(1, 1, a.add(a));
+        assertFraction(7, 6, a.add(b));
+        assertFraction(7, 6, b.add(a));
+        assertFraction(4, 3, b.add(b));
+
+        Fraction f1 = new Fraction(Integer.MAX_VALUE - 1, 1);
+        Fraction f2 = Fraction.ONE;
+        Fraction f = f1.add(f2);
+        assertEquals(Integer.MAX_VALUE, f.getNumerator());
+        assertEquals(1, f.getDenominator());
+        f = f1.add(1);
+        assertEquals(Integer.MAX_VALUE, f.getNumerator());
+        assertEquals(1, f.getDenominator());
+
+        f1 = new Fraction(-1, 13*13*2*2);
+        f2 = new Fraction(-2, 13*17*2);
+        f = f1.add(f2);
+        assertEquals(13*13*17*2*2, f.getDenominator());
+        assertEquals(-17 - 2*13*2, f.getNumerator());
+
+        try {
+            f.add(null);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {}
+
+        // if this fraction is added naively, it will overflow.
+        // check that it doesn't.
+        f1 = new Fraction(1,32768*3);
+        f2 = new Fraction(1,59049);
+        f = f1.add(f2);
+        assertEquals(52451, f.getNumerator());
+        assertEquals(1934917632, f.getDenominator());
+
+        f1 = new Fraction(Integer.MIN_VALUE, 3);
+        f2 = new Fraction(1,3);
+        f = f1.add(f2);
+        assertEquals(Integer.MIN_VALUE+1, f.getNumerator());
+        assertEquals(3, f.getDenominator());
+
+        f1 = new Fraction(Integer.MAX_VALUE - 1, 1);
+        f2 = Fraction.ONE;
+        f = f1.add(f2);
+        assertEquals(Integer.MAX_VALUE, f.getNumerator());
+        assertEquals(1, f.getDenominator());
+
+        try {
+            f = f.add(Fraction.ONE); // should overflow
+            fail("expecting ArithmeticException but got: " + f.toString());
+        } catch (ArithmeticException ex) {}
+
+        // denominator should not be a multiple of 2 or 3 to trigger overflow
+        f1 = new Fraction(Integer.MIN_VALUE, 5);
+        f2 = new Fraction(-1,5);
+        try {
+            f = f1.add(f2); // should overflow
+            fail("expecting ArithmeticException but got: " + f.toString());
+        } catch (ArithmeticException ex) {}
+
+        try {
+            f= new Fraction(-Integer.MAX_VALUE, 1);
+            f = f.add(f);
+            fail("expecting ArithmeticException");
+        } catch (ArithmeticException ex) {}
+
+        try {
+            f= new Fraction(-Integer.MAX_VALUE, 1);
+            f = f.add(f);
+            fail("expecting ArithmeticException");
+        } catch (ArithmeticException ex) {}
+
+        f1 = new Fraction(3,327680);
+        f2 = new Fraction(2,59049);
+        try {
+            f = f1.add(f2); // should overflow
+            fail("expecting ArithmeticException but got: " + f.toString());
+        } catch (ArithmeticException ex) {}
+    }
+
+    public void testDivide() {
+        Fraction a = new Fraction(1, 2);
+        Fraction b = new Fraction(2, 3);
+
+        assertFraction(1, 1, a.divide(a));
+        assertFraction(3, 4, a.divide(b));
+        assertFraction(4, 3, b.divide(a));
+        assertFraction(1, 1, b.divide(b));
+
+        Fraction f1 = new Fraction(3, 5);
+        Fraction f2 = Fraction.ZERO;
+        try {
+            f1.divide(f2);
+            fail("expecting ArithmeticException");
+        } catch (ArithmeticException ex) {}
+
+        f1 = new Fraction(0, 5);
+        f2 = new Fraction(2, 7);
+        Fraction f = f1.divide(f2);
+        assertSame(Fraction.ZERO, f);
+
+        f1 = new Fraction(2, 7);
+        f2 = Fraction.ONE;
+        f = f1.divide(f2);
+        assertEquals(2, f.getNumerator());
+        assertEquals(7, f.getDenominator());
+
+        f1 = new Fraction(1, Integer.MAX_VALUE);
+        f = f1.divide(f1);
+        assertEquals(1, f.getNumerator());
+        assertEquals(1, f.getDenominator());
+
+        f1 = new Fraction(Integer.MIN_VALUE, Integer.MAX_VALUE);
+        f2 = new Fraction(1, Integer.MAX_VALUE);
+        f = f1.divide(f2);
+        assertEquals(Integer.MIN_VALUE, f.getNumerator());
+        assertEquals(1, f.getDenominator());
+
+        try {
+            f.divide(null);
+            fail("IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {}
+
+        try {
+            f1 = new Fraction(1, Integer.MAX_VALUE);
+            f = f1.divide(f1.reciprocal());  // should overflow
+            fail("expecting ArithmeticException");
+        } catch (ArithmeticException ex) {}
+        try {
+            f1 = new Fraction(1, -Integer.MAX_VALUE);
+            f = f1.divide(f1.reciprocal());  // should overflow
+            fail("expecting ArithmeticException");
+        } catch (ArithmeticException ex) {}
+
+        f1 = new Fraction(6, 35);
+        f  = f1.divide(15);
+        assertEquals(2, f.getNumerator());
+        assertEquals(175, f.getDenominator());
+
+    }
+
+    public void testMultiply() {
+        Fraction a = new Fraction(1, 2);
+        Fraction b = new Fraction(2, 3);
+
+        assertFraction(1, 4, a.multiply(a));
+        assertFraction(1, 3, a.multiply(b));
+        assertFraction(1, 3, b.multiply(a));
+        assertFraction(4, 9, b.multiply(b));
+
+        Fraction f1 = new Fraction(Integer.MAX_VALUE, 1);
+        Fraction f2 = new Fraction(Integer.MIN_VALUE, Integer.MAX_VALUE);
+        Fraction f = f1.multiply(f2);
+        assertEquals(Integer.MIN_VALUE, f.getNumerator());
+        assertEquals(1, f.getDenominator());
+
+        try {
+            f.multiply(null);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {}
+
+        f1 = new Fraction(6, 35);
+        f  = f1.multiply(15);
+        assertEquals(18, f.getNumerator());
+        assertEquals(7, f.getDenominator());
+    }
+
+    public void testSubtract() {
+        Fraction a = new Fraction(1, 2);
+        Fraction b = new Fraction(2, 3);
+
+        assertFraction(0, 1, a.subtract(a));
+        assertFraction(-1, 6, a.subtract(b));
+        assertFraction(1, 6, b.subtract(a));
+        assertFraction(0, 1, b.subtract(b));
+
+        Fraction f = new Fraction(1,1);
+        try {
+            f.subtract(null);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {}
+
+        // if this fraction is subtracted naively, it will overflow.
+        // check that it doesn't.
+        Fraction f1 = new Fraction(1,32768*3);
+        Fraction f2 = new Fraction(1,59049);
+        f = f1.subtract(f2);
+        assertEquals(-13085, f.getNumerator());
+        assertEquals(1934917632, f.getDenominator());
+
+        f1 = new Fraction(Integer.MIN_VALUE, 3);
+        f2 = new Fraction(1,3).negate();
+        f = f1.subtract(f2);
+        assertEquals(Integer.MIN_VALUE+1, f.getNumerator());
+        assertEquals(3, f.getDenominator());
+
+        f1 = new Fraction(Integer.MAX_VALUE, 1);
+        f2 = Fraction.ONE;
+        f = f1.subtract(f2);
+        assertEquals(Integer.MAX_VALUE-1, f.getNumerator());
+        assertEquals(1, f.getDenominator());
+        f = f1.subtract(1);
+        assertEquals(Integer.MAX_VALUE-1, f.getNumerator());
+        assertEquals(1, f.getDenominator());
+
+        try {
+            f1 = new Fraction(1, Integer.MAX_VALUE);
+            f2 = new Fraction(1, Integer.MAX_VALUE - 1);
+            f = f1.subtract(f2);
+            fail("expecting ArithmeticException");  //should overflow
+        } catch (ArithmeticException ex) {}
+
+        // denominator should not be a multiple of 2 or 3 to trigger overflow
+        f1 = new Fraction(Integer.MIN_VALUE, 5);
+        f2 = new Fraction(1,5);
+        try {
+            f = f1.subtract(f2); // should overflow
+            fail("expecting ArithmeticException but got: " + f.toString());
+        } catch (ArithmeticException ex) {}
+
+        try {
+            f= new Fraction(Integer.MIN_VALUE, 1);
+            f = f.subtract(Fraction.ONE);
+            fail("expecting ArithmeticException");
+        } catch (ArithmeticException ex) {}
+
+        try {
+            f= new Fraction(Integer.MAX_VALUE, 1);
+            f = f.subtract(Fraction.ONE.negate());
+            fail("expecting ArithmeticException");
+        } catch (ArithmeticException ex) {}
+
+        f1 = new Fraction(3,327680);
+        f2 = new Fraction(2,59049);
+        try {
+            f = f1.subtract(f2); // should overflow
+            fail("expecting ArithmeticException but got: " + f.toString());
+        } catch (ArithmeticException ex) {}
+    }
+
+    public void testEqualsAndHashCode() {
+        Fraction zero  = new Fraction(0,1);
+        Fraction nullFraction = null;
+        assertTrue( zero.equals(zero));
+        assertFalse(zero.equals(nullFraction));
+        assertFalse(zero.equals(Double.valueOf(0)));
+        Fraction zero2 = new Fraction(0,2);
+        assertTrue(zero.equals(zero2));
+        assertEquals(zero.hashCode(), zero2.hashCode());
+        Fraction one = new Fraction(1,1);
+        assertFalse((one.equals(zero) ||zero.equals(one)));
+    }
+
+    public void testGetReducedFraction() {
+        Fraction threeFourths = new Fraction(3, 4);
+        assertTrue(threeFourths.equals(Fraction.getReducedFraction(6, 8)));
+        assertTrue(Fraction.ZERO.equals(Fraction.getReducedFraction(0, -1)));
+        try {
+            Fraction.getReducedFraction(1, 0);
+            fail("expecting ArithmeticException");
+        } catch (ArithmeticException ex) {
+            // expected
+        }
+        assertEquals(Fraction.getReducedFraction
+                (2, Integer.MIN_VALUE).getNumerator(),-1);
+        assertEquals(Fraction.getReducedFraction
+                (1, -1).getNumerator(), -1);
+    }
+
+    public void testToString() {
+        assertEquals("0", new Fraction(0, 3).toString());
+        assertEquals("3", new Fraction(6, 2).toString());
+        assertEquals("2 / 3", new Fraction(18, 27).toString());
+    }
+
+    public void testSerial() throws FractionConversionException {
+        Fraction[] fractions = {
+            new Fraction(3, 4), Fraction.ONE, Fraction.ZERO,
+            new Fraction(17), new Fraction(FastMath.PI, 1000),
+            new Fraction(-5, 2)
+        };
+        for (Fraction fraction : fractions) {
+            assertEquals(fraction, TestUtils.serializeAndRecover(fraction));
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/genetics/BinaryChromosomeTest.java b/src/test/java/org/apache/commons/math/genetics/BinaryChromosomeTest.java
new file mode 100644
index 0000000..e084b2c
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/genetics/BinaryChromosomeTest.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.genetics;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+
+public class BinaryChromosomeTest {
+
+    @Test
+    public void testInvalidConstructor() {
+        Integer[][] reprs = new Integer[][] {
+                new Integer[] {0,1,0,1,2},
+                new Integer[] {0,1,0,1,-1}
+        };
+
+        for (Integer[] repr : reprs) {
+            try {
+                new DummyBinaryChromosome(repr);
+                fail("Exception not caught");
+            } catch (IllegalArgumentException e) {
+
+            }
+        }
+    }
+
+    @Test
+    public void testRandomConstructor() {
+        for (int i=0; i<20; i++) {
+            new DummyBinaryChromosome(BinaryChromosome.randomBinaryRepresentation(10));
+        }
+    }
+
+    @Test
+    public void testIsSame() {
+        Chromosome c1 = new DummyBinaryChromosome(new Integer[] {0,1,0,1,0,1});
+        Chromosome c2 = new DummyBinaryChromosome(new Integer[] {0,1,1,0,1});
+        Chromosome c3 = new DummyBinaryChromosome(new Integer[] {0,1,0,1,0,1,1});
+        Chromosome c4 = new DummyBinaryChromosome(new Integer[] {1,1,0,1,0,1});
+        Chromosome c5 = new DummyBinaryChromosome(new Integer[] {0,1,0,1,0,0});
+        Chromosome c6 = new DummyBinaryChromosome(new Integer[] {0,1,0,1,0,1});
+
+        assertFalse(c1.isSame(c2));
+        assertFalse(c1.isSame(c3));
+        assertFalse(c1.isSame(c4));
+        assertFalse(c1.isSame(c5));
+        assertTrue(c1.isSame(c6));
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/genetics/BinaryMutationTest.java b/src/test/java/org/apache/commons/math/genetics/BinaryMutationTest.java
new file mode 100644
index 0000000..3483db4
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/genetics/BinaryMutationTest.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 static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class BinaryMutationTest {
+
+    @Test
+    public void testMutate() {
+        BinaryMutation mutation = new BinaryMutation();
+
+        // stochastic testing :)
+        for (int i=0; i<20; i++) {
+            DummyBinaryChromosome original = new DummyBinaryChromosome(BinaryChromosome.randomBinaryRepresentation(10));
+            DummyBinaryChromosome mutated = (DummyBinaryChromosome) mutation.mutate(original);
+
+            // one gene should be different
+            int numDifferent = 0;
+            for (int j=0; j<original.getRepresentation().size(); j++) {
+                if (original.getRepresentation().get(j) != mutated.getRepresentation().get(j))
+                    numDifferent++;
+            }
+            assertEquals(1, numDifferent);
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/genetics/ChromosomeTest.java b/src/test/java/org/apache/commons/math/genetics/ChromosomeTest.java
new file mode 100644
index 0000000..79be0cc
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/genetics/ChromosomeTest.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;
+
+import static org.junit.Assert.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Test;
+
+public class ChromosomeTest {
+
+    @Test
+    public void testCompareTo() {
+        Chromosome c1 = new Chromosome() {
+            public double fitness() {
+                return 0;
+            }
+        };
+        Chromosome c2 = new Chromosome() {
+            public double fitness() {
+                return 10;
+            }
+        };
+        Chromosome c3 = new Chromosome() {
+            public double fitness() {
+                return 10;
+            }
+        };
+
+        assertTrue(c1.compareTo(c2) < 0);
+        assertTrue(c2.compareTo(c1) > 0);
+        assertEquals(0,c3.compareTo(c2));
+        assertEquals(0,c2.compareTo(c3));
+    }
+
+    private abstract static class DummyChromosome extends Chromosome {
+        private final int repr;
+
+        public DummyChromosome(final int repr) {
+            this.repr = repr;
+        }
+        @Override
+        protected boolean isSame(Chromosome another) {
+            return ((DummyChromosome) another).repr == repr;
+        }
+    }
+
+    @Test
+    public void testFindSameChromosome() {
+        Chromosome c1 = new DummyChromosome(1) {
+            public double fitness() {
+                return 1;
+            }
+        };
+        Chromosome c2 = new DummyChromosome(2) {
+            public double fitness() {
+                return 2;
+            }
+        };
+        Chromosome c3 = new DummyChromosome(3) {
+            public double fitness() {
+                return 3;
+            }
+        };
+        Chromosome c4 = new DummyChromosome(1) {
+            public double fitness() {
+                return 5;
+            }
+        };
+        Chromosome c5 = new DummyChromosome(15) {
+            public double fitness() {
+                return 15;
+            }
+        };
+
+        List<Chromosome> popChr = new ArrayList<Chromosome>();
+        popChr.add(c1);
+        popChr.add(c2);
+        popChr.add(c3);
+        Population pop = new ListPopulation(popChr,3) {
+            public Population nextGeneration() {
+                // not important
+                return null;
+            }
+        };
+
+        assertNull(c5.findSameChromosome(pop));
+        assertEquals(c1, c4.findSameChromosome(pop));
+
+        c4.searchForFitnessUpdate(pop);
+        assertEquals(1, c4.getFitness(),0);
+    }
+
+}
+
diff --git a/src/test/java/org/apache/commons/math/genetics/DummyBinaryChromosome.java b/src/test/java/org/apache/commons/math/genetics/DummyBinaryChromosome.java
new file mode 100644
index 0000000..f76cc57
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/genetics/DummyBinaryChromosome.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;
+
+/**
+ * Implementation of BinaryChromosome for testing purposes
+ */
+public class DummyBinaryChromosome extends BinaryChromosome {
+
+    public DummyBinaryChromosome(List<Integer> representation) {
+        super(representation);
+    }
+
+    public DummyBinaryChromosome(Integer[] representation) {
+        super(representation);
+    }
+
+    @Override
+    public AbstractListChromosome<Integer> newFixedLengthChromosome(List<Integer> chromosomeRepresentation) {
+        return new DummyBinaryChromosome(chromosomeRepresentation);
+    }
+
+    public double fitness() {
+        // uninteresting
+        return 0;
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/genetics/DummyRandomKey.java b/src/test/java/org/apache/commons/math/genetics/DummyRandomKey.java
new file mode 100644
index 0000000..4e688aa
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/genetics/DummyRandomKey.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;
+
+/**
+ * Implementation of RandomKey for testing purposes
+ */
+public class DummyRandomKey extends RandomKey<String> {
+
+    public DummyRandomKey(List<Double> representation) {
+        super(representation);
+    }
+
+    public DummyRandomKey(Double[] representation) {
+        super(representation);
+    }
+
+    @Override
+    public AbstractListChromosome<Double> newFixedLengthChromosome(List<Double> chromosomeRepresentation) {
+        return new DummyRandomKey(chromosomeRepresentation);
+    }
+
+    public double fitness() {
+        // unimportant
+        return 0;
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/genetics/ElitisticListPopulationTest.java b/src/test/java/org/apache/commons/math/genetics/ElitisticListPopulationTest.java
new file mode 100644
index 0000000..fad1945
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/genetics/ElitisticListPopulationTest.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.genetics;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class ElitisticListPopulationTest {
+
+    private static int counter = 0;
+
+    @Test
+    public void testNextGeneration() {
+        ElitisticListPopulation pop = new ElitisticListPopulation(100, 0.203);
+
+        for (int i=0; i<pop.getPopulationLimit(); i++) {
+            pop.addChromosome(new DummyChromosome());
+        }
+
+        Population nextGeneration = pop.nextGeneration();
+
+        assertEquals(20, nextGeneration.getPopulationSize());
+    }
+
+    private static class DummyChromosome extends Chromosome {
+        private final int fitness;
+
+        public DummyChromosome() {
+            this.fitness = counter;
+            counter++;
+        }
+
+        public double fitness() {
+            return this.fitness;
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/genetics/FitnessCachingTest.java b/src/test/java/org/apache/commons/math/genetics/FitnessCachingTest.java
new file mode 100644
index 0000000..99d7b5a
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/genetics/FitnessCachingTest.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.genetics;
+
+import static org.junit.Assert.*;
+
+import java.util.LinkedList;
+import java.util.List;
+import org.junit.Test;
+
+
+public class FitnessCachingTest {
+
+    // parameters for the GA
+    private static final int DIMENSION = 50;
+    private static final double CROSSOVER_RATE = 1;
+    private static final double MUTATION_RATE = 0.1;
+    private static final int TOURNAMENT_ARITY = 5;
+
+    private static final int POPULATION_SIZE = 10;
+    private static final int NUM_GENERATIONS = 50;
+    private static final double ELITISM_RATE = 0.2;
+
+    // how many times was the fitness computed
+    private static int fitnessCalls = 0;
+
+
+    @Test
+    public void testFitnessCaching() {
+        // initialize a new genetic algorithm
+        GeneticAlgorithm ga = new GeneticAlgorithm(
+                new OnePointCrossover<Integer>(),
+                CROSSOVER_RATE, // all selected chromosomes will be recombined (=crosssover)
+                new BinaryMutation(),
+                MUTATION_RATE, // no mutation
+                new TournamentSelection(TOURNAMENT_ARITY)
+        );
+
+        // initial population
+        Population initial = randomPopulation();
+        // stopping conditions
+        StoppingCondition stopCond = new FixedGenerationCount(NUM_GENERATIONS);
+
+        // run the algorithm
+        ga.evolve(initial, stopCond);
+
+        int neededCalls =
+            POPULATION_SIZE /*initial population*/ +
+            (NUM_GENERATIONS - 1) /*for each population*/ * (int)(POPULATION_SIZE * (1.0 - ELITISM_RATE)) /*some chromosomes are copied*/
+            ;
+        assertTrue(fitnessCalls <= neededCalls); // some chromosomes after crossover may be the same os old ones
+    }
+
+
+    /**
+     * Initializes a random population.
+     */
+    private static ElitisticListPopulation randomPopulation() {
+        List<Chromosome> popList = new LinkedList<Chromosome>();
+
+        for (int i=0; i<POPULATION_SIZE; i++) {
+            BinaryChromosome randChrom = new DummyCountingBinaryChromosome(BinaryChromosome.randomBinaryRepresentation(DIMENSION));
+            popList.add(randChrom);
+        }
+        return new ElitisticListPopulation(popList, popList.size(), ELITISM_RATE);
+    }
+
+    private static class DummyCountingBinaryChromosome extends DummyBinaryChromosome {
+
+        public DummyCountingBinaryChromosome(List<Integer> representation) {
+            super(representation);
+        }
+
+        @Override
+        public double fitness() {
+            fitnessCalls++;
+            return 0;
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/genetics/FixedGenerationCountTest.java b/src/test/java/org/apache/commons/math/genetics/FixedGenerationCountTest.java
new file mode 100644
index 0000000..66cbe4c
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/genetics/FixedGenerationCountTest.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;
+
+import static org.junit.Assert.*;
+
+import java.util.Iterator;
+
+import org.junit.Test;
+
+public class FixedGenerationCountTest {
+
+    @Test
+    public void testIsSatisfied() {
+        FixedGenerationCount fgc = new FixedGenerationCount(20);
+
+        int cnt = 0;
+        Population pop = new Population() {
+            public void addChromosome(Chromosome chromosome) {
+                // unimportant
+            }
+            public Chromosome getFittestChromosome() {
+                // unimportant
+                return null;
+            }
+            public int getPopulationLimit() {
+                // unimportant
+                return 0;
+            }
+            public int getPopulationSize() {
+                // unimportant
+                return 0;
+            }
+            public Population nextGeneration() {
+                // unimportant
+                return null;
+            }
+            public Iterator<Chromosome> iterator() {
+                // unimportant
+                return null;
+            }
+        };
+
+        while (!fgc.isSatisfied(pop))
+            cnt++;
+        assertEquals(20, cnt);
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/genetics/GeneticAlgorithmTestBinary.java b/src/test/java/org/apache/commons/math/genetics/GeneticAlgorithmTestBinary.java
new file mode 100644
index 0000000..8fffa56
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/genetics/GeneticAlgorithmTestBinary.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.genetics;
+
+import static org.junit.Assert.*;
+
+import java.util.LinkedList;
+import java.util.List;
+import org.junit.Test;
+
+/**
+ * This is also an example of usage.
+ */
+public class GeneticAlgorithmTestBinary {
+
+    // parameters for the GA
+    private static final int DIMENSION = 50;
+    private static final int POPULATION_SIZE = 50;
+    private static final int NUM_GENERATIONS = 50;
+    private static final double ELITISM_RATE = 0.2;
+    private static final double CROSSOVER_RATE = 1;
+    private static final double MUTATION_RATE = 0.1;
+    private static final int TOURNAMENT_ARITY = 2;
+
+    @Test
+    public void test() {
+        // to test a stochastic algorithm is hard, so this will rather be an usage example
+
+        // initialize a new genetic algorithm
+        GeneticAlgorithm ga = new GeneticAlgorithm(
+                new OnePointCrossover<Integer>(),
+                CROSSOVER_RATE, // all selected chromosomes will be recombined (=crosssover)
+                new BinaryMutation(),
+                MUTATION_RATE,
+                new TournamentSelection(TOURNAMENT_ARITY)
+        );
+
+        assertEquals(0, ga.getGenerationsEvolved());
+
+        // initial population
+        Population initial = randomPopulation();
+        // stopping conditions
+        StoppingCondition stopCond = new FixedGenerationCount(NUM_GENERATIONS);
+
+        // best initial chromosome
+        Chromosome bestInitial = initial.getFittestChromosome();
+
+        // run the algorithm
+        Population finalPopulation = ga.evolve(initial, stopCond);
+
+        // best chromosome from the final population
+        Chromosome bestFinal = finalPopulation.getFittestChromosome();
+
+        // the only thing we can test is whether the final solution is not worse than the initial one
+        // however, for some implementations of GA, this need not be true :)
+
+        assertTrue(bestFinal.compareTo(bestInitial) > 0);
+        assertEquals(NUM_GENERATIONS, ga.getGenerationsEvolved());
+
+    }
+
+
+
+
+    /**
+     * Initializes a random population.
+     */
+    private static ElitisticListPopulation randomPopulation() {
+        List<Chromosome> popList = new LinkedList<Chromosome>();
+
+        for (int i=0; i<POPULATION_SIZE; i++) {
+            BinaryChromosome randChrom = new FindOnes(BinaryChromosome.randomBinaryRepresentation(DIMENSION));
+            popList.add(randChrom);
+        }
+        return new ElitisticListPopulation(popList, popList.size(), ELITISM_RATE);
+    }
+
+    /**
+     * Chromosomes represented by a binary chromosome.
+     *
+     * The goal is to set all bits (genes) to 1.
+     */
+    private static class FindOnes extends BinaryChromosome {
+
+        public FindOnes(List<Integer> representation) {
+            super(representation);
+        }
+
+        /**
+         * Returns number of elements != 0
+         */
+        public double fitness() {
+            int num = 0;
+            for (int val : this.getRepresentation()) {
+                if (val != 0)
+                    num++;
+            }
+            // number of elements >= 0
+            return num;
+        }
+
+        @Override
+        public AbstractListChromosome<Integer> newFixedLengthChromosome(List<Integer> chromosomeRepresentation) {
+            return new FindOnes(chromosomeRepresentation);
+        }
+
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/genetics/GeneticAlgorithmTestPermutations.java b/src/test/java/org/apache/commons/math/genetics/GeneticAlgorithmTestPermutations.java
new file mode 100644
index 0000000..93dbcda
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/genetics/GeneticAlgorithmTestPermutations.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.genetics;
+
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.math.util.FastMath;
+import org.junit.Test;
+
+/**
+ * This is also an example of usage.
+ *
+ * This algorithm does "stochastic sorting" of a sequence 0,...,N.
+ *
+ */
+public class GeneticAlgorithmTestPermutations {
+
+    // parameters for the GA
+    private static final int DIMENSION = 20;
+    private static final int POPULATION_SIZE = 80;
+    private static final int NUM_GENERATIONS = 200;
+    private static final double ELITISM_RATE = 0.2;
+    private static final double CROSSOVER_RATE = 1;
+    private static final double MUTATION_RATE = 0.08;
+    private static final int TOURNAMENT_ARITY = 2;
+
+    // numbers from 0 to N-1
+    private static final List<Integer> sequence = new ArrayList<Integer>();
+    static {
+        for (int i=0; i<DIMENSION; i++) {
+            sequence.add(i);
+        }
+    }
+
+    @Test
+    public void test() {
+        // to test a stochastic algorithm is hard, so this will rather be an usage example
+
+        // initialize a new genetic algorithm
+        GeneticAlgorithm ga = new GeneticAlgorithm(
+                new OnePointCrossover<Integer>(),
+                CROSSOVER_RATE,
+                new RandomKeyMutation(),
+                MUTATION_RATE,
+                new TournamentSelection(TOURNAMENT_ARITY)
+        );
+
+        // initial population
+        Population initial = randomPopulation();
+        // stopping conditions
+        StoppingCondition stopCond = new FixedGenerationCount(NUM_GENERATIONS);
+
+        // best initial chromosome
+        Chromosome bestInitial = initial.getFittestChromosome();
+
+        // run the algorithm
+        Population finalPopulation = ga.evolve(initial, stopCond);
+
+        // best chromosome from the final population
+        Chromosome bestFinal = finalPopulation.getFittestChromosome();
+
+        // the only thing we can test is whether the final solution is not worse than the initial one
+        // however, for some implementations of GA, this need not be true :)
+
+        assertTrue(bestFinal.compareTo(bestInitial) > 0);
+
+        //System.out.println(bestInitial);
+        //System.out.println(bestFinal);
+    }
+
+
+    /**
+     * Initializes a random population
+     */
+    private static ElitisticListPopulation randomPopulation() {
+        List<Chromosome> popList = new ArrayList<Chromosome>();
+        for (int i=0; i<POPULATION_SIZE; i++) {
+            Chromosome randChrom = new MinPermutations(RandomKey.randomPermutation(DIMENSION));
+            popList.add(randChrom);
+        }
+        return new ElitisticListPopulation(popList, popList.size(), ELITISM_RATE);
+    }
+
+    /**
+     * Chromosomes representing a permutation of (0,1,2,...,DIMENSION-1).
+     *
+     * The goal is to sort the sequence.
+     */
+    private static class MinPermutations extends RandomKey<Integer> {
+
+        public MinPermutations(List<Double> representation) {
+            super(representation);
+        }
+
+        public double fitness() {
+            int res = 0;
+            List<Integer> decoded = decode(sequence);
+            for (int i=0; i<decoded.size(); i++) {
+                int value = decoded.get(i);
+                if (value != i) {
+                    // bad position found
+                    res += FastMath.abs(value - i);
+                }
+            }
+            // the most fitted chromosome is the one with minimal error
+            // therefore we must return negative value
+            return -res;
+        }
+
+        @Override
+        public AbstractListChromosome<Double> newFixedLengthChromosome(List<Double> chromosomeRepresentation) {
+            return new MinPermutations(chromosomeRepresentation);
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/genetics/ListPopulationTest.java b/src/test/java/org/apache/commons/math/genetics/ListPopulationTest.java
new file mode 100644
index 0000000..f43ef95
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/genetics/ListPopulationTest.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.genetics;
+
+import static org.junit.Assert.*;
+
+import java.util.ArrayList;
+
+import org.junit.Test;
+
+public class ListPopulationTest {
+
+    @Test
+    public void testGetFittestChromosome() {
+        Chromosome c1 = new Chromosome() {
+            public double fitness() {
+                return 0;
+            }
+        };
+        Chromosome c2 = new Chromosome() {
+            public double fitness() {
+                return 10;
+            }
+        };
+        Chromosome c3 = new Chromosome() {
+            public double fitness() {
+                return 15;
+            }
+        };
+
+        ArrayList<Chromosome> chromosomes = new ArrayList<Chromosome> ();
+        chromosomes.add(c1);
+        chromosomes.add(c2);
+        chromosomes.add(c3);
+
+        ListPopulation population = new ListPopulation(chromosomes,10) {
+
+            public Population nextGeneration() {
+                // not important
+                return null;
+            }
+        };
+
+        assertEquals(c3, population.getFittestChromosome());
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/genetics/OnePointCrossoverTest.java b/src/test/java/org/apache/commons/math/genetics/OnePointCrossoverTest.java
new file mode 100644
index 0000000..a332498
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/genetics/OnePointCrossoverTest.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.genetics;
+
+import static org.junit.Assert.*;
+import org.junit.Test;
+
+public class OnePointCrossoverTest {
+
+    @Test
+    public void testCrossover() {
+        Integer[] p1 = new Integer[] {1,0,1,0,0,1,0,1,1};
+        Integer[] p2 = new Integer[] {0,1,1,0,1,0,1,1,1};
+
+        BinaryChromosome p1c = new DummyBinaryChromosome(p1);
+        BinaryChromosome p2c = new DummyBinaryChromosome(p2);
+
+        OnePointCrossover<Integer> opc = new OnePointCrossover<Integer>();
+
+        // how to test a stochastic method?
+        for (int i=0; i<20; i++) {
+            ChromosomePair pair = opc.crossover(p1c,p2c);
+
+            Integer[] c1 = new Integer[p1.length];
+            Integer[] c2 = new Integer[p2.length];
+
+            c1 = ((BinaryChromosome) pair.getFirst()).getRepresentation().toArray(c1);
+            c2 = ((BinaryChromosome) pair.getSecond()).getRepresentation().toArray(c2);
+
+            // first and last values will be the same
+            assertEquals((int) p1[0], (int) c1[0]);
+            assertEquals((int) p2[0], (int) c2[0]);
+            assertEquals((int) p1[p1.length-1], (int) c1[c1.length-1]);
+            assertEquals((int) p2[p2.length-1], (int) c2[c2.length-1]);
+            // moreover, in the above setting, the 2nd, 3rd and 7th values will be the same
+            assertEquals((int) p1[2], (int) c1[2]);
+            assertEquals((int) p2[2], (int) c2[2]);
+            assertEquals((int) p1[3], (int) c1[3]);
+            assertEquals((int) p2[3], (int) c2[3]);
+            assertEquals((int) p1[7], (int) c1[7]);
+            assertEquals((int) p2[7], (int) c2[7]);
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/genetics/RandomKeyMutationTest.java b/src/test/java/org/apache/commons/math/genetics/RandomKeyMutationTest.java
new file mode 100644
index 0000000..dc5bab5
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/genetics/RandomKeyMutationTest.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 static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class RandomKeyMutationTest {
+
+    @Test
+    public void testMutate() {
+        MutationPolicy mutation = new RandomKeyMutation();
+        int l=10;
+        for (int i=0; i<20; i++) {
+            DummyRandomKey origRk = new DummyRandomKey(RandomKey.randomPermutation(l));
+            Chromosome mutated = mutation.mutate(origRk);
+            DummyRandomKey mutatedRk = (DummyRandomKey) mutated;
+
+            int changes = 0;
+            for (int j=0; j<origRk.getLength(); j++) {
+                if (origRk.getRepresentation().get(j) != mutatedRk.getRepresentation().get(j)) {
+                    changes++;
+                }
+            }
+            assertEquals(1,changes);
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/genetics/RandomKeyTest.java b/src/test/java/org/apache/commons/math/genetics/RandomKeyTest.java
new file mode 100644
index 0000000..887d5c8
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/genetics/RandomKeyTest.java
@@ -0,0 +1,164 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.genetics;
+
+import static org.junit.Assert.*;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+
+import org.junit.Test;
+
+public class RandomKeyTest {
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testConstructor1() {
+        new DummyRandomKey(new Double[] {0.2, 0.3, 1.2});
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void testConstructor2() {
+        new DummyRandomKey(new Double[] {0.2, 0.3, -0.2});
+    }
+
+    @Test
+    public void testIsSame() {
+        DummyRandomKey drk1 = new DummyRandomKey(new Double[] {0.4, 0.1, 0.5, 0.8, 0.2});
+        DummyRandomKey drk2 = new DummyRandomKey(new Double[] {0.4, 0.1, 0.5, 0.8, 0.2});
+        DummyRandomKey drk3 = new DummyRandomKey(new Double[] {0.4, 0.15, 0.5, 0.8, 0.2});
+        DummyRandomKey drk4 = new DummyRandomKey(new Double[] {0.4, 0.25, 0.5, 0.8, 0.2});
+        DummyRandomKey drk5 = new DummyRandomKey(new Double[] {0.4, 0.25, 0.5, 0.8, 0.2, 0.5});
+
+        assertTrue(drk1.isSame(drk2));
+        assertTrue(drk2.isSame(drk3));
+        assertFalse(drk3.isSame(drk4));
+        assertFalse(drk4.isSame(drk5));
+    }
+
+    @Test
+    public void testDecode() {
+        DummyRandomKey drk = new DummyRandomKey(new Double[] {0.4, 0.1, 0.5, 0.8, 0.2});
+        List<String> decoded = drk.decode(Arrays.asList(new String[] {"a", "b", "c", "d", "e"}));
+
+        assertEquals("b", decoded.get(0));
+        assertEquals("e", decoded.get(1));
+        assertEquals("a", decoded.get(2));
+        assertEquals("c", decoded.get(3));
+        assertEquals("d", decoded.get(4));
+    }
+
+    @Test
+    public void testRandomPermutation() {
+        // never generate an invalid one
+        for (int i=0; i<10; i++) {
+            DummyRandomKey drk = new DummyRandomKey(RandomKey.randomPermutation(20));
+            assertNotNull(drk);
+        }
+    }
+
+    @Test
+    public void testIdentityPermutation() {
+        DummyRandomKey drk = new DummyRandomKey(RandomKey.identityPermutation(5));
+        List<String> decoded = drk.decode(Arrays.asList(new String[] {"a", "b", "c", "d", "e"}));
+
+        assertEquals("a", decoded.get(0));
+        assertEquals("b", decoded.get(1));
+        assertEquals("c", decoded.get(2));
+        assertEquals("d", decoded.get(3));
+        assertEquals("e", decoded.get(4));
+    }
+
+    @Test
+    public void testComparatorPermutation() {
+        List<String> data = Arrays.asList(new String[] {"x", "b", "c", "z", "b"});
+
+        List<Double> permutation = RandomKey.comparatorPermutation(data, new Comparator<String>() {
+            public int compare(String o1, String o2) {
+                return o1.compareTo(o2);
+            }
+        });
+        Double[] permArr = new Double[data.size()];
+        permArr = permutation.toArray(permArr);
+        assertArrayEquals(new Double[] {0.6,0.0,0.4,0.8,0.2}, permArr);
+        List<String> decodedData = new DummyRandomKey(permutation).decode(data);
+        assertEquals("b", decodedData.get(0));
+        assertEquals("b", decodedData.get(1));
+        assertEquals("c", decodedData.get(2));
+        assertEquals("x", decodedData.get(3));
+        assertEquals("z", decodedData.get(4));
+
+        permutation = RandomKey.comparatorPermutation(data, new Comparator<String>() {
+            public int compare(String o1, String o2) {
+                return o2.compareTo(o1);
+            }
+        });
+        permArr = new Double[data.size()];
+        permArr = permutation.toArray(permArr);
+        assertArrayEquals(new Double[] {0.2,0.6,0.4,0.0,0.8}, permArr);
+        decodedData = new DummyRandomKey(permutation).decode(data);
+        assertEquals("z", decodedData.get(0));
+        assertEquals("x", decodedData.get(1));
+        assertEquals("c", decodedData.get(2));
+        assertEquals("b", decodedData.get(3));
+        assertEquals("b", decodedData.get(4));
+    }
+
+    @Test
+    public void testInducedPermutation() {
+        List<String> origData = Arrays.asList(new String[] {"a", "b", "c", "d", "d"});
+        List<String> permutedData = Arrays.asList(new String[] {"d", "b", "c", "a", "d"});
+
+        DummyRandomKey drk = new DummyRandomKey(RandomKey.inducedPermutation(origData, permutedData));
+        List<String> decoded = drk.decode(origData);
+
+        assertEquals("d", decoded.get(0));
+        assertEquals("b", decoded.get(1));
+        assertEquals("c", decoded.get(2));
+        assertEquals("a", decoded.get(3));
+        assertEquals("d", decoded.get(4));
+
+        try {
+            RandomKey.inducedPermutation(
+                    Arrays.asList(new String[] {"a", "b", "c", "d", "d"}),
+                    Arrays.asList(new String[] {"a", "b", "c", "d"})
+            );
+            fail("Uncaught exception");
+        } catch (IllegalArgumentException e) {
+            // no-op
+        }
+        try {
+            RandomKey.inducedPermutation(
+                    Arrays.asList(new String[] {"a", "b", "c", "d", "d"}),
+                    Arrays.asList(new String[] {"a", "b", "c", "d", "f"})
+            );
+            fail("Uncaught exception");
+        } catch (IllegalArgumentException e) {
+            // no-op
+        }
+    }
+
+    @Test
+    public void testEqualRepr() {
+        DummyRandomKey drk = new DummyRandomKey(new Double[] {0.2, 0.2, 0.5});
+        List<String> decodedData = drk.decode(Arrays.asList(new String[] {"a", "b", "c"}));
+        assertEquals("a", decodedData.get(0));
+        assertEquals("b", decodedData.get(1));
+        assertEquals("c", decodedData.get(2));
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/genetics/TournamentSelectionTest.java b/src/test/java/org/apache/commons/math/genetics/TournamentSelectionTest.java
new file mode 100644
index 0000000..d6b3f7a
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/genetics/TournamentSelectionTest.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.genetics;
+
+import static org.junit.Assert.*;
+import org.junit.Test;
+
+public class TournamentSelectionTest {
+
+    private static int counter = 0;
+
+    @Test
+    public void testSelect() {
+        TournamentSelection ts = new TournamentSelection(2);
+        ElitisticListPopulation pop = new ElitisticListPopulation(100, 0.203);
+
+        for (int i=0; i<pop.getPopulationLimit(); i++) {
+            pop.addChromosome(new DummyChromosome());
+        }
+        // how to write a test for stochastic method?
+        for (int i=0; i<20; i++) {
+            ChromosomePair pair = ts.select(pop);
+            // the worst chromosome should NEVER be selected
+            assertTrue(pair.getFirst().getFitness() > 0);
+            assertTrue(pair.getSecond().getFitness() > 0);
+        }
+    }
+
+    private static class DummyChromosome extends Chromosome {
+        private final int fitness;
+
+        public DummyChromosome() {
+            this.fitness = counter;
+            counter++;
+        }
+
+        public double fitness() {
+            return this.fitness;
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/geometry/FrenchVector3DFormatTest.java b/src/test/java/org/apache/commons/math/geometry/FrenchVector3DFormatTest.java
new file mode 100644
index 0000000..c70b1d9
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/geometry/FrenchVector3DFormatTest.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.geometry;
+
+import java.util.Locale;
+
+
+public class FrenchVector3DFormatTest extends Vector3DFormatAbstractTest {
+
+    @Override
+    protected char getDecimalCharacter() {
+        return ',';
+    }
+
+    @Override
+    protected Locale getLocale() {
+        return Locale.FRENCH;
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/geometry/RotationOrderTest.java b/src/test/java/org/apache/commons/math/geometry/RotationOrderTest.java
new file mode 100644
index 0000000..d80f341
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/geometry/RotationOrderTest.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.geometry;
+
+import java.lang.reflect.Field;
+
+import org.apache.commons.math.geometry.RotationOrder;
+
+import junit.framework.*;
+
+public class RotationOrderTest
+  extends TestCase {
+
+  public RotationOrderTest(String name) {
+    super(name);
+  }
+
+  public void testName() {
+
+    RotationOrder[] orders = {
+      RotationOrder.XYZ, RotationOrder.XZY, RotationOrder.YXZ,
+      RotationOrder.YZX, RotationOrder.ZXY, RotationOrder.ZYX,
+      RotationOrder.XYX, RotationOrder.XZX, RotationOrder.YXY,
+      RotationOrder.YZY, RotationOrder.ZXZ, RotationOrder.ZYZ
+    };
+
+    for (int i = 0; i < orders.length; ++i) {
+      assertEquals(getFieldName(orders[i]), orders[i].toString());
+    }
+
+  }
+
+  private String getFieldName(RotationOrder order) {
+    try {
+      Field[] fields = RotationOrder.class.getFields();
+      for (int i = 0; i < fields.length; ++i) {
+        if (fields[i].get(null) == order) {
+          return fields[i].getName();
+        }
+      }
+    } catch (IllegalAccessException iae) {
+      // ignored
+    }
+    return "unknown";
+  }
+
+}
diff --git a/src/test/java/org/apache/commons/math/geometry/RotationTest.java b/src/test/java/org/apache/commons/math/geometry/RotationTest.java
new file mode 100644
index 0000000..9730fbd
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/geometry/RotationTest.java
@@ -0,0 +1,488 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.geometry.CardanEulerSingularityException;
+import org.apache.commons.math.geometry.NotARotationMatrixException;
+import org.apache.commons.math.geometry.Rotation;
+import org.apache.commons.math.geometry.RotationOrder;
+import org.apache.commons.math.geometry.Vector3D;
+import org.apache.commons.math.util.FastMath;
+import org.apache.commons.math.util.MathUtils;
+
+import junit.framework.*;
+
+public class RotationTest
+  extends TestCase {
+
+  public RotationTest(String name) {
+    super(name);
+  }
+
+  public void testIdentity() {
+
+    Rotation r = Rotation.IDENTITY;
+    checkVector(r.applyTo(Vector3D.PLUS_I), Vector3D.PLUS_I);
+    checkVector(r.applyTo(Vector3D.PLUS_J), Vector3D.PLUS_J);
+    checkVector(r.applyTo(Vector3D.PLUS_K), Vector3D.PLUS_K);
+    checkAngle(r.getAngle(), 0);
+
+    r = new Rotation(-1, 0, 0, 0, false);
+    checkVector(r.applyTo(Vector3D.PLUS_I), Vector3D.PLUS_I);
+    checkVector(r.applyTo(Vector3D.PLUS_J), Vector3D.PLUS_J);
+    checkVector(r.applyTo(Vector3D.PLUS_K), Vector3D.PLUS_K);
+    checkAngle(r.getAngle(), 0);
+
+    r = new Rotation(42, 0, 0, 0, true);
+    checkVector(r.applyTo(Vector3D.PLUS_I), Vector3D.PLUS_I);
+    checkVector(r.applyTo(Vector3D.PLUS_J), Vector3D.PLUS_J);
+    checkVector(r.applyTo(Vector3D.PLUS_K), Vector3D.PLUS_K);
+    checkAngle(r.getAngle(), 0);
+
+  }
+
+  public void testAxisAngle() {
+
+    Rotation r = new Rotation(new Vector3D(10, 10, 10), 2 * FastMath.PI / 3);
+    checkVector(r.applyTo(Vector3D.PLUS_I), Vector3D.PLUS_J);
+    checkVector(r.applyTo(Vector3D.PLUS_J), Vector3D.PLUS_K);
+    checkVector(r.applyTo(Vector3D.PLUS_K), Vector3D.PLUS_I);
+    double s = 1 / FastMath.sqrt(3);
+    checkVector(r.getAxis(), new Vector3D(s, s, s));
+    checkAngle(r.getAngle(), 2 * FastMath.PI / 3);
+
+    try {
+      new Rotation(new Vector3D(0, 0, 0), 2 * FastMath.PI / 3);
+      fail("an exception should have been thrown");
+    } catch (ArithmeticException e) {
+    }
+
+    r = new Rotation(Vector3D.PLUS_K, 1.5 * FastMath.PI);
+    checkVector(r.getAxis(), new Vector3D(0, 0, -1));
+    checkAngle(r.getAngle(), 0.5 * FastMath.PI);
+
+    r = new Rotation(Vector3D.PLUS_J, FastMath.PI);
+    checkVector(r.getAxis(), Vector3D.PLUS_J);
+    checkAngle(r.getAngle(), FastMath.PI);
+
+    checkVector(Rotation.IDENTITY.getAxis(), Vector3D.PLUS_I);
+
+  }
+
+  public void testRevert() {
+    Rotation r = new Rotation(0.001, 0.36, 0.48, 0.8, true);
+    Rotation reverted = r.revert();
+    checkRotation(r.applyTo(reverted), 1, 0, 0, 0);
+    checkRotation(reverted.applyTo(r), 1, 0, 0, 0);
+    assertEquals(r.getAngle(), reverted.getAngle(), 1.0e-12);
+    assertEquals(-1, Vector3D.dotProduct(r.getAxis(), reverted.getAxis()), 1.0e-12);
+  }
+
+  public void testVectorOnePair() {
+
+    Vector3D u = new Vector3D(3, 2, 1);
+    Vector3D v = new Vector3D(-4, 2, 2);
+    Rotation r = new Rotation(u, v);
+    checkVector(r.applyTo(u.scalarMultiply(v.getNorm())), v.scalarMultiply(u.getNorm()));
+
+    checkAngle(new Rotation(u, u.negate()).getAngle(), FastMath.PI);
+
+    try {
+        new Rotation(u, Vector3D.ZERO);
+        fail("an exception should have been thrown");
+    } catch (IllegalArgumentException e) {
+        // expected behavior
+    }
+
+  }
+
+  public void testVectorTwoPairs() {
+
+    Vector3D u1 = new Vector3D(3, 0, 0);
+    Vector3D u2 = new Vector3D(0, 5, 0);
+    Vector3D v1 = new Vector3D(0, 0, 2);
+    Vector3D v2 = new Vector3D(-2, 0, 2);
+    Rotation r = new Rotation(u1, u2, v1, v2);
+    checkVector(r.applyTo(Vector3D.PLUS_I), Vector3D.PLUS_K);
+    checkVector(r.applyTo(Vector3D.PLUS_J), Vector3D.MINUS_I);
+
+    r = new Rotation(u1, u2, u1.negate(), u2.negate());
+    Vector3D axis = r.getAxis();
+    if (Vector3D.dotProduct(axis, Vector3D.PLUS_K) > 0) {
+      checkVector(axis, Vector3D.PLUS_K);
+    } else {
+      checkVector(axis, Vector3D.MINUS_K);
+    }
+    checkAngle(r.getAngle(), FastMath.PI);
+
+    double sqrt = FastMath.sqrt(2) / 2;
+    r = new Rotation(Vector3D.PLUS_I,  Vector3D.PLUS_J,
+                     new Vector3D(0.5, 0.5,  sqrt),
+                     new Vector3D(0.5, 0.5, -sqrt));
+    checkRotation(r, sqrt, 0.5, 0.5, 0);
+
+    r = new Rotation(u1, u2, u1, Vector3D.crossProduct(u1, u2));
+    checkRotation(r, sqrt, -sqrt, 0, 0);
+
+    checkRotation(new Rotation(u1, u2, u1, u2), 1, 0, 0, 0);
+
+    try {
+        new Rotation(u1, u2, Vector3D.ZERO, v2);
+        fail("an exception should have been thrown");
+    } catch (IllegalArgumentException e) {
+      // expected behavior
+    }
+
+  }
+
+  public void testMatrix()
+    throws NotARotationMatrixException {
+
+    try {
+      new Rotation(new double[][] {
+                     { 0.0, 1.0, 0.0 },
+                     { 1.0, 0.0, 0.0 }
+                   }, 1.0e-7);
+      fail("Expecting NotARotationMatrixException");
+    } catch (NotARotationMatrixException nrme) {
+      // expected behavior
+    }
+
+    try {
+      new Rotation(new double[][] {
+                     {  0.445888,  0.797184, -0.407040 },
+                     {  0.821760, -0.184320,  0.539200 },
+                     { -0.354816,  0.574912,  0.737280 }
+                   }, 1.0e-7);
+      fail("Expecting NotARotationMatrixException");
+    } catch (NotARotationMatrixException nrme) {
+      // expected behavior
+    }
+
+    try {
+        new Rotation(new double[][] {
+                       {  0.4,  0.8, -0.4 },
+                       { -0.4,  0.6,  0.7 },
+                       {  0.8, -0.2,  0.5 }
+                     }, 1.0e-15);
+        fail("Expecting NotARotationMatrixException");
+      } catch (NotARotationMatrixException nrme) {
+        // expected behavior
+      }
+
+    checkRotation(new Rotation(new double[][] {
+                                 {  0.445888,  0.797184, -0.407040 },
+                                 { -0.354816,  0.574912,  0.737280 },
+                                 {  0.821760, -0.184320,  0.539200 }
+                               }, 1.0e-10),
+                  0.8, 0.288, 0.384, 0.36);
+
+    checkRotation(new Rotation(new double[][] {
+                                 {  0.539200,  0.737280,  0.407040 },
+                                 {  0.184320, -0.574912,  0.797184 },
+                                 {  0.821760, -0.354816, -0.445888 }
+                              }, 1.0e-10),
+                  0.36, 0.8, 0.288, 0.384);
+
+    checkRotation(new Rotation(new double[][] {
+                                 { -0.445888,  0.797184, -0.407040 },
+                                 {  0.354816,  0.574912,  0.737280 },
+                                 {  0.821760,  0.184320, -0.539200 }
+                               }, 1.0e-10),
+                  0.384, 0.36, 0.8, 0.288);
+
+    checkRotation(new Rotation(new double[][] {
+                                 { -0.539200,  0.737280,  0.407040 },
+                                 { -0.184320, -0.574912,  0.797184 },
+                                 {  0.821760,  0.354816,  0.445888 }
+                               }, 1.0e-10),
+                  0.288, 0.384, 0.36, 0.8);
+
+    double[][] m1 = { { 0.0, 1.0, 0.0 },
+                      { 0.0, 0.0, 1.0 },
+                      { 1.0, 0.0, 0.0 } };
+    Rotation r = new Rotation(m1, 1.0e-7);
+    checkVector(r.applyTo(Vector3D.PLUS_I), Vector3D.PLUS_K);
+    checkVector(r.applyTo(Vector3D.PLUS_J), Vector3D.PLUS_I);
+    checkVector(r.applyTo(Vector3D.PLUS_K), Vector3D.PLUS_J);
+
+    double[][] m2 = { { 0.83203, -0.55012, -0.07139 },
+                      { 0.48293,  0.78164, -0.39474 },
+                      { 0.27296,  0.29396,  0.91602 } };
+    r = new Rotation(m2, 1.0e-12);
+
+    double[][] m3 = r.getMatrix();
+    double d00 = m2[0][0] - m3[0][0];
+    double d01 = m2[0][1] - m3[0][1];
+    double d02 = m2[0][2] - m3[0][2];
+    double d10 = m2[1][0] - m3[1][0];
+    double d11 = m2[1][1] - m3[1][1];
+    double d12 = m2[1][2] - m3[1][2];
+    double d20 = m2[2][0] - m3[2][0];
+    double d21 = m2[2][1] - m3[2][1];
+    double d22 = m2[2][2] - m3[2][2];
+
+    assertTrue(FastMath.abs(d00) < 6.0e-6);
+    assertTrue(FastMath.abs(d01) < 6.0e-6);
+    assertTrue(FastMath.abs(d02) < 6.0e-6);
+    assertTrue(FastMath.abs(d10) < 6.0e-6);
+    assertTrue(FastMath.abs(d11) < 6.0e-6);
+    assertTrue(FastMath.abs(d12) < 6.0e-6);
+    assertTrue(FastMath.abs(d20) < 6.0e-6);
+    assertTrue(FastMath.abs(d21) < 6.0e-6);
+    assertTrue(FastMath.abs(d22) < 6.0e-6);
+
+    assertTrue(FastMath.abs(d00) > 4.0e-7);
+    assertTrue(FastMath.abs(d01) > 4.0e-7);
+    assertTrue(FastMath.abs(d02) > 4.0e-7);
+    assertTrue(FastMath.abs(d10) > 4.0e-7);
+    assertTrue(FastMath.abs(d11) > 4.0e-7);
+    assertTrue(FastMath.abs(d12) > 4.0e-7);
+    assertTrue(FastMath.abs(d20) > 4.0e-7);
+    assertTrue(FastMath.abs(d21) > 4.0e-7);
+    assertTrue(FastMath.abs(d22) > 4.0e-7);
+
+    for (int i = 0; i < 3; ++i) {
+      for (int j = 0; j < 3; ++j) {
+        double m3tm3 = m3[i][0] * m3[j][0]
+                     + m3[i][1] * m3[j][1]
+                     + m3[i][2] * m3[j][2];
+        if (i == j) {
+          assertTrue(FastMath.abs(m3tm3 - 1.0) < 1.0e-10);
+        } else {
+          assertTrue(FastMath.abs(m3tm3) < 1.0e-10);
+        }
+      }
+    }
+
+    checkVector(r.applyTo(Vector3D.PLUS_I),
+                new Vector3D(m3[0][0], m3[1][0], m3[2][0]));
+    checkVector(r.applyTo(Vector3D.PLUS_J),
+                new Vector3D(m3[0][1], m3[1][1], m3[2][1]));
+    checkVector(r.applyTo(Vector3D.PLUS_K),
+                new Vector3D(m3[0][2], m3[1][2], m3[2][2]));
+
+    double[][] m4 = { { 1.0,  0.0,  0.0 },
+                      { 0.0, -1.0,  0.0 },
+                      { 0.0,  0.0, -1.0 } };
+    r = new Rotation(m4, 1.0e-7);
+    checkAngle(r.getAngle(), FastMath.PI);
+
+    try {
+      double[][] m5 = { { 0.0, 0.0, 1.0 },
+                        { 0.0, 1.0, 0.0 },
+                        { 1.0, 0.0, 0.0 } };
+      r = new Rotation(m5, 1.0e-7);
+      fail("got " + r + ", should have caught an exception");
+    } catch (NotARotationMatrixException e) {
+      // expected
+    }
+
+  }
+
+  public void testAngles()
+    throws CardanEulerSingularityException {
+
+    RotationOrder[] CardanOrders = {
+      RotationOrder.XYZ, RotationOrder.XZY, RotationOrder.YXZ,
+      RotationOrder.YZX, RotationOrder.ZXY, RotationOrder.ZYX
+    };
+
+    for (int i = 0; i < CardanOrders.length; ++i) {
+      for (double alpha1 = 0.1; alpha1 < 6.2; alpha1 += 0.3) {
+        for (double alpha2 = -1.55; alpha2 < 1.55; alpha2 += 0.3) {
+          for (double alpha3 = 0.1; alpha3 < 6.2; alpha3 += 0.3) {
+            Rotation r = new Rotation(CardanOrders[i], alpha1, alpha2, alpha3);
+            double[] angles = r.getAngles(CardanOrders[i]);
+            checkAngle(angles[0], alpha1);
+            checkAngle(angles[1], alpha2);
+            checkAngle(angles[2], alpha3);
+          }
+        }
+      }
+    }
+
+    RotationOrder[] EulerOrders = {
+            RotationOrder.XYX, RotationOrder.XZX, RotationOrder.YXY,
+            RotationOrder.YZY, RotationOrder.ZXZ, RotationOrder.ZYZ
+          };
+
+    for (int i = 0; i < EulerOrders.length; ++i) {
+      for (double alpha1 = 0.1; alpha1 < 6.2; alpha1 += 0.3) {
+        for (double alpha2 = 0.05; alpha2 < 3.1; alpha2 += 0.3) {
+          for (double alpha3 = 0.1; alpha3 < 6.2; alpha3 += 0.3) {
+            Rotation r = new Rotation(EulerOrders[i],
+                                      alpha1, alpha2, alpha3);
+            double[] angles = r.getAngles(EulerOrders[i]);
+            checkAngle(angles[0], alpha1);
+            checkAngle(angles[1], alpha2);
+            checkAngle(angles[2], alpha3);
+          }
+        }
+      }
+    }
+
+  }
+
+  public void testSingularities() {
+
+    RotationOrder[] CardanOrders = {
+      RotationOrder.XYZ, RotationOrder.XZY, RotationOrder.YXZ,
+      RotationOrder.YZX, RotationOrder.ZXY, RotationOrder.ZYX
+    };
+
+    double[] singularCardanAngle = { FastMath.PI / 2, -FastMath.PI / 2 };
+    for (int i = 0; i < CardanOrders.length; ++i) {
+      for (int j = 0; j < singularCardanAngle.length; ++j) {
+        Rotation r = new Rotation(CardanOrders[i], 0.1, singularCardanAngle[j], 0.3);
+        try {
+          r.getAngles(CardanOrders[i]);
+          fail("an exception should have been caught");
+        } catch (CardanEulerSingularityException cese) {
+          // expected behavior
+        }
+      }
+    }
+
+    RotationOrder[] EulerOrders = {
+            RotationOrder.XYX, RotationOrder.XZX, RotationOrder.YXY,
+            RotationOrder.YZY, RotationOrder.ZXZ, RotationOrder.ZYZ
+          };
+
+    double[] singularEulerAngle = { 0, FastMath.PI };
+    for (int i = 0; i < EulerOrders.length; ++i) {
+      for (int j = 0; j < singularEulerAngle.length; ++j) {
+        Rotation r = new Rotation(EulerOrders[i], 0.1, singularEulerAngle[j], 0.3);
+        try {
+          r.getAngles(EulerOrders[i]);
+          fail("an exception should have been caught");
+        } catch (CardanEulerSingularityException cese) {
+          // expected behavior
+        }
+      }
+    }
+
+
+  }
+
+  public void testQuaternion() {
+
+    Rotation r1 = new Rotation(new Vector3D(2, -3, 5), 1.7);
+    double n = 23.5;
+    Rotation r2 = new Rotation(n * r1.getQ0(), n * r1.getQ1(),
+                               n * r1.getQ2(), n * r1.getQ3(),
+                               true);
+    for (double x = -0.9; x < 0.9; x += 0.2) {
+      for (double y = -0.9; y < 0.9; y += 0.2) {
+        for (double z = -0.9; z < 0.9; z += 0.2) {
+          Vector3D u = new Vector3D(x, y, z);
+          checkVector(r2.applyTo(u), r1.applyTo(u));
+        }
+      }
+    }
+
+    r1 = new Rotation( 0.288,  0.384,  0.36,  0.8, false);
+    checkRotation(r1, -r1.getQ0(), -r1.getQ1(), -r1.getQ2(), -r1.getQ3());
+
+  }
+
+  public void testCompose() {
+
+    Rotation r1 = new Rotation(new Vector3D(2, -3, 5), 1.7);
+    Rotation r2 = new Rotation(new Vector3D(-1, 3, 2), 0.3);
+    Rotation r3 = r2.applyTo(r1);
+
+    for (double x = -0.9; x < 0.9; x += 0.2) {
+      for (double y = -0.9; y < 0.9; y += 0.2) {
+        for (double z = -0.9; z < 0.9; z += 0.2) {
+          Vector3D u = new Vector3D(x, y, z);
+          checkVector(r2.applyTo(r1.applyTo(u)), r3.applyTo(u));
+        }
+      }
+    }
+
+  }
+
+  public void testComposeInverse() {
+
+    Rotation r1 = new Rotation(new Vector3D(2, -3, 5), 1.7);
+    Rotation r2 = new Rotation(new Vector3D(-1, 3, 2), 0.3);
+    Rotation r3 = r2.applyInverseTo(r1);
+
+    for (double x = -0.9; x < 0.9; x += 0.2) {
+      for (double y = -0.9; y < 0.9; y += 0.2) {
+        for (double z = -0.9; z < 0.9; z += 0.2) {
+          Vector3D u = new Vector3D(x, y, z);
+          checkVector(r2.applyInverseTo(r1.applyTo(u)), r3.applyTo(u));
+        }
+      }
+    }
+
+  }
+
+  public void testApplyInverseTo() {
+
+    Rotation r = new Rotation(new Vector3D(2, -3, 5), 1.7);
+    for (double lambda = 0; lambda < 6.2; lambda += 0.2) {
+      for (double phi = -1.55; phi < 1.55; phi += 0.2) {
+          Vector3D u = new Vector3D(FastMath.cos(lambda) * FastMath.cos(phi),
+                                    FastMath.sin(lambda) * FastMath.cos(phi),
+                                    FastMath.sin(phi));
+          r.applyInverseTo(r.applyTo(u));
+          checkVector(u, r.applyInverseTo(r.applyTo(u)));
+          checkVector(u, r.applyTo(r.applyInverseTo(u)));
+      }
+    }
+
+    r = Rotation.IDENTITY;
+    for (double lambda = 0; lambda < 6.2; lambda += 0.2) {
+      for (double phi = -1.55; phi < 1.55; phi += 0.2) {
+          Vector3D u = new Vector3D(FastMath.cos(lambda) * FastMath.cos(phi),
+                                    FastMath.sin(lambda) * FastMath.cos(phi),
+                                    FastMath.sin(phi));
+          checkVector(u, r.applyInverseTo(r.applyTo(u)));
+          checkVector(u, r.applyTo(r.applyInverseTo(u)));
+      }
+    }
+
+    r = new Rotation(Vector3D.PLUS_K, FastMath.PI);
+    for (double lambda = 0; lambda < 6.2; lambda += 0.2) {
+      for (double phi = -1.55; phi < 1.55; phi += 0.2) {
+          Vector3D u = new Vector3D(FastMath.cos(lambda) * FastMath.cos(phi),
+                                    FastMath.sin(lambda) * FastMath.cos(phi),
+                                    FastMath.sin(phi));
+          checkVector(u, r.applyInverseTo(r.applyTo(u)));
+          checkVector(u, r.applyTo(r.applyInverseTo(u)));
+      }
+    }
+
+  }
+
+  private void checkVector(Vector3D v1, Vector3D v2) {
+    assertTrue(v1.subtract(v2).getNorm() < 1.0e-10);
+  }
+
+  private void checkAngle(double a1, double a2) {
+    assertEquals(a1, MathUtils.normalizeAngle(a2, a1), 1.0e-10);
+  }
+
+  private void checkRotation(Rotation r, double q0, double q1, double q2, double q3) {
+    assertEquals(0, Rotation.distance(r, new Rotation(q0, q1, q2, q3, false)), 1.0e-12);
+  }
+
+}
diff --git a/src/test/java/org/apache/commons/math/geometry/Vector3DFormatAbstractTest.java b/src/test/java/org/apache/commons/math/geometry/Vector3DFormatAbstractTest.java
new file mode 100644
index 0000000..ff32187
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/geometry/Vector3DFormatAbstractTest.java
@@ -0,0 +1,361 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.geometry;
+
+import java.text.NumberFormat;
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.util.Locale;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.math.util.CompositeFormat;
+
+public abstract class Vector3DFormatAbstractTest extends TestCase {
+
+    Vector3DFormat vector3DFormat = null;
+    Vector3DFormat vector3DFormatSquare = null;
+
+    protected abstract Locale getLocale();
+
+    protected abstract char getDecimalCharacter();
+
+    @Override
+    protected void setUp() throws Exception {
+        vector3DFormat = Vector3DFormat.getInstance(getLocale());
+        final NumberFormat nf = NumberFormat.getInstance(getLocale());
+        nf.setMaximumFractionDigits(2);
+        vector3DFormatSquare = new Vector3DFormat("[", "]", " : ", nf);
+    }
+
+    public void testSimpleNoDecimals() {
+        Vector3D c = new Vector3D(1, 1, 1);
+        String expected = "{1; 1; 1}";
+        String actual = vector3DFormat.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void testSimpleWithDecimals() {
+        Vector3D c = new Vector3D(1.23, 1.43, 1.63);
+        String expected =
+            "{1"    + getDecimalCharacter() +
+            "23; 1" + getDecimalCharacter() +
+            "43; 1" + getDecimalCharacter() +
+            "63}";
+        String actual = vector3DFormat.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void testSimpleWithDecimalsTrunc() {
+        Vector3D c = new Vector3D(1.2323, 1.4343, 1.6333);
+        String expected =
+            "{1"    + getDecimalCharacter() +
+            "23; 1" + getDecimalCharacter() +
+            "43; 1" + getDecimalCharacter() +
+            "63}";
+        String actual = vector3DFormat.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void testNegativeX() {
+        Vector3D c = new Vector3D(-1.2323, 1.4343, 1.6333);
+        String expected =
+            "{-1"    + getDecimalCharacter() +
+            "23; 1" + getDecimalCharacter() +
+            "43; 1" + getDecimalCharacter() +
+            "63}";
+        String actual = vector3DFormat.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void testNegativeY() {
+        Vector3D c = new Vector3D(1.2323, -1.4343, 1.6333);
+        String expected =
+            "{1"    + getDecimalCharacter() +
+            "23; -1" + getDecimalCharacter() +
+            "43; 1" + getDecimalCharacter() +
+            "63}";
+        String actual = vector3DFormat.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void testNegativeZ() {
+        Vector3D c = new Vector3D(1.2323, 1.4343, -1.6333);
+        String expected =
+            "{1"    + getDecimalCharacter() +
+            "23; 1" + getDecimalCharacter() +
+            "43; -1" + getDecimalCharacter() +
+            "63}";
+        String actual = vector3DFormat.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void testNonDefaultSetting() {
+        Vector3D c = new Vector3D(1, 1, 1);
+        String expected = "[1 : 1 : 1]";
+        String actual = vector3DFormatSquare.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void testStaticFormatVector3D() {
+        Locale defaultLocal = Locale.getDefault();
+        Locale.setDefault(getLocale());
+
+        Vector3D c = new Vector3D(232.222, -342.33, 432.444);
+        String expected =
+            "{232"    + getDecimalCharacter() +
+            "22; -342" + getDecimalCharacter() +
+            "33; 432" + getDecimalCharacter() +
+            "44}";
+        String actual = Vector3DFormat.formatVector3D(c);
+        assertEquals(expected, actual);
+
+        Locale.setDefault(defaultLocal);
+    }
+
+    public void testNan() {
+        Vector3D c = Vector3D.NaN;
+        String expected = "{(NaN); (NaN); (NaN)}";
+        String actual = vector3DFormat.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void testPositiveInfinity() {
+        Vector3D c = Vector3D.POSITIVE_INFINITY;
+        String expected = "{(Infinity); (Infinity); (Infinity)}";
+        String actual = vector3DFormat.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void tesNegativeInfinity() {
+        Vector3D c = Vector3D.NEGATIVE_INFINITY;
+        String expected = "{(-Infinity); (-Infinity); (-Infinity)}";
+        String actual = vector3DFormat.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void testParseSimpleNoDecimals() {
+        String source = "{1; 1; 1}";
+        Vector3D expected = new Vector3D(1, 1, 1);
+        try {
+            Vector3D actual = (Vector3D) vector3DFormat.parseObject(source);
+            assertEquals(expected, actual);
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParseIgnoredWhitespace() {
+        Vector3D expected = new Vector3D(1, 1, 1);
+        ParsePosition pos1 = new ParsePosition(0);
+        String source1 = "{1;1;1}";
+        assertEquals(expected, vector3DFormat.parseObject(source1, pos1));
+        assertEquals(source1.length(), pos1.getIndex());
+        ParsePosition pos2 = new ParsePosition(0);
+        String source2 = " { 1 ; 1 ; 1 } ";
+        assertEquals(expected, vector3DFormat.parseObject(source2, pos2));
+        assertEquals(source2.length() - 1, pos2.getIndex());
+    }
+
+    public void testParseSimpleWithDecimals() {
+        String source =
+            "{1" + getDecimalCharacter() +
+            "23; 1" + getDecimalCharacter() +
+            "43; 1" + getDecimalCharacter() +
+            "63}";
+        Vector3D expected = new Vector3D(1.23, 1.43, 1.63);
+        try {
+            Vector3D actual = (Vector3D) vector3DFormat.parseObject(source);
+            assertEquals(expected, actual);
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParseSimpleWithDecimalsTrunc() {
+        String source =
+            "{1" + getDecimalCharacter() +
+            "2323; 1" + getDecimalCharacter() +
+            "4343; 1" + getDecimalCharacter() +
+            "6333}";
+        Vector3D expected = new Vector3D(1.2323, 1.4343, 1.6333);
+        try {
+            Vector3D actual = (Vector3D) vector3DFormat.parseObject(source);
+            assertEquals(expected, actual);
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParseNegativeX() {
+        String source =
+            "{-1" + getDecimalCharacter() +
+            "2323; 1" + getDecimalCharacter() +
+            "4343; 1" + getDecimalCharacter() +
+            "6333}";
+        Vector3D expected = new Vector3D(-1.2323, 1.4343, 1.6333);
+        try {
+            Vector3D actual = (Vector3D) vector3DFormat.parseObject(source);
+            assertEquals(expected, actual);
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParseNegativeY() {
+        String source =
+            "{1" + getDecimalCharacter() +
+            "2323; -1" + getDecimalCharacter() +
+            "4343; 1" + getDecimalCharacter() +
+            "6333}";
+        Vector3D expected = new Vector3D(1.2323, -1.4343, 1.6333);
+        try {
+            Vector3D actual = (Vector3D) vector3DFormat.parseObject(source);
+            assertEquals(expected, actual);
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParseNegativeZ() {
+        String source =
+            "{1" + getDecimalCharacter() +
+            "2323; 1" + getDecimalCharacter() +
+            "4343; -1" + getDecimalCharacter() +
+            "6333}";
+        Vector3D expected = new Vector3D(1.2323, 1.4343, -1.6333);
+        try {
+            Vector3D actual = (Vector3D) vector3DFormat.parseObject(source);
+            assertEquals(expected, actual);
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParseNegativeAll() {
+        String source =
+            "{-1" + getDecimalCharacter() +
+            "2323; -1" + getDecimalCharacter() +
+            "4343; -1" + getDecimalCharacter() +
+            "6333}";
+        Vector3D expected = new Vector3D(-1.2323, -1.4343, -1.6333);
+        try {
+            Vector3D actual = (Vector3D) vector3DFormat.parseObject(source);
+            assertEquals(expected, actual);
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParseZeroX() {
+        String source =
+            "{0" + getDecimalCharacter() +
+            "0; -1" + getDecimalCharacter() +
+            "4343; 1" + getDecimalCharacter() +
+            "6333}";
+        Vector3D expected = new Vector3D(0.0, -1.4343, 1.6333);
+        try {
+            Vector3D actual = (Vector3D) vector3DFormat.parseObject(source);
+            assertEquals(expected, actual);
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParseNonDefaultSetting() {
+        String source =
+            "[1" + getDecimalCharacter() +
+            "2323 : 1" + getDecimalCharacter() +
+            "4343 : 1" + getDecimalCharacter() +
+            "6333]";
+        Vector3D expected = new Vector3D(1.2323, 1.4343, 1.6333);
+        try {
+            Vector3D actual = (Vector3D) vector3DFormatSquare.parseObject(source);
+            assertEquals(expected, actual);
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParseNan() {
+        String source = "{(NaN); (NaN); (NaN)}";
+        try {
+            Vector3D actual = (Vector3D) vector3DFormat.parseObject(source);
+            assertEquals(Vector3D.NaN, actual);
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParsePositiveInfinity() {
+        String source = "{(Infinity); (Infinity); (Infinity)}";
+        try {
+            Vector3D actual = (Vector3D)vector3DFormat.parseObject(source);
+            assertEquals(Vector3D.POSITIVE_INFINITY, actual);
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParseNegativeInfinity() {
+        String source = "{(-Infinity); (-Infinity); (-Infinity)}";
+        try {
+            Vector3D actual = (Vector3D)vector3DFormat.parseObject(source);
+            assertEquals(Vector3D.NEGATIVE_INFINITY, actual);
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testConstructorSingleFormat() {
+        NumberFormat nf = NumberFormat.getInstance();
+        Vector3DFormat cf = new Vector3DFormat(nf);
+        assertNotNull(cf);
+        assertEquals(nf, cf.getFormat());
+    }
+
+    public void testFormatObject() {
+        try {
+            CompositeFormat cf = new Vector3DFormat();
+            Object object = new Object();
+            cf.format(object);
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // success
+        }
+    }
+
+    public void testForgottenPrefix() {
+        ParsePosition pos = new ParsePosition(0);
+        assertNull(new Vector3DFormat().parse("1; 1; 1}", pos));
+        assertEquals(0, pos.getErrorIndex());
+    }
+
+    public void testForgottenSeparator() {
+        ParsePosition pos = new ParsePosition(0);
+        assertNull(new Vector3DFormat().parse("{1; 1 1}", pos));
+        assertEquals(6, pos.getErrorIndex());
+    }
+
+    public void testForgottenSuffix() {
+        ParsePosition pos = new ParsePosition(0);
+        assertNull(new Vector3DFormat().parse("{1; 1; 1 ", pos));
+        assertEquals(8, pos.getErrorIndex());
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/geometry/Vector3DFormatTest.java b/src/test/java/org/apache/commons/math/geometry/Vector3DFormatTest.java
new file mode 100644
index 0000000..ca3f287
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/geometry/Vector3DFormatTest.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.geometry;
+
+import java.util.Locale;
+
+
+public class Vector3DFormatTest extends Vector3DFormatAbstractTest {
+
+    @Override
+    protected char getDecimalCharacter() {
+        return '.';
+    }
+
+    @Override
+    protected Locale getLocale() {
+        return Locale.US;
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/geometry/Vector3DTest.java b/src/test/java/org/apache/commons/math/geometry/Vector3DTest.java
new file mode 100644
index 0000000..286eae6
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/geometry/Vector3DTest.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.geometry;
+
+import org.apache.commons.math.geometry.Vector3D;
+import org.apache.commons.math.util.FastMath;
+
+import junit.framework.*;
+
+public class Vector3DTest
+  extends TestCase {
+
+  public Vector3DTest(String name) {
+    super(name);
+  }
+
+  public void testConstructors() {
+      double r = FastMath.sqrt(2) /2;
+      checkVector(new Vector3D(2, new Vector3D(FastMath.PI / 3, -FastMath.PI / 4)),
+                  r, r * FastMath.sqrt(3), -2 * r);
+      checkVector(new Vector3D(2, Vector3D.PLUS_I,
+                              -3, Vector3D.MINUS_K),
+                  2, 0, 3);
+      checkVector(new Vector3D(2, Vector3D.PLUS_I,
+                               5, Vector3D.PLUS_J,
+                              -3, Vector3D.MINUS_K),
+                  2, 5, 3);
+      checkVector(new Vector3D(2, Vector3D.PLUS_I,
+                               5, Vector3D.PLUS_J,
+                               5, Vector3D.MINUS_J,
+                               -3, Vector3D.MINUS_K),
+                  2, 0, 3);
+  }
+
+  public void testCoordinates() {
+    Vector3D v = new Vector3D(1, 2, 3);
+    assertTrue(FastMath.abs(v.getX() - 1) < 1.0e-12);
+    assertTrue(FastMath.abs(v.getY() - 2) < 1.0e-12);
+    assertTrue(FastMath.abs(v.getZ() - 3) < 1.0e-12);
+  }
+
+  public void testNorm1() {
+    assertEquals(0.0, Vector3D.ZERO.getNorm1());
+    assertEquals(6.0, new Vector3D(1, -2, 3).getNorm1(), 0);
+  }
+
+  public void testNorm() {
+      assertEquals(0.0, Vector3D.ZERO.getNorm());
+      assertEquals(FastMath.sqrt(14), new Vector3D(1, 2, 3).getNorm(), 1.0e-12);
+    }
+
+  public void testNormInf() {
+      assertEquals(0.0, Vector3D.ZERO.getNormInf());
+      assertEquals(3.0, new Vector3D(1, -2, 3).getNormInf(), 0);
+    }
+
+  public void testDistance1() {
+      Vector3D v1 = new Vector3D(1, -2, 3);
+      Vector3D v2 = new Vector3D(-4, 2, 0);
+      assertEquals(0.0, Vector3D.distance1(Vector3D.MINUS_I, Vector3D.MINUS_I), 0);
+      assertEquals(12.0, Vector3D.distance1(v1, v2), 1.0e-12);
+      assertEquals(v1.subtract(v2).getNorm1(), Vector3D.distance1(v1, v2), 1.0e-12);
+  }
+
+  public void testDistance() {
+      Vector3D v1 = new Vector3D(1, -2, 3);
+      Vector3D v2 = new Vector3D(-4, 2, 0);
+      assertEquals(0.0, Vector3D.distance(Vector3D.MINUS_I, Vector3D.MINUS_I), 0);
+      assertEquals(FastMath.sqrt(50), Vector3D.distance(v1, v2), 1.0e-12);
+      assertEquals(v1.subtract(v2).getNorm(), Vector3D.distance(v1, v2), 1.0e-12);
+  }
+
+  public void testDistanceSq() {
+      Vector3D v1 = new Vector3D(1, -2, 3);
+      Vector3D v2 = new Vector3D(-4, 2, 0);
+      assertEquals(0.0, Vector3D.distanceSq(Vector3D.MINUS_I, Vector3D.MINUS_I), 0);
+      assertEquals(50.0, Vector3D.distanceSq(v1, v2), 1.0e-12);
+      assertEquals(Vector3D.distance(v1, v2) * Vector3D.distance(v1, v2),
+                   Vector3D.distanceSq(v1, v2), 1.0e-12);
+  }
+
+  public void testDistanceInf() {
+      Vector3D v1 = new Vector3D(1, -2, 3);
+      Vector3D v2 = new Vector3D(-4, 2, 0);
+      assertEquals(0.0, Vector3D.distanceInf(Vector3D.MINUS_I, Vector3D.MINUS_I), 0);
+      assertEquals(5.0, Vector3D.distanceInf(v1, v2), 1.0e-12);
+      assertEquals(v1.subtract(v2).getNormInf(), Vector3D.distanceInf(v1, v2), 1.0e-12);
+  }
+
+  public void testSubtract() {
+
+    Vector3D v1 = new Vector3D(1, 2, 3);
+    Vector3D v2 = new Vector3D(-3, -2, -1);
+    v1 = v1.subtract(v2);
+    checkVector(v1, 4, 4, 4);
+
+    checkVector(v2.subtract(v1), -7, -6, -5);
+    checkVector(v2.subtract(3, v1), -15, -14, -13);
+
+  }
+
+  public void testAdd() {
+    Vector3D v1 = new Vector3D(1, 2, 3);
+    Vector3D v2 = new Vector3D(-3, -2, -1);
+    v1 = v1.add(v2);
+    checkVector(v1, -2, 0, 2);
+
+    checkVector(v2.add(v1), -5, -2, 1);
+    checkVector(v2.add(3, v1), -9, -2, 5);
+
+  }
+
+  public void testScalarProduct() {
+    Vector3D v = new Vector3D(1, 2, 3);
+    v = v.scalarMultiply(3);
+    checkVector(v, 3, 6, 9);
+
+    checkVector(v.scalarMultiply(0.5), 1.5, 3, 4.5);
+
+  }
+
+  public void testVectorialProducts() {
+    Vector3D v1 = new Vector3D(2, 1, -4);
+    Vector3D v2 = new Vector3D(3, 1, -1);
+
+    assertTrue(FastMath.abs(Vector3D.dotProduct(v1, v2) - 11) < 1.0e-12);
+
+    Vector3D v3 = Vector3D.crossProduct(v1, v2);
+    checkVector(v3, 3, -10, -1);
+
+    assertTrue(FastMath.abs(Vector3D.dotProduct(v1, v3)) < 1.0e-12);
+    assertTrue(FastMath.abs(Vector3D.dotProduct(v2, v3)) < 1.0e-12);
+
+  }
+
+  public void testAngular() {
+
+    assertEquals(0,           Vector3D.PLUS_I.getAlpha(), 1.0e-10);
+    assertEquals(0,           Vector3D.PLUS_I.getDelta(), 1.0e-10);
+    assertEquals(FastMath.PI / 2, Vector3D.PLUS_J.getAlpha(), 1.0e-10);
+    assertEquals(0,           Vector3D.PLUS_J.getDelta(), 1.0e-10);
+    assertEquals(0,           Vector3D.PLUS_K.getAlpha(), 1.0e-10);
+    assertEquals(FastMath.PI / 2, Vector3D.PLUS_K.getDelta(), 1.0e-10);
+
+    Vector3D u = new Vector3D(-1, 1, -1);
+    assertEquals(3 * FastMath.PI /4, u.getAlpha(), 1.0e-10);
+    assertEquals(-1.0 / FastMath.sqrt(3), FastMath.sin(u.getDelta()), 1.0e-10);
+
+  }
+
+  public void testAngularSeparation() {
+    Vector3D v1 = new Vector3D(2, -1, 4);
+
+    Vector3D  k = v1.normalize();
+    Vector3D  i = k.orthogonal();
+    Vector3D v2 = k.scalarMultiply(FastMath.cos(1.2)).add(i.scalarMultiply(FastMath.sin(1.2)));
+
+    assertTrue(FastMath.abs(Vector3D.angle(v1, v2) - 1.2) < 1.0e-12);
+
+  }
+
+  public void testNormalize() {
+    assertEquals(1.0, new Vector3D(5, -4, 2).normalize().getNorm(), 1.0e-12);
+    try {
+        Vector3D.ZERO.normalize();
+        fail("an exception should have been thrown");
+    } catch (ArithmeticException ae) {
+        // expected behavior
+    }
+  }
+
+  public void testOrthogonal() {
+      Vector3D v1 = new Vector3D(0.1, 2.5, 1.3);
+      assertEquals(0.0, Vector3D.dotProduct(v1, v1.orthogonal()), 1.0e-12);
+      Vector3D v2 = new Vector3D(2.3, -0.003, 7.6);
+      assertEquals(0.0, Vector3D.dotProduct(v2, v2.orthogonal()), 1.0e-12);
+      Vector3D v3 = new Vector3D(-1.7, 1.4, 0.2);
+      assertEquals(0.0, Vector3D.dotProduct(v3, v3.orthogonal()), 1.0e-12);
+      try {
+          new Vector3D(0, 0, 0).orthogonal();
+          fail("an exception should have been thrown");
+      } catch (ArithmeticException ae) {
+          // expected behavior
+      }
+  }
+
+  public void testAngle() {
+     assertEquals(0.22572612855273393616,
+                  Vector3D.angle(new Vector3D(1, 2, 3), new Vector3D(4, 5, 6)),
+                  1.0e-12);
+     assertEquals(7.98595620686106654517199e-8,
+                  Vector3D.angle(new Vector3D(1, 2, 3), new Vector3D(2, 4, 6.000001)),
+                  1.0e-12);
+     assertEquals(3.14159257373023116985197793156,
+                  Vector3D.angle(new Vector3D(1, 2, 3), new Vector3D(-2, -4, -6.000001)),
+                  1.0e-12);
+     try {
+         Vector3D.angle(Vector3D.ZERO, Vector3D.PLUS_I);
+         fail("an exception should have been thrown");
+     } catch (ArithmeticException ae) {
+         // expected behavior
+     }
+  }
+
+  private void checkVector(Vector3D v, double x, double y, double z) {
+      assertEquals(x, v.getX(), 1.0e-12);
+      assertEquals(y, v.getY(), 1.0e-12);
+      assertEquals(z, v.getZ(), 1.0e-12);
+  }
+
+}
diff --git a/src/test/java/org/apache/commons/math/linear/AbstractRealVectorTest.java b/src/test/java/org/apache/commons/math/linear/AbstractRealVectorTest.java
new file mode 100644
index 0000000..dfae89a
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/linear/AbstractRealVectorTest.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.linear;
+
+import junit.framework.TestCase;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.linear.RealVector.Entry;
+import org.apache.commons.math.util.FastMath;
+
+import java.util.Iterator;
+import java.util.Random;
+
+/**
+ *
+ */
+public class AbstractRealVectorTest extends TestCase {
+    private double[] vec1 = { 1d, 2d, 3d, 4d, 5d };
+    private double[] vec2 = { -3d, 0d, 0d, 2d, 1d };
+
+    private static class TestVectorImpl extends AbstractRealVector {
+        private double[] values;
+
+        TestVectorImpl(double[] values) {
+            this.values = values;
+        }
+
+        @Override
+        public double[] getData() { return values; }
+
+        @Override
+        public AbstractRealVector copy() {
+            return new TestVectorImpl(values.clone());
+        }
+
+
+        UnsupportedOperationException unsupported() {
+            return new UnsupportedOperationException("Test implementation only supports methods necessary for testing");
+        }
+
+        @Override
+        public RealVector add(RealVector v) throws IllegalArgumentException {
+            RealVector result = new ArrayRealVector(v);
+            return result.add(this);
+        }
+
+        @Override
+        public RealVector subtract(RealVector v) throws IllegalArgumentException {
+            RealVector result = new ArrayRealVector(v);
+            return result.subtract(this).mapMultiplyToSelf(-1);
+        }
+
+        @Override
+        public RealVector mapAddToSelf(double d) {
+            for(int i=0; i<values.length; i++) {
+                values[i] += d;
+            }
+            return this;
+        }
+
+        @Override
+        public RealVector mapSubtractToSelf(double d) {
+            for(int i=0; i<values.length; i++) {
+                values[i] -= d;
+            }
+            return this;
+        }
+
+        @Override
+        public RealVector mapMultiplyToSelf(double d) {
+            for(int i=0; i<values.length; i++) {
+                values[i] *= d;
+            }
+            return this;
+        }
+
+        @Override
+        public RealVector mapDivideToSelf(double d) {
+            for(int i=0; i<values.length; i++) {
+                values[i] /= d;
+            }
+            return this;
+        }
+
+        @Override
+        public RealVector mapPowToSelf(double d) {
+            for(int i=0; i<values.length; i++) {
+                values[i] = FastMath.pow(values[i], d);
+            }
+            return this;
+        }
+
+        @Override
+        public RealVector mapInvToSelf() {
+            for(int i=0; i<values.length; i++) {
+                values[i] = 1/values[i];
+            }
+            return this;
+        }
+
+        public RealVector ebeMultiply(RealVector v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        public RealVector ebeDivide(RealVector v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        @Override
+        public double dotProduct(RealVector v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        @Override
+        public double getNorm() {
+            throw unsupported();
+        }
+
+        @Override
+        public double getL1Norm() {
+            throw unsupported();
+        }
+
+        @Override
+        public double getLInfNorm() {
+            throw unsupported();
+        }
+
+        public RealVector projection(RealVector v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        public double getEntry(int index) throws MatrixIndexException {
+            return values[index];
+        }
+
+        public void setEntry(int index, double value) throws MatrixIndexException {
+            values[index] = value;
+        }
+
+        public int getDimension() {
+            return values.length;
+        }
+
+        public RealVector append(RealVector v) {
+            throw unsupported();
+        }
+
+        public RealVector append(double d) {
+            throw unsupported();
+        }
+
+        public RealVector append(double[] a) {
+            throw unsupported();
+        }
+
+        public RealVector getSubVector(int index, int n) throws MatrixIndexException {
+            throw unsupported();
+        }
+
+        public boolean isNaN() {
+            throw unsupported();
+        }
+
+        public boolean isInfinite() {
+            throw unsupported();
+        }
+    }
+
+    private static void assertEquals(double[] d1, double[] d2) {
+        assertEquals(d1.length, d2.length);
+        for(int i=0; i<d1.length; i++) assertEquals(d1[i], d2[i]);
+    }
+
+    public void testMap() throws Exception {
+        double[] vec1Squared = { 1d, 4d, 9d, 16d, 25d };
+        RealVector v = new TestVectorImpl(vec1.clone());
+        RealVector w = v.map(new UnivariateRealFunction() { public double value(double x) { return x * x; } });
+        assertEquals(vec1Squared, w.getData());
+    }
+
+    public void testIterator() throws Exception {
+        RealVector v = new TestVectorImpl(vec2.clone());
+        Entry e;
+        int i = 0;
+        for(Iterator<Entry> it = v.iterator(); it.hasNext() && (e = it.next()) != null; i++) {
+            assertEquals(vec2[i], e.getValue());
+        }
+    }
+
+    public void testSparseIterator() throws Exception {
+        RealVector v = new TestVectorImpl(vec2.clone());
+        Entry e;
+        int i = 0;
+        double[] nonDefaultV2 = { -3d, 2d, 1d };
+        for(Iterator<Entry> it = v.sparseIterator(); it.hasNext() && (e = it.next()) != null; i++) {
+            assertEquals(nonDefaultV2[i], e.getValue());
+        }
+        double [] onlyOne = {0d, 1.0, 0d};
+        v = new TestVectorImpl(onlyOne);
+        for(Iterator<Entry> it = v.sparseIterator(); it.hasNext() && (e = it.next()) != null; ) {
+            assertEquals(onlyOne[1], e.getValue());
+        }
+        
+    }
+
+    public void testClone() throws Exception {
+        double[] d = new double[1000000];
+        Random r = new Random(1234);
+        for(int i=0;i<d.length; i++) d[i] = r.nextDouble();
+        assertTrue(new ArrayRealVector(d).getNorm() > 0);
+        double[] c = d.clone();
+        c[0] = 1;
+        assertNotSame(c[0], d[0]);
+        d[0] = 1;
+        assertEquals(new ArrayRealVector(d).getNorm(), new ArrayRealVector(c).getNorm());
+        long cloneTime = 0;
+        long setAndAddTime = 0;
+        for(int i=0; i<10; i++) {
+          long start = System.nanoTime();
+          double[] v = d.clone();
+          for(int j=0; j<v.length; j++) v[j] += 1234.5678;
+          if(i > 4) cloneTime += System.nanoTime() - start;
+          start = System.nanoTime();
+          v = new double[d.length];
+          for(int j=0; j<v.length; j++) v[j] = d[j] + 1234.5678;
+          if(i > 4) setAndAddTime += System.nanoTime() - start;
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/linear/Array2DRowRealMatrixTest.java b/src/test/java/org/apache/commons/math/linear/Array2DRowRealMatrixTest.java
new file mode 100644
index 0000000..3c50a6e
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/linear/Array2DRowRealMatrixTest.java
@@ -0,0 +1,1008 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 junit.framework.TestCase;
+
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.linear.MatrixVisitorException;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Test cases for the {@link Array2DRowRealMatrix} class.
+ *
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ */
+
+public final class Array2DRowRealMatrixTest extends TestCase {
+
+    // 3 x 3 identity matrix
+    protected double[][] id = { {1d,0d,0d}, {0d,1d,0d}, {0d,0d,1d} };
+
+    // Test data for group operations
+    protected double[][] testData = { {1d,2d,3d}, {2d,5d,3d}, {1d,0d,8d} };
+    protected double[][] testDataLU = {{2d, 5d, 3d}, {.5d, -2.5d, 6.5d}, {0.5d, 0.2d, .2d}};
+    protected double[][] testDataPlus2 = { {3d,4d,5d}, {4d,7d,5d}, {3d,2d,10d} };
+    protected double[][] testDataMinus = { {-1d,-2d,-3d}, {-2d,-5d,-3d},
+       {-1d,0d,-8d} };
+    protected double[] testDataRow1 = {1d,2d,3d};
+    protected double[] testDataCol3 = {3d,3d,8d};
+    protected double[][] testDataInv =
+        { {-40d,16d,9d}, {13d,-5d,-3d}, {5d,-2d,-1d} };
+    protected double[] preMultTest = {8,12,33};
+    protected double[][] testData2 ={ {1d,2d,3d}, {2d,5d,3d}};
+    protected double[][] testData2T = { {1d,2d}, {2d,5d}, {3d,3d}};
+    protected double[][] testDataPlusInv =
+        { {-39d,18d,12d}, {15d,0d,0d}, {6d,-2d,7d} };
+
+    // lu decomposition tests
+    protected double[][] luData = { {2d,3d,3d}, {0d,5d,7d}, {6d,9d,8d} };
+    protected double[][] luDataLUDecomposition = { {6d,9d,8d}, {0d,5d,7d},
+            {0.33333333333333,0d,0.33333333333333} };
+
+    // singular matrices
+    protected double[][] singular = { {2d,3d}, {2d,3d} };
+    protected double[][] bigSingular = {{1d,2d,3d,4d}, {2d,5d,3d,4d},
+        {7d,3d,256d,1930d}, {3d,7d,6d,8d}}; // 4th row = 1st + 2nd
+    protected double[][] detData = { {1d,2d,3d}, {4d,5d,6d}, {7d,8d,10d} };
+    protected double[][] detData2 = { {1d, 3d}, {2d, 4d}};
+
+    // vectors
+    protected double[] testVector = {1,2,3};
+    protected double[] testVector2 = {1,2,3,4};
+
+    // submatrix accessor tests
+    protected double[][] subTestData = {{1, 2, 3, 4}, {1.5, 2.5, 3.5, 4.5},
+            {2, 4, 6, 8}, {4, 5, 6, 7}};
+    // array selections
+    protected double[][] subRows02Cols13 = { {2, 4}, {4, 8}};
+    protected double[][] subRows03Cols12 = { {2, 3}, {5, 6}};
+    protected double[][] subRows03Cols123 = { {2, 3, 4} , {5, 6, 7}};
+    // effective permutations
+    protected double[][] subRows20Cols123 = { {4, 6, 8} , {2, 3, 4}};
+    protected double[][] subRows31Cols31 = {{7, 5}, {4.5, 2.5}};
+    // contiguous ranges
+    protected double[][] subRows01Cols23 = {{3,4} , {3.5, 4.5}};
+    protected double[][] subRows23Cols00 = {{2} , {4}};
+    protected double[][] subRows00Cols33 = {{4}};
+    // row matrices
+    protected double[][] subRow0 = {{1,2,3,4}};
+    protected double[][] subRow3 = {{4,5,6,7}};
+    // column matrices
+    protected double[][] subColumn1 = {{2}, {2.5}, {4}, {5}};
+    protected double[][] subColumn3 = {{4}, {4.5}, {8}, {7}};
+
+    // tolerances
+    protected double entryTolerance = 10E-16;
+    protected double normTolerance = 10E-14;
+
+    public Array2DRowRealMatrixTest(String name) {
+        super(name);
+    }
+
+    /** test dimensions */
+    public void testDimensions() {
+        Array2DRowRealMatrix m = new Array2DRowRealMatrix(testData);
+        Array2DRowRealMatrix m2 = new Array2DRowRealMatrix(testData2);
+        assertEquals("testData row dimension",3,m.getRowDimension());
+        assertEquals("testData column dimension",3,m.getColumnDimension());
+        assertTrue("testData is square",m.isSquare());
+        assertEquals("testData2 row dimension",m2.getRowDimension(),2);
+        assertEquals("testData2 column dimension",m2.getColumnDimension(),3);
+        assertTrue("testData2 is not square",!m2.isSquare());
+    }
+
+    /** test copy functions */
+    public void testCopyFunctions() {
+        Array2DRowRealMatrix m1 = new Array2DRowRealMatrix(testData);
+        Array2DRowRealMatrix m2 = new Array2DRowRealMatrix(m1.getData());
+        assertEquals(m2,m1);
+        Array2DRowRealMatrix m3 = new Array2DRowRealMatrix(testData);
+        Array2DRowRealMatrix m4 = new Array2DRowRealMatrix(m3.getData(), false);
+        assertEquals(m4,m3);
+    }
+
+    /** test add */
+    public void testAdd() {
+        Array2DRowRealMatrix m = new Array2DRowRealMatrix(testData);
+        Array2DRowRealMatrix mInv = new Array2DRowRealMatrix(testDataInv);
+        RealMatrix mPlusMInv = m.add(mInv);
+        double[][] sumEntries = mPlusMInv.getData();
+        for (int row = 0; row < m.getRowDimension(); row++) {
+            for (int col = 0; col < m.getColumnDimension(); col++) {
+                assertEquals("sum entry entry",
+                    testDataPlusInv[row][col],sumEntries[row][col],
+                        entryTolerance);
+            }
+        }
+    }
+
+    /** test add failure */
+    public void testAddFail() {
+        Array2DRowRealMatrix m = new Array2DRowRealMatrix(testData);
+        Array2DRowRealMatrix m2 = new Array2DRowRealMatrix(testData2);
+        try {
+            m.add(m2);
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    /** test norm */
+    public void testNorm() {
+        Array2DRowRealMatrix m = new Array2DRowRealMatrix(testData);
+        Array2DRowRealMatrix m2 = new Array2DRowRealMatrix(testData2);
+        assertEquals("testData norm",14d,m.getNorm(),entryTolerance);
+        assertEquals("testData2 norm",7d,m2.getNorm(),entryTolerance);
+    }
+
+    /** test Frobenius norm */
+    public void testFrobeniusNorm() {
+        Array2DRowRealMatrix m = new Array2DRowRealMatrix(testData);
+        Array2DRowRealMatrix m2 = new Array2DRowRealMatrix(testData2);
+        assertEquals("testData Frobenius norm", FastMath.sqrt(117.0), m.getFrobeniusNorm(), entryTolerance);
+        assertEquals("testData2 Frobenius norm", FastMath.sqrt(52.0), m2.getFrobeniusNorm(), entryTolerance);
+    }
+
+     /** test m-n = m + -n */
+    public void testPlusMinus() {
+        Array2DRowRealMatrix m = new Array2DRowRealMatrix(testData);
+        Array2DRowRealMatrix m2 = new Array2DRowRealMatrix(testDataInv);
+        TestUtils.assertEquals("m-n = m + -n",m.subtract(m2),
+            m2.scalarMultiply(-1d).add(m),entryTolerance);
+        try {
+            m.subtract(new Array2DRowRealMatrix(testData2));
+            fail("Expecting illegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    /** test multiply */
+     public void testMultiply() {
+        Array2DRowRealMatrix m = new Array2DRowRealMatrix(testData);
+        Array2DRowRealMatrix mInv = new Array2DRowRealMatrix(testDataInv);
+        Array2DRowRealMatrix identity = new Array2DRowRealMatrix(id);
+        Array2DRowRealMatrix m2 = new Array2DRowRealMatrix(testData2);
+        TestUtils.assertEquals("inverse multiply",m.multiply(mInv),
+            identity,entryTolerance);
+        TestUtils.assertEquals("inverse multiply",mInv.multiply(m),
+            identity,entryTolerance);
+        TestUtils.assertEquals("identity multiply",m.multiply(identity),
+            m,entryTolerance);
+        TestUtils.assertEquals("identity multiply",identity.multiply(mInv),
+            mInv,entryTolerance);
+        TestUtils.assertEquals("identity multiply",m2.multiply(identity),
+            m2,entryTolerance);
+        try {
+            m.multiply(new Array2DRowRealMatrix(bigSingular));
+            fail("Expecting illegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    //Additional Test for Array2DRowRealMatrixTest.testMultiply
+
+    private double[][] d3 = new double[][] {{1,2,3,4},{5,6,7,8}};
+    private double[][] d4 = new double[][] {{1},{2},{3},{4}};
+    private double[][] d5 = new double[][] {{30},{70}};
+
+    public void testMultiply2() {
+       RealMatrix m3 = new Array2DRowRealMatrix(d3);
+       RealMatrix m4 = new Array2DRowRealMatrix(d4);
+       RealMatrix m5 = new Array2DRowRealMatrix(d5);
+       TestUtils.assertEquals("m3*m4=m5", m3.multiply(m4), m5, entryTolerance);
+   }
+
+    /** test trace */
+    public void testTrace() {
+        RealMatrix m = new Array2DRowRealMatrix(id);
+        assertEquals("identity trace",3d,m.getTrace(),entryTolerance);
+        m = new Array2DRowRealMatrix(testData2);
+        try {
+            m.getTrace();
+            fail("Expecting NonSquareMatrixException");
+        } catch (NonSquareMatrixException ex) {
+            // ignored
+        }
+    }
+
+    /** test sclarAdd */
+    public void testScalarAdd() {
+        RealMatrix m = new Array2DRowRealMatrix(testData);
+        TestUtils.assertEquals("scalar add",new Array2DRowRealMatrix(testDataPlus2),
+            m.scalarAdd(2d),entryTolerance);
+    }
+
+    /** test operate */
+    public void testOperate() {
+        RealMatrix m = new Array2DRowRealMatrix(id);
+        TestUtils.assertEquals("identity operate", testVector,
+                    m.operate(testVector), entryTolerance);
+        TestUtils.assertEquals("identity operate", testVector,
+                    m.operate(new ArrayRealVector(testVector)).getData(), entryTolerance);
+        m = new Array2DRowRealMatrix(bigSingular);
+        try {
+            m.operate(testVector);
+            fail("Expecting illegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    /** test issue MATH-209 */
+    public void testMath209() {
+        RealMatrix a = new Array2DRowRealMatrix(new double[][] {
+                { 1, 2 }, { 3, 4 }, { 5, 6 }
+        }, false);
+        double[] b = a.operate(new double[] { 1, 1 });
+        assertEquals(a.getRowDimension(), b.length);
+        assertEquals( 3.0, b[0], 1.0e-12);
+        assertEquals( 7.0, b[1], 1.0e-12);
+        assertEquals(11.0, b[2], 1.0e-12);
+    }
+
+    /** test transpose */
+    public void testTranspose() {
+        RealMatrix m = new Array2DRowRealMatrix(testData);
+        RealMatrix mIT = new LUDecompositionImpl(m).getSolver().getInverse().transpose();
+        RealMatrix mTI = new LUDecompositionImpl(m.transpose()).getSolver().getInverse();
+        TestUtils.assertEquals("inverse-transpose", mIT, mTI, normTolerance);
+        m = new Array2DRowRealMatrix(testData2);
+        RealMatrix mt = new Array2DRowRealMatrix(testData2T);
+        TestUtils.assertEquals("transpose",mt,m.transpose(),normTolerance);
+    }
+
+    /** test preMultiply by vector */
+    public void testPremultiplyVector() {
+        RealMatrix m = new Array2DRowRealMatrix(testData);
+        TestUtils.assertEquals("premultiply", m.preMultiply(testVector),
+                    preMultTest, normTolerance);
+        TestUtils.assertEquals("premultiply", m.preMultiply(new ArrayRealVector(testVector).getData()),
+                    preMultTest, normTolerance);
+        m = new Array2DRowRealMatrix(bigSingular);
+        try {
+            m.preMultiply(testVector);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    public void testPremultiply() {
+        RealMatrix m3 = new Array2DRowRealMatrix(d3);
+        RealMatrix m4 = new Array2DRowRealMatrix(d4);
+        RealMatrix m5 = new Array2DRowRealMatrix(d5);
+        TestUtils.assertEquals("m3*m4=m5", m4.preMultiply(m3), m5, entryTolerance);
+
+        Array2DRowRealMatrix m = new Array2DRowRealMatrix(testData);
+        Array2DRowRealMatrix mInv = new Array2DRowRealMatrix(testDataInv);
+        Array2DRowRealMatrix identity = new Array2DRowRealMatrix(id);
+        TestUtils.assertEquals("inverse multiply",m.preMultiply(mInv),
+                identity,entryTolerance);
+        TestUtils.assertEquals("inverse multiply",mInv.preMultiply(m),
+                identity,entryTolerance);
+        TestUtils.assertEquals("identity multiply",m.preMultiply(identity),
+                m,entryTolerance);
+        TestUtils.assertEquals("identity multiply",identity.preMultiply(mInv),
+                mInv,entryTolerance);
+        try {
+            m.preMultiply(new Array2DRowRealMatrix(bigSingular));
+            fail("Expecting illegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    public void testGetVectors() {
+        RealMatrix m = new Array2DRowRealMatrix(testData);
+        TestUtils.assertEquals("get row",m.getRow(0),testDataRow1,entryTolerance);
+        TestUtils.assertEquals("get col",m.getColumn(2),testDataCol3,entryTolerance);
+        try {
+            m.getRow(10);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // ignored
+        }
+        try {
+            m.getColumn(-1);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // ignored
+        }
+    }
+
+    public void testGetEntry() {
+        RealMatrix m = new Array2DRowRealMatrix(testData);
+        assertEquals("get entry",m.getEntry(0,1),2d,entryTolerance);
+        try {
+            m.getEntry(10, 4);
+            fail ("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    /** test examples in user guide */
+    public void testExamples() {
+        // Create a real matrix with two rows and three columns
+        double[][] matrixData = { {1d,2d,3d}, {2d,5d,3d}};
+        RealMatrix m = new Array2DRowRealMatrix(matrixData);
+        // One more with three rows, two columns
+        double[][] matrixData2 = { {1d,2d}, {2d,5d}, {1d, 7d}};
+        RealMatrix n = new Array2DRowRealMatrix(matrixData2);
+        // Now multiply m by n
+        RealMatrix p = m.multiply(n);
+        assertEquals(2, p.getRowDimension());
+        assertEquals(2, p.getColumnDimension());
+        // Invert p
+        RealMatrix pInverse = new LUDecompositionImpl(p).getSolver().getInverse();
+        assertEquals(2, pInverse.getRowDimension());
+        assertEquals(2, pInverse.getColumnDimension());
+
+        // Solve example
+        double[][] coefficientsData = {{2, 3, -2}, {-1, 7, 6}, {4, -3, -5}};
+        RealMatrix coefficients = new Array2DRowRealMatrix(coefficientsData);
+        double[] constants = {1, -2, 1};
+        double[] solution = new LUDecompositionImpl(coefficients).getSolver().solve(constants);
+        assertEquals(2 * solution[0] + 3 * solution[1] -2 * solution[2], constants[0], 1E-12);
+        assertEquals(-1 * solution[0] + 7 * solution[1] + 6 * solution[2], constants[1], 1E-12);
+        assertEquals(4 * solution[0] - 3 * solution[1] -5 * solution[2], constants[2], 1E-12);
+
+    }
+
+    // test submatrix accessors
+    public void testGetSubMatrix() {
+        RealMatrix m = new Array2DRowRealMatrix(subTestData);
+        checkGetSubMatrix(m, subRows23Cols00,  2 , 3 , 0, 0, false);
+        checkGetSubMatrix(m, subRows00Cols33,  0 , 0 , 3, 3, false);
+        checkGetSubMatrix(m, subRows01Cols23,  0 , 1 , 2, 3, false);
+        checkGetSubMatrix(m, subRows02Cols13,  new int[] { 0, 2 }, new int[] { 1, 3 },    false);
+        checkGetSubMatrix(m, subRows03Cols12,  new int[] { 0, 3 }, new int[] { 1, 2 },    false);
+        checkGetSubMatrix(m, subRows03Cols123, new int[] { 0, 3 }, new int[] { 1, 2, 3 }, false);
+        checkGetSubMatrix(m, subRows20Cols123, new int[] { 2, 0 }, new int[] { 1, 2, 3 }, false);
+        checkGetSubMatrix(m, subRows31Cols31,  new int[] { 3, 1 }, new int[] { 3, 1 },    false);
+        checkGetSubMatrix(m, subRows31Cols31,  new int[] { 3, 1 }, new int[] { 3, 1 },    false);
+        checkGetSubMatrix(m, null,  1, 0, 2, 4, true);
+        checkGetSubMatrix(m, null, -1, 1, 2, 2, true);
+        checkGetSubMatrix(m, null,  1, 0, 2, 2, true);
+        checkGetSubMatrix(m, null,  1, 0, 2, 4, true);
+        checkGetSubMatrix(m, null, new int[] {},    new int[] { 0 }, true);
+        checkGetSubMatrix(m, null, new int[] { 0 }, new int[] { 4 }, true);
+    }
+
+    private void checkGetSubMatrix(RealMatrix m, double[][] reference,
+                                   int startRow, int endRow, int startColumn, int endColumn,
+                                   boolean mustFail) {
+        try {
+            RealMatrix sub = m.getSubMatrix(startRow, endRow, startColumn, endColumn);
+            assertEquals(new Array2DRowRealMatrix(reference), sub);
+            if (mustFail) {
+                fail("Expecting MatrixIndexException");
+            }
+        } catch (MatrixIndexException e) {
+            if (!mustFail) {
+                throw e;
+            }
+        }
+    }
+
+    private void checkGetSubMatrix(RealMatrix m, double[][] reference,
+                                   int[] selectedRows, int[] selectedColumns,
+                                   boolean mustFail) {
+        try {
+            RealMatrix sub = m.getSubMatrix(selectedRows, selectedColumns);
+            assertEquals(new Array2DRowRealMatrix(reference), sub);
+            if (mustFail) {
+                fail("Expecting MatrixIndexException");
+            }
+        } catch (MatrixIndexException e) {
+            if (!mustFail) {
+                throw e;
+            }
+        }
+    }
+
+    public void testCopySubMatrix() {
+        RealMatrix m = new Array2DRowRealMatrix(subTestData);
+        checkCopy(m, subRows23Cols00,  2 , 3 , 0, 0, false);
+        checkCopy(m, subRows00Cols33,  0 , 0 , 3, 3, false);
+        checkCopy(m, subRows01Cols23,  0 , 1 , 2, 3, false);
+        checkCopy(m, subRows02Cols13,  new int[] { 0, 2 }, new int[] { 1, 3 },    false);
+        checkCopy(m, subRows03Cols12,  new int[] { 0, 3 }, new int[] { 1, 2 },    false);
+        checkCopy(m, subRows03Cols123, new int[] { 0, 3 }, new int[] { 1, 2, 3 }, false);
+        checkCopy(m, subRows20Cols123, new int[] { 2, 0 }, new int[] { 1, 2, 3 }, false);
+        checkCopy(m, subRows31Cols31,  new int[] { 3, 1 }, new int[] { 3, 1 },    false);
+        checkCopy(m, subRows31Cols31,  new int[] { 3, 1 }, new int[] { 3, 1 },    false);
+
+        checkCopy(m, null,  1, 0, 2, 4, true);
+        checkCopy(m, null, -1, 1, 2, 2, true);
+        checkCopy(m, null,  1, 0, 2, 2, true);
+        checkCopy(m, null,  1, 0, 2, 4, true);
+        checkCopy(m, null, new int[] {},    new int[] { 0 }, true);
+        checkCopy(m, null, new int[] { 0 }, new int[] { 4 }, true);
+    }
+
+    private void checkCopy(RealMatrix m, double[][] reference,
+                           int startRow, int endRow, int startColumn, int endColumn,
+                           boolean mustFail) {
+        try {
+            double[][] sub = (reference == null) ?
+                             new double[1][1] :
+                             new double[reference.length][reference[0].length];
+            m.copySubMatrix(startRow, endRow, startColumn, endColumn, sub);
+            assertEquals(new Array2DRowRealMatrix(reference), new Array2DRowRealMatrix(sub));
+            if (mustFail) {
+                fail("Expecting MatrixIndexException");
+            }
+        } catch (MatrixIndexException e) {
+            if (!mustFail) {
+                throw e;
+            }
+        }
+    }
+
+    private void checkCopy(RealMatrix m, double[][] reference,
+                           int[] selectedRows, int[] selectedColumns,
+                           boolean mustFail) {
+        try {
+            double[][] sub = (reference == null) ?
+                    new double[1][1] :
+                    new double[reference.length][reference[0].length];
+            m.copySubMatrix(selectedRows, selectedColumns, sub);
+            assertEquals(new Array2DRowRealMatrix(reference), new Array2DRowRealMatrix(sub));
+            if (mustFail) {
+                fail("Expecting MatrixIndexException");
+            }
+        } catch (MatrixIndexException e) {
+            if (!mustFail) {
+                throw e;
+            }
+        }
+    }
+
+    public void testGetRowMatrix() {
+        RealMatrix m = new Array2DRowRealMatrix(subTestData);
+        RealMatrix mRow0 = new Array2DRowRealMatrix(subRow0);
+        RealMatrix mRow3 = new Array2DRowRealMatrix(subRow3);
+        assertEquals("Row0", mRow0,
+                m.getRowMatrix(0));
+        assertEquals("Row3", mRow3,
+                m.getRowMatrix(3));
+        try {
+            m.getRowMatrix(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getRowMatrix(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testSetRowMatrix() {
+        RealMatrix m = new Array2DRowRealMatrix(subTestData);
+        RealMatrix mRow3 = new Array2DRowRealMatrix(subRow3);
+        assertNotSame(mRow3, m.getRowMatrix(0));
+        m.setRowMatrix(0, mRow3);
+        assertEquals(mRow3, m.getRowMatrix(0));
+        try {
+            m.setRowMatrix(-1, mRow3);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.setRowMatrix(0, m);
+            fail("Expecting InvalidMatrixException");
+        } catch (InvalidMatrixException ex) {
+            // expected
+        }
+    }
+
+    public void testGetColumnMatrix() {
+        RealMatrix m = new Array2DRowRealMatrix(subTestData);
+        RealMatrix mColumn1 = new Array2DRowRealMatrix(subColumn1);
+        RealMatrix mColumn3 = new Array2DRowRealMatrix(subColumn3);
+        assertEquals("Column1", mColumn1,
+                m.getColumnMatrix(1));
+        assertEquals("Column3", mColumn3,
+                m.getColumnMatrix(3));
+        try {
+            m.getColumnMatrix(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getColumnMatrix(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testSetColumnMatrix() {
+        RealMatrix m = new Array2DRowRealMatrix(subTestData);
+        RealMatrix mColumn3 = new Array2DRowRealMatrix(subColumn3);
+        assertNotSame(mColumn3, m.getColumnMatrix(1));
+        m.setColumnMatrix(1, mColumn3);
+        assertEquals(mColumn3, m.getColumnMatrix(1));
+        try {
+            m.setColumnMatrix(-1, mColumn3);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.setColumnMatrix(0, m);
+            fail("Expecting InvalidMatrixException");
+        } catch (InvalidMatrixException ex) {
+            // expected
+        }
+    }
+
+    public void testGetRowVector() {
+        RealMatrix m = new Array2DRowRealMatrix(subTestData);
+        RealVector mRow0 = new ArrayRealVector(subRow0[0]);
+        RealVector mRow3 = new ArrayRealVector(subRow3[0]);
+        assertEquals("Row0", mRow0, m.getRowVector(0));
+        assertEquals("Row3", mRow3, m.getRowVector(3));
+        try {
+            m.getRowVector(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getRowVector(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testSetRowVector() {
+        RealMatrix m = new Array2DRowRealMatrix(subTestData);
+        RealVector mRow3 = new ArrayRealVector(subRow3[0]);
+        assertNotSame(mRow3, m.getRowMatrix(0));
+        m.setRowVector(0, mRow3);
+        assertEquals(mRow3, m.getRowVector(0));
+        try {
+            m.setRowVector(-1, mRow3);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.setRowVector(0, new ArrayRealVector(5));
+            fail("Expecting InvalidMatrixException");
+        } catch (InvalidMatrixException ex) {
+            // expected
+        }
+    }
+
+    public void testGetColumnVector() {
+        RealMatrix m = new Array2DRowRealMatrix(subTestData);
+        RealVector mColumn1 = columnToVector(subColumn1);
+        RealVector mColumn3 = columnToVector(subColumn3);
+        assertEquals("Column1", mColumn1, m.getColumnVector(1));
+        assertEquals("Column3", mColumn3, m.getColumnVector(3));
+        try {
+            m.getColumnVector(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getColumnVector(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testSetColumnVector() {
+        RealMatrix m = new Array2DRowRealMatrix(subTestData);
+        RealVector mColumn3 = columnToVector(subColumn3);
+        assertNotSame(mColumn3, m.getColumnVector(1));
+        m.setColumnVector(1, mColumn3);
+        assertEquals(mColumn3, m.getColumnVector(1));
+        try {
+            m.setColumnVector(-1, mColumn3);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.setColumnVector(0, new ArrayRealVector(5));
+            fail("Expecting InvalidMatrixException");
+        } catch (InvalidMatrixException ex) {
+            // expected
+        }
+    }
+
+    private RealVector columnToVector(double[][] column) {
+        double[] data = new double[column.length];
+        for (int i = 0; i < data.length; ++i) {
+            data[i] = column[i][0];
+        }
+        return new ArrayRealVector(data, false);
+    }
+
+    public void testGetRow() {
+        RealMatrix m = new Array2DRowRealMatrix(subTestData);
+        checkArrays(subRow0[0], m.getRow(0));
+        checkArrays(subRow3[0], m.getRow(3));
+        try {
+            m.getRow(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getRow(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testSetRow() {
+        RealMatrix m = new Array2DRowRealMatrix(subTestData);
+        assertTrue(subRow3[0][0] != m.getRow(0)[0]);
+        m.setRow(0, subRow3[0]);
+        checkArrays(subRow3[0], m.getRow(0));
+        try {
+            m.setRow(-1, subRow3[0]);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.setRow(0, new double[5]);
+            fail("Expecting InvalidMatrixException");
+        } catch (InvalidMatrixException ex) {
+            // expected
+        }
+    }
+
+    public void testGetColumn() {
+        RealMatrix m = new Array2DRowRealMatrix(subTestData);
+        double[] mColumn1 = columnToArray(subColumn1);
+        double[] mColumn3 = columnToArray(subColumn3);
+        checkArrays(mColumn1, m.getColumn(1));
+        checkArrays(mColumn3, m.getColumn(3));
+        try {
+            m.getColumn(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getColumn(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testSetColumn() {
+        RealMatrix m = new Array2DRowRealMatrix(subTestData);
+        double[] mColumn3 = columnToArray(subColumn3);
+        assertTrue(mColumn3[0] != m.getColumn(1)[0]);
+        m.setColumn(1, mColumn3);
+        checkArrays(mColumn3, m.getColumn(1));
+        try {
+            m.setColumn(-1, mColumn3);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.setColumn(0, new double[5]);
+            fail("Expecting InvalidMatrixException");
+        } catch (InvalidMatrixException ex) {
+            // expected
+        }
+    }
+
+    private double[] columnToArray(double[][] column) {
+        double[] data = new double[column.length];
+        for (int i = 0; i < data.length; ++i) {
+            data[i] = column[i][0];
+        }
+        return data;
+    }
+
+    private void checkArrays(double[] expected, double[] actual) {
+        assertEquals(expected.length, actual.length);
+        for (int i = 0; i < expected.length; ++i) {
+            assertEquals(expected[i], actual[i]);
+        }
+    }
+
+    public void testEqualsAndHashCode() {
+        Array2DRowRealMatrix m = new Array2DRowRealMatrix(testData);
+        Array2DRowRealMatrix m1 = (Array2DRowRealMatrix) m.copy();
+        Array2DRowRealMatrix mt = (Array2DRowRealMatrix) m.transpose();
+        assertTrue(m.hashCode() != mt.hashCode());
+        assertEquals(m.hashCode(), m1.hashCode());
+        assertEquals(m, m);
+        assertEquals(m, m1);
+        assertFalse(m.equals(null));
+        assertFalse(m.equals(mt));
+        assertFalse(m.equals(new Array2DRowRealMatrix(bigSingular)));
+    }
+
+    public void testToString() {
+        Array2DRowRealMatrix m = new Array2DRowRealMatrix(testData);
+        assertEquals("Array2DRowRealMatrix{{1.0,2.0,3.0},{2.0,5.0,3.0},{1.0,0.0,8.0}}",
+                m.toString());
+        m = new Array2DRowRealMatrix();
+        assertEquals("Array2DRowRealMatrix{}",
+                m.toString());
+    }
+
+    public void testSetSubMatrix() throws Exception {
+        Array2DRowRealMatrix m = new Array2DRowRealMatrix(testData);
+        m.setSubMatrix(detData2,1,1);
+        RealMatrix expected = MatrixUtils.createRealMatrix
+            (new double[][] {{1.0,2.0,3.0},{2.0,1.0,3.0},{1.0,2.0,4.0}});
+        assertEquals(expected, m);
+
+        m.setSubMatrix(detData2,0,0);
+        expected = MatrixUtils.createRealMatrix
+            (new double[][] {{1.0,3.0,3.0},{2.0,4.0,3.0},{1.0,2.0,4.0}});
+        assertEquals(expected, m);
+
+        m.setSubMatrix(testDataPlus2,0,0);
+        expected = MatrixUtils.createRealMatrix
+            (new double[][] {{3.0,4.0,5.0},{4.0,7.0,5.0},{3.0,2.0,10.0}});
+        assertEquals(expected, m);
+
+        // dimension overflow
+        try {
+            m.setSubMatrix(testData,1,1);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException e) {
+            // expected
+        }
+        // dimension underflow
+        try {
+            m.setSubMatrix(testData,-1,1);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException e) {
+            // expected
+        }
+        try {
+            m.setSubMatrix(testData,1,-1);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException e) {
+            // expected
+        }
+
+        // null
+        try {
+            m.setSubMatrix(null,1,1);
+            fail("expecting NullPointerException");
+        } catch (NullPointerException e) {
+            // expected
+        }
+        Array2DRowRealMatrix m2 = new Array2DRowRealMatrix();
+        try {
+            m2.setSubMatrix(testData,0,1);
+            fail("expecting IllegalStateException");
+        } catch (IllegalStateException e) {
+            // expected
+        }
+        try {
+            m2.setSubMatrix(testData,1,0);
+            fail("expecting IllegalStateException");
+        } catch (IllegalStateException e) {
+            // expected
+        }
+
+        // ragged
+        try {
+            m.setSubMatrix(new double[][] {{1}, {2, 3}}, 0, 0);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        // empty
+        try {
+            m.setSubMatrix(new double[][] {{}}, 0, 0);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+    }
+
+    public void testWalk() throws MatrixVisitorException {
+        int rows    = 150;
+        int columns = 75;
+
+        RealMatrix m = new Array2DRowRealMatrix(rows, columns);
+        m.walkInRowOrder(new SetVisitor());
+        GetVisitor getVisitor = new GetVisitor();
+        m.walkInOptimizedOrder(getVisitor);
+        assertEquals(rows * columns, getVisitor.getCount());
+
+        m = new Array2DRowRealMatrix(rows, columns);
+        m.walkInRowOrder(new SetVisitor(), 1, rows - 2, 1, columns - 2);
+        getVisitor = new GetVisitor();
+        m.walkInOptimizedOrder(getVisitor, 1, rows - 2, 1, columns - 2);
+        assertEquals((rows - 2) * (columns - 2), getVisitor.getCount());
+        for (int i = 0; i < rows; ++i) {
+            assertEquals(0.0, m.getEntry(i, 0), 0);
+            assertEquals(0.0, m.getEntry(i, columns - 1), 0);
+        }
+        for (int j = 0; j < columns; ++j) {
+            assertEquals(0.0, m.getEntry(0, j), 0);
+            assertEquals(0.0, m.getEntry(rows - 1, j), 0);
+        }
+
+        m = new Array2DRowRealMatrix(rows, columns);
+        m.walkInColumnOrder(new SetVisitor());
+        getVisitor = new GetVisitor();
+        m.walkInOptimizedOrder(getVisitor);
+        assertEquals(rows * columns, getVisitor.getCount());
+
+        m = new Array2DRowRealMatrix(rows, columns);
+        m.walkInColumnOrder(new SetVisitor(), 1, rows - 2, 1, columns - 2);
+        getVisitor = new GetVisitor();
+        m.walkInOptimizedOrder(getVisitor, 1, rows - 2, 1, columns - 2);
+        assertEquals((rows - 2) * (columns - 2), getVisitor.getCount());
+        for (int i = 0; i < rows; ++i) {
+            assertEquals(0.0, m.getEntry(i, 0), 0);
+            assertEquals(0.0, m.getEntry(i, columns - 1), 0);
+        }
+        for (int j = 0; j < columns; ++j) {
+            assertEquals(0.0, m.getEntry(0, j), 0);
+            assertEquals(0.0, m.getEntry(rows - 1, j), 0);
+        }
+
+        m = new Array2DRowRealMatrix(rows, columns);
+        m.walkInOptimizedOrder(new SetVisitor());
+        getVisitor = new GetVisitor();
+        m.walkInRowOrder(getVisitor);
+        assertEquals(rows * columns, getVisitor.getCount());
+
+        m = new Array2DRowRealMatrix(rows, columns);
+        m.walkInOptimizedOrder(new SetVisitor(), 1, rows - 2, 1, columns - 2);
+        getVisitor = new GetVisitor();
+        m.walkInRowOrder(getVisitor, 1, rows - 2, 1, columns - 2);
+        assertEquals((rows - 2) * (columns - 2), getVisitor.getCount());
+        for (int i = 0; i < rows; ++i) {
+            assertEquals(0.0, m.getEntry(i, 0), 0);
+            assertEquals(0.0, m.getEntry(i, columns - 1), 0);
+        }
+        for (int j = 0; j < columns; ++j) {
+            assertEquals(0.0, m.getEntry(0, j), 0);
+            assertEquals(0.0, m.getEntry(rows - 1, j), 0);
+        }
+
+        m = new Array2DRowRealMatrix(rows, columns);
+        m.walkInOptimizedOrder(new SetVisitor());
+        getVisitor = new GetVisitor();
+        m.walkInColumnOrder(getVisitor);
+        assertEquals(rows * columns, getVisitor.getCount());
+
+        m = new Array2DRowRealMatrix(rows, columns);
+        m.walkInOptimizedOrder(new SetVisitor(), 1, rows - 2, 1, columns - 2);
+        getVisitor = new GetVisitor();
+        m.walkInColumnOrder(getVisitor, 1, rows - 2, 1, columns - 2);
+        assertEquals((rows - 2) * (columns - 2), getVisitor.getCount());
+        for (int i = 0; i < rows; ++i) {
+            assertEquals(0.0, m.getEntry(i, 0), 0);
+            assertEquals(0.0, m.getEntry(i, columns - 1), 0);
+        }
+        for (int j = 0; j < columns; ++j) {
+            assertEquals(0.0, m.getEntry(0, j), 0);
+            assertEquals(0.0, m.getEntry(rows - 1, j), 0);
+        }
+
+    }
+
+    public void testSerial()  {
+        Array2DRowRealMatrix m = new Array2DRowRealMatrix(testData);
+        assertEquals(m,TestUtils.serializeAndRecover(m));
+    }
+
+
+    private static class SetVisitor extends DefaultRealMatrixChangingVisitor {
+        @Override
+        public double visit(int i, int j, double value) {
+            return i + j / 1024.0;
+        }
+    }
+
+    private static class GetVisitor extends DefaultRealMatrixPreservingVisitor {
+        private int count = 0;
+        @Override
+        public void visit(int i, int j, double value) {
+            ++count;
+            assertEquals(i + j / 1024.0, value, 0.0);
+        }
+        public int getCount() {
+            return count;
+        }
+    }
+
+    //--------------- -----------------Protected methods
+
+    /** extracts the l  and u matrices from compact lu representation */
+    protected void splitLU(RealMatrix lu, double[][] lowerData, double[][] upperData) throws InvalidMatrixException {
+        if (!lu.isSquare() || lowerData.length != lowerData[0].length || upperData.length != upperData[0].length ||
+                lowerData.length != upperData.length
+                || lowerData.length != lu.getRowDimension()) {
+            throw new InvalidMatrixException("incorrect dimensions");
+        }
+        int n = lu.getRowDimension();
+        for (int i = 0; i < n; i++) {
+            for (int j = 0; j < n; j++) {
+                if (j < i) {
+                    lowerData[i][j] = lu.getEntry(i, j);
+                    upperData[i][j] = 0d;
+                } else if (i == j) {
+                    lowerData[i][j] = 1d;
+                    upperData[i][j] = lu.getEntry(i, j);
+                } else {
+                    lowerData[i][j] = 0d;
+                    upperData[i][j] = lu.getEntry(i, j);
+                }
+            }
+        }
+    }
+
+    /** Returns the result of applying the given row permutation to the matrix */
+    protected RealMatrix permuteRows(RealMatrix matrix, int[] permutation) {
+        if (!matrix.isSquare() || matrix.getRowDimension() != permutation.length) {
+            throw new IllegalArgumentException("dimension mismatch");
+        }
+        int n = matrix.getRowDimension();
+        int m = matrix.getColumnDimension();
+        double out[][] = new double[m][n];
+        for (int i = 0; i < n; i++) {
+            for (int j = 0; j < m; j++) {
+                out[i][j] = matrix.getEntry(permutation[i], j);
+            }
+        }
+        return new Array2DRowRealMatrix(out);
+    }
+
+//    /** Useful for debugging */
+//    private void dumpMatrix(RealMatrix m) {
+//          for (int i = 0; i < m.getRowDimension(); i++) {
+//              String os = "";
+//              for (int j = 0; j < m.getColumnDimension(); j++) {
+//                  os += m.getEntry(i, j) + " ";
+//              }
+//              System.out.println(os);
+//          }
+//    }
+
+}
+
diff --git a/src/test/java/org/apache/commons/math/linear/ArrayFieldVectorTest.java b/src/test/java/org/apache/commons/math/linear/ArrayFieldVectorTest.java
new file mode 100644
index 0000000..729dbb1
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/linear/ArrayFieldVectorTest.java
@@ -0,0 +1,632 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.linear;
+
+import java.io.Serializable;
+import java.lang.reflect.Array;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.math.Field;
+import org.apache.commons.math.FieldElement;
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.fraction.Fraction;
+import org.apache.commons.math.fraction.FractionField;
+
+/**
+ * Test cases for the {@link ArrayFieldVector} class.
+ *
+ * @version $Revision: 1003993 $ $Date: 2010-10-03 18:39:16 +0200 (dim. 03 oct. 2010) $
+ */
+public class ArrayFieldVectorTest extends TestCase {
+
+    //
+    protected Fraction[][] ma1 = {
+            {new Fraction(1), new Fraction(2), new Fraction(3)},
+            {new Fraction(4), new Fraction(5), new Fraction(6)},
+            {new Fraction(7), new Fraction(8), new Fraction(9)}
+    };
+    protected Fraction[] vec1 = {new Fraction(1), new Fraction(2), new Fraction(3)};
+    protected Fraction[] vec2 = {new Fraction(4), new Fraction(5), new Fraction(6)};
+    protected Fraction[] vec3 = {new Fraction(7), new Fraction(8), new Fraction(9)};
+    protected Fraction[] vec4 = { new Fraction(1), new Fraction(2), new Fraction(3),
+                                  new Fraction(4), new Fraction(5), new Fraction(6),
+                                  new Fraction(7), new Fraction(8), new Fraction(9)};
+    protected Fraction[] vec_null = {new Fraction(0), new Fraction(0), new Fraction(0)};
+    protected Fraction[] dvec1 = {new Fraction(1), new Fraction(2), new Fraction(3),
+                                  new Fraction(4), new Fraction(5), new Fraction(6),
+                                  new Fraction(7), new Fraction(8), new Fraction(9)};
+    protected Fraction[][] mat1 = {
+            {new Fraction(1), new Fraction(2), new Fraction(3)},
+            {new Fraction(4), new Fraction(5), new Fraction(6)},
+            {new Fraction(7), new Fraction(8), new Fraction(9)}
+    };
+
+    // Testclass to test the FieldVector<Fraction> interface
+    // only with enough content to support the test
+    public static class FieldVectorTestImpl<T extends FieldElement<T>>
+        implements FieldVector<T>, Serializable {
+
+        private static final long serialVersionUID = 3970959016014158539L;
+
+        private final Field<T> field;
+
+        /** Entries of the vector. */
+        protected T[] data;
+
+        /** 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);
+        }
+
+        public FieldVectorTestImpl(T[] d) {
+            field = d[0].getField();
+            data = d.clone();
+        }
+
+        public Field<T> getField() {
+            return field;
+        }
+
+        private UnsupportedOperationException unsupported() {
+            return new UnsupportedOperationException("Not supported, unneeded for test purposes");
+        }
+
+        public FieldVector<T> copy() {
+            throw unsupported();
+        }
+
+        public FieldVector<T> add(FieldVector<T> v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        public FieldVector<T> add(T[] v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        public FieldVector<T> subtract(FieldVector<T> v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        public FieldVector<T> subtract(T[] v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        public FieldVector<T> mapAdd(T d) {
+            throw unsupported();
+        }
+
+        public FieldVector<T> mapAddToSelf(T d) {
+            throw unsupported();
+        }
+
+        public FieldVector<T> mapSubtract(T d) {
+            throw unsupported();
+        }
+
+        public FieldVector<T> mapSubtractToSelf(T d) {
+            throw unsupported();
+        }
+
+        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 FieldVectorTestImpl<T>(out);
+        }
+
+        public FieldVector<T> mapMultiplyToSelf(T d) {
+            throw unsupported();
+        }
+
+        public FieldVector<T> mapDivide(T d) {
+            throw unsupported();
+        }
+
+        public FieldVector<T> mapDivideToSelf(T d) {
+            throw unsupported();
+        }
+
+        public FieldVector<T> mapInv() {
+            throw unsupported();
+        }
+
+        public FieldVector<T> mapInvToSelf() {
+            throw unsupported();
+        }
+
+        public FieldVector<T> ebeMultiply(FieldVector<T> v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        public FieldVector<T> ebeMultiply(T[] v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        public FieldVector<T> ebeDivide(FieldVector<T> v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        public FieldVector<T> ebeDivide(T[] v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        public T[] getData() {
+            return data.clone();
+        }
+
+        public T dotProduct(FieldVector<T> v) throws IllegalArgumentException {
+            T dot = field.getZero();
+            for (int i = 0; i < data.length; i++) {
+                dot = dot.add(data[i].multiply(v.getEntry(i)));
+            }
+            return dot;
+        }
+
+        public T dotProduct(T[] v) throws IllegalArgumentException {
+            T dot = field.getZero();
+            for (int i = 0; i < data.length; i++) {
+                dot = dot.add(data[i].multiply(v[i]));
+            }
+            return dot;
+        }
+
+        public FieldVector<T> projection(FieldVector<T> v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        public FieldVector<T> projection(T[] v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        public FieldMatrix<T> outerProduct(FieldVector<T> v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        public FieldMatrix<T> outerProduct(T[] v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        public T getEntry(int index) throws MatrixIndexException {
+            return data[index];
+        }
+
+        public int getDimension() {
+            return data.length;
+        }
+
+        public FieldVector<T> append(FieldVector<T> v) {
+            throw unsupported();
+        }
+
+        public FieldVector<T> append(T d) {
+            throw unsupported();
+        }
+
+        public FieldVector<T> append(T[] a) {
+            throw unsupported();
+        }
+
+        public FieldVector<T> getSubVector(int index, int n) throws MatrixIndexException {
+            throw unsupported();
+        }
+
+        public void setEntry(int index, T value) throws MatrixIndexException {
+            throw unsupported();
+        }
+
+        public void setSubVector(int index, FieldVector<T> v) throws MatrixIndexException {
+            throw unsupported();
+        }
+
+        public void setSubVector(int index, T[] v) throws MatrixIndexException {
+            throw unsupported();
+        }
+
+        public void set(T value) {
+            throw unsupported();
+        }
+
+        public T[] toArray() {
+            throw unsupported();
+        }
+
+    }
+
+    public void testConstructors() {
+
+        ArrayFieldVector<Fraction> v0 = new ArrayFieldVector<Fraction>(FractionField.getInstance());
+        assertEquals(0, v0.getDimension());
+
+        ArrayFieldVector<Fraction> v1 = new ArrayFieldVector<Fraction>(FractionField.getInstance(), 7);
+        assertEquals(7, v1.getDimension());
+        assertEquals(new Fraction(0), v1.getEntry(6));
+
+        ArrayFieldVector<Fraction> v2 = new ArrayFieldVector<Fraction>(5, new Fraction(123, 100));
+        assertEquals(5, v2.getDimension());
+        assertEquals(new Fraction(123, 100), v2.getEntry(4));
+
+        ArrayFieldVector<Fraction> v3 = new ArrayFieldVector<Fraction>(vec1);
+        assertEquals(3, v3.getDimension());
+        assertEquals(new Fraction(2), v3.getEntry(1));
+
+        ArrayFieldVector<Fraction> v4 = new ArrayFieldVector<Fraction>(vec4, 3, 2);
+        assertEquals(2, v4.getDimension());
+        assertEquals(new Fraction(4), v4.getEntry(0));
+        try {
+            new ArrayFieldVector<Fraction>(vec4, 8, 3);
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected behavior
+        }
+
+        FieldVector<Fraction> v5_i = new ArrayFieldVector<Fraction>(dvec1);
+        assertEquals(9, v5_i.getDimension());
+        assertEquals(new Fraction(9), v5_i.getEntry(8));
+
+        ArrayFieldVector<Fraction> v5 = new ArrayFieldVector<Fraction>(dvec1);
+        assertEquals(9, v5.getDimension());
+        assertEquals(new Fraction(9), v5.getEntry(8));
+
+        ArrayFieldVector<Fraction> v6 = new ArrayFieldVector<Fraction>(dvec1, 3, 2);
+        assertEquals(2, v6.getDimension());
+        assertEquals(new Fraction(4), v6.getEntry(0));
+        try {
+            new ArrayFieldVector<Fraction>(dvec1, 8, 3);
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected behavior
+        }
+
+        ArrayFieldVector<Fraction> v7 = new ArrayFieldVector<Fraction>(v1);
+        assertEquals(7, v7.getDimension());
+        assertEquals(new Fraction(0), v7.getEntry(6));
+
+        FieldVectorTestImpl<Fraction> v7_i = new FieldVectorTestImpl<Fraction>(vec1);
+
+        ArrayFieldVector<Fraction> v7_2 = new ArrayFieldVector<Fraction>(v7_i);
+        assertEquals(3, v7_2.getDimension());
+        assertEquals(new Fraction(2), v7_2.getEntry(1));
+
+        ArrayFieldVector<Fraction> v8 = new ArrayFieldVector<Fraction>(v1, true);
+        assertEquals(7, v8.getDimension());
+        assertEquals(new Fraction(0), v8.getEntry(6));
+        assertNotSame("testData not same object ", v1.data, v8.data);
+
+        ArrayFieldVector<Fraction> v8_2 = new ArrayFieldVector<Fraction>(v1, false);
+        assertEquals(7, v8_2.getDimension());
+        assertEquals(new Fraction(0), v8_2.getEntry(6));
+        assertEquals(v1.data, v8_2.data);
+
+        ArrayFieldVector<Fraction> v9 = new ArrayFieldVector<Fraction>(v1, v3);
+        assertEquals(10, v9.getDimension());
+        assertEquals(new Fraction(1), v9.getEntry(7));
+
+    }
+
+    public void testDataInOut() {
+
+        ArrayFieldVector<Fraction> v1 = new ArrayFieldVector<Fraction>(vec1);
+        ArrayFieldVector<Fraction> v2 = new ArrayFieldVector<Fraction>(vec2);
+        ArrayFieldVector<Fraction> v4 = new ArrayFieldVector<Fraction>(vec4);
+        FieldVectorTestImpl<Fraction> v2_t = new FieldVectorTestImpl<Fraction>(vec2);
+
+        FieldVector<Fraction> v_append_1 = v1.append(v2);
+        assertEquals(6, v_append_1.getDimension());
+        assertEquals(new Fraction(4), v_append_1.getEntry(3));
+
+        FieldVector<Fraction> v_append_2 = v1.append(new Fraction(2));
+        assertEquals(4, v_append_2.getDimension());
+        assertEquals(new Fraction(2), v_append_2.getEntry(3));
+
+        FieldVector<Fraction> v_append_3 = v1.append(vec2);
+        assertEquals(6, v_append_3.getDimension());
+        assertEquals(new Fraction(4), v_append_3.getEntry(3));
+
+        FieldVector<Fraction> v_append_4 = v1.append(v2_t);
+        assertEquals(6, v_append_4.getDimension());
+        assertEquals(new Fraction(4), v_append_4.getEntry(3));
+
+        FieldVector<Fraction> v_copy = v1.copy();
+        assertEquals(3, v_copy.getDimension());
+        assertNotSame("testData not same object ", v1.data, v_copy.getData());
+
+        Fraction[] a_frac = v1.toArray();
+        assertEquals(3, a_frac.length);
+        assertNotSame("testData not same object ", v1.data, a_frac);
+
+
+//      ArrayFieldVector<Fraction> vout4 = (ArrayFieldVector<Fraction>) v1.clone();
+//      assertEquals(3, vout4.getDimension());
+//      assertEquals(v1.data, vout4.data);
+
+
+        FieldVector<Fraction> vout5 = v4.getSubVector(3, 3);
+        assertEquals(3, vout5.getDimension());
+        assertEquals(new Fraction(5), vout5.getEntry(1));
+        try {
+            v4.getSubVector(3, 7);
+            fail("MatrixIndexException expected");
+        } catch (MatrixIndexException ex) {
+            // expected behavior
+        }
+
+        ArrayFieldVector<Fraction> v_set1 = (ArrayFieldVector<Fraction>) v1.copy();
+        v_set1.setEntry(1, new Fraction(11));
+        assertEquals(new Fraction(11), v_set1.getEntry(1));
+        try {
+            v_set1.setEntry(3, new Fraction(11));
+            fail("MatrixIndexException expected");
+        } catch (MatrixIndexException ex) {
+            // expected behavior
+        }
+
+        ArrayFieldVector<Fraction> v_set2 = (ArrayFieldVector<Fraction>) v4.copy();
+        v_set2.set(3, v1);
+        assertEquals(new Fraction(1), v_set2.getEntry(3));
+        assertEquals(new Fraction(7), v_set2.getEntry(6));
+        try {
+            v_set2.set(7, v1);
+            fail("MatrixIndexException expected");
+        } catch (MatrixIndexException ex) {
+            // expected behavior
+        }
+
+        ArrayFieldVector<Fraction> v_set3 = (ArrayFieldVector<Fraction>) v1.copy();
+        v_set3.set(new Fraction(13));
+        assertEquals(new Fraction(13), v_set3.getEntry(2));
+
+        try {
+            v_set3.getEntry(23);
+            fail("ArrayIndexOutOfBoundsException expected");
+        } catch (ArrayIndexOutOfBoundsException ex) {
+            // expected behavior
+        }
+
+        ArrayFieldVector<Fraction> v_set4 = (ArrayFieldVector<Fraction>) v4.copy();
+        v_set4.setSubVector(3, v2_t);
+        assertEquals(new Fraction(4), v_set4.getEntry(3));
+        assertEquals(new Fraction(7), v_set4.getEntry(6));
+        try {
+            v_set4.setSubVector(7, v2_t);
+            fail("MatrixIndexException expected");
+        } catch (MatrixIndexException ex) {
+            // expected behavior
+        }
+
+
+        ArrayFieldVector<Fraction> vout10 = (ArrayFieldVector<Fraction>) v1.copy();
+        ArrayFieldVector<Fraction> vout10_2 = (ArrayFieldVector<Fraction>) v1.copy();
+        assertEquals(vout10, vout10_2);
+        vout10_2.setEntry(0, new Fraction(11, 10));
+        assertNotSame(vout10, vout10_2);
+
+    }
+
+    public void testMapFunctions() {
+        ArrayFieldVector<Fraction> v1 = new ArrayFieldVector<Fraction>(vec1);
+
+        //octave =  v1 .+ 2.0
+        FieldVector<Fraction> v_mapAdd = v1.mapAdd(new Fraction(2));
+        Fraction[] result_mapAdd = {new Fraction(3), new Fraction(4), new Fraction(5)};
+        checkArray("compare vectors" ,result_mapAdd,v_mapAdd.getData());
+
+        //octave =  v1 .+ 2.0
+        FieldVector<Fraction> v_mapAddToSelf = v1.copy();
+        v_mapAddToSelf.mapAddToSelf(new Fraction(2));
+        Fraction[] result_mapAddToSelf = {new Fraction(3), new Fraction(4), new Fraction(5)};
+        checkArray("compare vectors" ,result_mapAddToSelf,v_mapAddToSelf.getData());
+
+        //octave =  v1 .- 2.0
+        FieldVector<Fraction> v_mapSubtract = v1.mapSubtract(new Fraction(2));
+        Fraction[] result_mapSubtract = {new Fraction(-1), new Fraction(0), new Fraction(1)};
+        checkArray("compare vectors" ,result_mapSubtract,v_mapSubtract.getData());
+
+        //octave =  v1 .- 2.0
+        FieldVector<Fraction> v_mapSubtractToSelf = v1.copy();
+        v_mapSubtractToSelf.mapSubtractToSelf(new Fraction(2));
+        Fraction[] result_mapSubtractToSelf = {new Fraction(-1), new Fraction(0), new Fraction(1)};
+        checkArray("compare vectors" ,result_mapSubtractToSelf,v_mapSubtractToSelf.getData());
+
+        //octave =  v1 .* 2.0
+        FieldVector<Fraction> v_mapMultiply = v1.mapMultiply(new Fraction(2));
+        Fraction[] result_mapMultiply = {new Fraction(2), new Fraction(4), new Fraction(6)};
+        checkArray("compare vectors" ,result_mapMultiply,v_mapMultiply.getData());
+
+        //octave =  v1 .* 2.0
+        FieldVector<Fraction> v_mapMultiplyToSelf = v1.copy();
+        v_mapMultiplyToSelf.mapMultiplyToSelf(new Fraction(2));
+        Fraction[] result_mapMultiplyToSelf = {new Fraction(2), new Fraction(4), new Fraction(6)};
+        checkArray("compare vectors" ,result_mapMultiplyToSelf,v_mapMultiplyToSelf.getData());
+
+        //octave =  v1 ./ 2.0
+        FieldVector<Fraction> v_mapDivide = v1.mapDivide(new Fraction(2));
+        Fraction[] result_mapDivide = {new Fraction(1, 2), new Fraction(1), new Fraction(3, 2)};
+        checkArray("compare vectors" ,result_mapDivide,v_mapDivide.getData());
+
+        //octave =  v1 ./ 2.0
+        FieldVector<Fraction> v_mapDivideToSelf = v1.copy();
+        v_mapDivideToSelf.mapDivideToSelf(new Fraction(2));
+        Fraction[] result_mapDivideToSelf = {new Fraction(1, 2), new Fraction(1), new Fraction(3, 2)};
+        checkArray("compare vectors" ,result_mapDivideToSelf,v_mapDivideToSelf.getData());
+
+        //octave =  v1 .^-1
+        FieldVector<Fraction> v_mapInv = v1.mapInv();
+        Fraction[] result_mapInv = {new Fraction(1),new Fraction(1, 2),new Fraction(1, 3)};
+        checkArray("compare vectors" ,result_mapInv,v_mapInv.getData());
+
+        //octave =  v1 .^-1
+        FieldVector<Fraction> v_mapInvToSelf = v1.copy();
+        v_mapInvToSelf.mapInvToSelf();
+        Fraction[] result_mapInvToSelf = {new Fraction(1),new Fraction(1, 2),new Fraction(1, 3)};
+        checkArray("compare vectors" ,result_mapInvToSelf,v_mapInvToSelf.getData());
+
+    }
+
+    public void testBasicFunctions() {
+        ArrayFieldVector<Fraction> v1 = new ArrayFieldVector<Fraction>(vec1);
+        ArrayFieldVector<Fraction> v2 = new ArrayFieldVector<Fraction>(vec2);
+        new ArrayFieldVector<Fraction>(vec_null);
+
+        FieldVectorTestImpl<Fraction> v2_t = new FieldVectorTestImpl<Fraction>(vec2);
+
+        //octave =  v1 + v2
+        ArrayFieldVector<Fraction> v_add = v1.add(v2);
+        Fraction[] result_add = {new Fraction(5), new Fraction(7), new Fraction(9)};
+        checkArray("compare vect" ,v_add.getData(),result_add);
+
+        FieldVectorTestImpl<Fraction> vt2 = new FieldVectorTestImpl<Fraction>(vec2);
+        FieldVector<Fraction> v_add_i = v1.add(vt2);
+        Fraction[] result_add_i = {new Fraction(5), new Fraction(7), new Fraction(9)};
+        checkArray("compare vect" ,v_add_i.getData(),result_add_i);
+
+        //octave =  v1 - v2
+        ArrayFieldVector<Fraction> v_subtract = v1.subtract(v2);
+        Fraction[] result_subtract = {new Fraction(-3), new Fraction(-3), new Fraction(-3)};
+        checkArray("compare vect" ,v_subtract.getData(),result_subtract);
+
+        FieldVector<Fraction> v_subtract_i = v1.subtract(vt2);
+        Fraction[] result_subtract_i = {new Fraction(-3), new Fraction(-3), new Fraction(-3)};
+        checkArray("compare vect" ,v_subtract_i.getData(),result_subtract_i);
+
+        // octave v1 .* v2
+        ArrayFieldVector<Fraction>  v_ebeMultiply = v1.ebeMultiply(v2);
+        Fraction[] result_ebeMultiply = {new Fraction(4), new Fraction(10), new Fraction(18)};
+        checkArray("compare vect" ,v_ebeMultiply.getData(),result_ebeMultiply);
+
+        FieldVector<Fraction>  v_ebeMultiply_2 = v1.ebeMultiply(v2_t);
+        Fraction[] result_ebeMultiply_2 = {new Fraction(4), new Fraction(10), new Fraction(18)};
+        checkArray("compare vect" ,v_ebeMultiply_2.getData(),result_ebeMultiply_2);
+
+        // octave v1 ./ v2
+        ArrayFieldVector<Fraction>  v_ebeDivide = v1.ebeDivide(v2);
+        Fraction[] result_ebeDivide = {new Fraction(1, 4), new Fraction(2, 5), new Fraction(1, 2)};
+        checkArray("compare vect" ,v_ebeDivide.getData(),result_ebeDivide);
+
+        FieldVector<Fraction>  v_ebeDivide_2 = v1.ebeDivide(v2_t);
+        Fraction[] result_ebeDivide_2 = {new Fraction(1, 4), new Fraction(2, 5), new Fraction(1, 2)};
+        checkArray("compare vect" ,v_ebeDivide_2.getData(),result_ebeDivide_2);
+
+        // octave  dot(v1,v2)
+        Fraction dot =  v1.dotProduct(v2);
+        assertEquals("compare val ",new Fraction(32), dot);
+
+        // octave  dot(v1,v2_t)
+        Fraction dot_2 =  v1.dotProduct(v2_t);
+        assertEquals("compare val ",new Fraction(32), dot_2);
+
+        FieldMatrix<Fraction> m_outerProduct = v1.outerProduct(v2);
+        assertEquals("compare val ",new Fraction(4), m_outerProduct.getEntry(0,0));
+
+        FieldMatrix<Fraction> m_outerProduct_2 = v1.outerProduct(v2_t);
+        assertEquals("compare val ",new Fraction(4), m_outerProduct_2.getEntry(0,0));
+
+        ArrayFieldVector<Fraction> v_projection = v1.projection(v2);
+        Fraction[] result_projection = {new Fraction(128, 77), new Fraction(160, 77), new Fraction(192, 77)};
+        checkArray("compare vect", v_projection.getData(), result_projection);
+
+        FieldVector<Fraction> v_projection_2 = v1.projection(v2_t);
+        Fraction[] result_projection_2 = {new Fraction(128, 77), new Fraction(160, 77), new Fraction(192, 77)};
+        checkArray("compare vect", v_projection_2.getData(), result_projection_2);
+
+    }
+
+    public void testMisc() {
+        ArrayFieldVector<Fraction> v1 = new ArrayFieldVector<Fraction>(vec1);
+        ArrayFieldVector<Fraction> v4 = new ArrayFieldVector<Fraction>(vec4);
+        FieldVector<Fraction> v4_2 = new ArrayFieldVector<Fraction>(vec4);
+
+        String out1 = v1.toString();
+        assertTrue("some output ",  out1.length()!=0);
+        /*
+         Fraction[] dout1 = v1.copyOut();
+        assertEquals(3, dout1.length);
+        assertNotSame("testData not same object ", v1.data, dout1);
+         */
+        try {
+            v1.checkVectorDimensions(2);
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected behavior
+        }
+
+       try {
+            v1.checkVectorDimensions(v4);
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected behavior
+        }
+
+        try {
+            v1.checkVectorDimensions(v4_2);
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected behavior
+        }
+
+    }
+
+    public void testSerial()  {
+        ArrayFieldVector<Fraction> v = new ArrayFieldVector<Fraction>(vec1);
+        assertEquals(v,TestUtils.serializeAndRecover(v));
+    }
+
+    public void testZeroVectors() {
+
+        // when the field is not specified, array cannot be empty
+        try {
+            new ArrayFieldVector<Fraction>(new Fraction[0]);
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected behavior
+        }
+        try {
+            new ArrayFieldVector<Fraction>(new Fraction[0], true);
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected behavior
+        }
+        try {
+            new ArrayFieldVector<Fraction>(new Fraction[0], false);
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected behavior
+        }
+
+        // when the field is specified, array can be empty
+        assertEquals(0, new ArrayFieldVector<Fraction>(FractionField.getInstance(), new Fraction[0]).getDimension());
+        assertEquals(0, new ArrayFieldVector<Fraction>(FractionField.getInstance(), new Fraction[0], true).getDimension());
+        assertEquals(0, new ArrayFieldVector<Fraction>(FractionField.getInstance(), new Fraction[0], false).getDimension());
+
+    }
+
+    /** verifies that two vectors are equals */
+    protected void checkArray(String msg, Fraction[] m, Fraction[] n) {
+        if (m.length != n.length) {
+            fail("vectors have different lengths");
+        }
+        for (int i = 0; i < m.length; i++) {
+            assertEquals(msg + " " +  i + " elements differ", m[i],n[i]);
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/linear/ArrayRealVectorTest.java b/src/test/java/org/apache/commons/math/linear/ArrayRealVectorTest.java
new file mode 100644
index 0000000..66b8160
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/linear/ArrayRealVectorTest.java
@@ -0,0 +1,1310 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.Iterator;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.linear.MatrixVisitorException;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Test cases for the {@link ArrayRealVector} class.
+ *
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ */
+public class ArrayRealVectorTest extends TestCase {
+
+    //
+    protected double[][] ma1 = {{1d, 2d, 3d}, {4d, 5d, 6d}, {7d, 8d, 9d}};
+    protected double[] vec1 = {1d, 2d, 3d};
+    protected double[] vec2 = {4d, 5d, 6d};
+    protected double[] vec3 = {7d, 8d, 9d};
+    protected double[] vec4 = {1d, 2d, 3d, 4d, 5d, 6d, 7d, 8d, 9d};
+    protected double[] vec5 = { -4d, 0d, 3d, 1d, -6d, 3d};
+    protected double[] vec_null = {0d, 0d, 0d};
+    protected Double[] dvec1 = {1d, 2d, 3d, 4d, 5d, 6d, 7d, 8d, 9d};
+    protected double[][] mat1 = {{1d, 2d, 3d}, {4d, 5d, 6d},{ 7d, 8d, 9d}};
+
+    // tolerances
+    protected double entryTolerance = 10E-16;
+    protected double normTolerance = 10E-14;
+
+    // Testclass to test the RealVector interface
+    // only with enough content to support the test
+    public static class RealVectorTestImpl implements RealVector, Serializable {
+
+        /** Serializable version identifier. */
+        private static final long serialVersionUID = 4715341047369582908L;
+
+        /** Entries of the vector. */
+        protected double data[];
+
+        public RealVectorTestImpl(double[] d) {
+            data = d.clone();
+        }
+
+        private MatrixVisitorException unsupported() {
+            return new MatrixVisitorException("Not supported, unneeded for test purposes", new Object[0]);
+        }
+
+        public RealVector map(UnivariateRealFunction function) throws MatrixVisitorException {
+            throw unsupported();
+        }
+
+        public RealVector mapToSelf(UnivariateRealFunction function) throws MatrixVisitorException {
+            throw unsupported();
+        }
+
+        public Iterator<Entry> iterator() {
+            return new Iterator<Entry>() {
+                int i = 0;
+                public boolean hasNext() {
+                    return i<data.length;
+                }
+                public Entry next() {
+                    final int j = i++;
+                    Entry e = new Entry() {
+                        @Override
+                        public double getValue() {
+                            return data[j];
+                        }
+                        @Override
+                        public void setValue(double newValue) {
+                            data[j] = newValue;
+                        }
+                    };
+                    e.setIndex(j);
+                    return e;
+                }
+                public void remove() { }
+            };
+        }
+
+        public Iterator<Entry> sparseIterator() {
+            return iterator();
+        }
+
+        public RealVector copy() {
+            throw unsupported();
+        }
+
+        public RealVector add(RealVector v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        public RealVector add(double[] v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        public RealVector subtract(RealVector v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        public RealVector subtract(double[] v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        public RealVector mapAdd(double d) {
+            throw unsupported();
+        }
+
+        public RealVector mapAddToSelf(double d) {
+            throw unsupported();
+        }
+
+        public RealVector mapSubtract(double d) {
+            throw unsupported();
+        }
+
+        public RealVector mapSubtractToSelf(double d) {
+            throw unsupported();
+        }
+
+        public RealVector mapMultiply(double d) {
+            double[] out = new double[data.length];
+            for (int i = 0; i < data.length; i++) {
+                out[i] = data[i] * d;
+            }
+            return new ArrayRealVector(out);
+        }
+
+        public RealVector mapMultiplyToSelf(double d) {
+            throw unsupported();
+        }
+
+        public RealVector mapDivide(double d) {
+            throw unsupported();
+        }
+
+        public RealVector mapDivideToSelf(double d) {
+            throw unsupported();
+        }
+
+        public RealVector mapPow(double d) {
+            throw unsupported();
+        }
+
+        public RealVector mapPowToSelf(double d) {
+            throw unsupported();
+        }
+
+        public RealVector mapExp() {
+            throw unsupported();
+        }
+
+        public RealVector mapExpToSelf() {
+            throw unsupported();
+        }
+
+        public RealVector mapExpm1() {
+            throw unsupported();
+        }
+
+        public RealVector mapExpm1ToSelf() {
+            throw unsupported();
+        }
+
+        public RealVector mapLog() {
+            throw unsupported();
+        }
+
+        public RealVector mapLogToSelf() {
+            throw unsupported();
+        }
+
+        public RealVector mapLog10() {
+            throw unsupported();
+        }
+
+        public RealVector mapLog10ToSelf() {
+            throw unsupported();
+        }
+
+        public RealVector mapLog1p() {
+            throw unsupported();
+        }
+
+        public RealVector mapLog1pToSelf() {
+            throw unsupported();
+        }
+
+        public RealVector mapCosh() {
+            throw unsupported();
+        }
+
+        public RealVector mapCoshToSelf() {
+            throw unsupported();
+        }
+
+        public RealVector mapSinh() {
+            throw unsupported();
+        }
+
+        public RealVector mapSinhToSelf() {
+            throw unsupported();
+        }
+
+        public RealVector mapTanh() {
+            throw unsupported();
+        }
+
+        public RealVector mapTanhToSelf() {
+            throw unsupported();
+        }
+
+        public RealVector mapCos() {
+            throw unsupported();
+        }
+
+        public RealVector mapCosToSelf() {
+            throw unsupported();
+        }
+
+        public RealVector mapSin() {
+            throw unsupported();
+        }
+
+        public RealVector mapSinToSelf() {
+            throw unsupported();
+        }
+
+        public RealVector mapTan() {
+            throw unsupported();
+        }
+
+        public RealVector mapTanToSelf() {
+            throw unsupported();
+        }
+
+        public RealVector mapAcos() {
+            throw unsupported();
+        }
+
+        public RealVector mapAcosToSelf() {
+            throw unsupported();
+        }
+
+        public RealVector mapAsin() {
+            throw unsupported();
+        }
+
+        public RealVector mapAsinToSelf() {
+            throw unsupported();
+        }
+
+        public RealVector mapAtan() {
+            throw unsupported();
+        }
+
+        public RealVector mapAtanToSelf() {
+            throw unsupported();
+        }
+
+        public RealVector mapInv() {
+            throw unsupported();
+        }
+
+        public RealVector mapInvToSelf() {
+            throw unsupported();
+        }
+
+        public RealVector mapAbs() {
+            throw unsupported();
+        }
+
+        public RealVector mapAbsToSelf() {
+            throw unsupported();
+        }
+
+        public RealVector mapSqrt() {
+            throw unsupported();
+        }
+
+        public RealVector mapSqrtToSelf() {
+            throw unsupported();
+        }
+
+        public RealVector mapCbrt() {
+            throw unsupported();
+        }
+
+        public RealVector mapCbrtToSelf() {
+            throw unsupported();
+        }
+
+        public RealVector mapCeil() {
+            throw unsupported();
+        }
+
+        public RealVector mapCeilToSelf() {
+            throw unsupported();
+        }
+
+        public RealVector mapFloor() {
+            throw unsupported();
+        }
+
+        public RealVector mapFloorToSelf() {
+            throw unsupported();
+        }
+
+        public RealVector mapRint() {
+            throw unsupported();
+        }
+
+        public RealVector mapRintToSelf() {
+            throw unsupported();
+        }
+
+        public RealVector mapSignum() {
+            throw unsupported();
+        }
+
+        public RealVector mapSignumToSelf() {
+            throw unsupported();
+        }
+
+        public RealVector mapUlp() {
+            throw unsupported();
+        }
+
+        public RealVector mapUlpToSelf() {
+            throw unsupported();
+        }
+
+        public RealVector ebeMultiply(RealVector v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        public RealVector ebeMultiply(double[] v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        public RealVector ebeDivide(RealVector v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        public RealVector ebeDivide(double[] v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        public double[] getData() {
+            return data.clone();
+        }
+
+        public double dotProduct(RealVector v) throws IllegalArgumentException {
+            double dot = 0;
+            for (int i = 0; i < data.length; i++) {
+                dot += data[i] * v.getEntry(i);
+            }
+            return dot;
+        }
+
+        public double dotProduct(double[] v) throws IllegalArgumentException {
+            double dot = 0;
+            for (int i = 0; i < data.length; i++) {
+                dot += data[i] * v[i];
+            }
+            return dot;
+        }
+
+        public double getNorm() {
+            throw unsupported();
+        }
+
+        public double getL1Norm() {
+            throw unsupported();
+        }
+
+        public double getLInfNorm() {
+            throw unsupported();
+        }
+
+        public double getDistance(RealVector v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        public double getDistance(double[] v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        public double getL1Distance(RealVector v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        public double getL1Distance(double[] v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        public double getLInfDistance(RealVector v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        public double getLInfDistance(double[] v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        public RealVector unitVector() {
+            throw unsupported();
+        }
+
+        public void unitize() {
+            throw unsupported();
+        }
+
+        public RealVector projection(RealVector v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        public RealVector projection(double[] v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        public RealMatrix outerProduct(RealVector v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        public RealMatrix outerProduct(double[] v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        public double getEntry(int index) throws MatrixIndexException {
+            return data[index];
+        }
+
+        public int getDimension() {
+            return data.length;
+        }
+
+        public RealVector append(RealVector v) {
+            throw unsupported();
+        }
+
+        public RealVector append(double d) {
+            throw unsupported();
+        }
+
+        public RealVector append(double[] a) {
+            throw unsupported();
+        }
+
+        public RealVector getSubVector(int index, int n) throws MatrixIndexException {
+            throw unsupported();
+        }
+
+        public void setEntry(int index, double value) throws MatrixIndexException {
+            throw unsupported();
+        }
+
+        public void setSubVector(int index, RealVector v) throws MatrixIndexException {
+            throw unsupported();
+        }
+
+        public void setSubVector(int index, double[] v) throws MatrixIndexException {
+            throw unsupported();
+        }
+
+        public void set(double value) {
+            throw unsupported();
+        }
+
+        public double[] toArray() {
+            throw unsupported();
+        }
+
+        public boolean isNaN() {
+            throw unsupported();
+        }
+
+        public boolean isInfinite() {
+            throw unsupported();
+        }
+
+    }
+
+    public void testConstructors() {
+
+        ArrayRealVector v0 = new ArrayRealVector();
+        assertEquals("testData len", 0, v0.getDimension());
+
+        ArrayRealVector v1 = new ArrayRealVector(7);
+        assertEquals("testData len", 7, v1.getDimension());
+        assertEquals("testData is 0.0 ", 0.0, v1.getEntry(6));
+
+        ArrayRealVector v2 = new ArrayRealVector(5, 1.23);
+        assertEquals("testData len", 5, v2.getDimension());
+        assertEquals("testData is 1.23 ", 1.23, v2.getEntry(4));
+
+        ArrayRealVector v3 = new ArrayRealVector(vec1);
+        assertEquals("testData len", 3, v3.getDimension());
+        assertEquals("testData is 2.0 ", 2.0, v3.getEntry(1));
+
+        ArrayRealVector v3_bis = new ArrayRealVector(vec1, true);
+        assertEquals("testData len", 3, v3_bis.getDimension());
+        assertEquals("testData is 2.0 ", 2.0, v3_bis.getEntry(1));
+        assertNotSame(v3_bis.getDataRef(), vec1);
+        assertNotSame(v3_bis.getData(), vec1);
+
+        ArrayRealVector v3_ter = new ArrayRealVector(vec1, false);
+        assertEquals("testData len", 3, v3_ter.getDimension());
+        assertEquals("testData is 2.0 ", 2.0, v3_ter.getEntry(1));
+        assertSame(v3_ter.getDataRef(), vec1);
+        assertNotSame(v3_ter.getData(), vec1);
+
+        ArrayRealVector v4 = new ArrayRealVector(vec4, 3, 2);
+        assertEquals("testData len", 2, v4.getDimension());
+        assertEquals("testData is 4.0 ", 4.0, v4.getEntry(0));
+        try {
+            new ArrayRealVector(vec4, 8, 3);
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected behavior
+        }
+
+        RealVector v5_i = new ArrayRealVector(dvec1);
+        assertEquals("testData len", 9, v5_i.getDimension());
+        assertEquals("testData is 9.0 ", 9.0, v5_i.getEntry(8));
+
+        ArrayRealVector v5 = new ArrayRealVector(dvec1);
+        assertEquals("testData len", 9, v5.getDimension());
+        assertEquals("testData is 9.0 ", 9.0, v5.getEntry(8));
+
+        ArrayRealVector v6 = new ArrayRealVector(dvec1, 3, 2);
+        assertEquals("testData len", 2, v6.getDimension());
+        assertEquals("testData is 4.0 ", 4.0, v6.getEntry(0));
+        try {
+            new ArrayRealVector(dvec1, 8, 3);
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected behavior
+        }
+
+        ArrayRealVector v7 = new ArrayRealVector(v1);
+        assertEquals("testData len", 7, v7.getDimension());
+        assertEquals("testData is 0.0 ", 0.0, v7.getEntry(6));
+
+        RealVectorTestImpl v7_i = new RealVectorTestImpl(vec1);
+
+        ArrayRealVector v7_2 = new ArrayRealVector(v7_i);
+        assertEquals("testData len", 3, v7_2.getDimension());
+        assertEquals("testData is 0.0 ", 2.0d, v7_2.getEntry(1));
+
+        ArrayRealVector v8 = new ArrayRealVector(v1, true);
+        assertEquals("testData len", 7, v8.getDimension());
+        assertEquals("testData is 0.0 ", 0.0, v8.getEntry(6));
+        assertNotSame("testData not same object ", v1.data, v8.data);
+
+        ArrayRealVector v8_2 = new ArrayRealVector(v1, false);
+        assertEquals("testData len", 7, v8_2.getDimension());
+        assertEquals("testData is 0.0 ", 0.0, v8_2.getEntry(6));
+        assertEquals("testData same object ", v1.data, v8_2.data);
+
+        ArrayRealVector v9 = new ArrayRealVector(v1, v3);
+        assertEquals("testData len", 10, v9.getDimension());
+        assertEquals("testData is 1.0 ", 1.0, v9.getEntry(7));
+
+        ArrayRealVector v10 = new ArrayRealVector(v2, new RealVectorTestImpl(vec3));
+        assertEquals("testData len", 8, v10.getDimension());
+        assertEquals("testData is 1.23 ", 1.23, v10.getEntry(4));
+        assertEquals("testData is 7.0 ", 7.0, v10.getEntry(5));
+
+        ArrayRealVector v11 = new ArrayRealVector(new RealVectorTestImpl(vec3), v2);
+        assertEquals("testData len", 8, v11.getDimension());
+        assertEquals("testData is 9.0 ", 9.0, v11.getEntry(2));
+        assertEquals("testData is 1.23 ", 1.23, v11.getEntry(3));
+
+        ArrayRealVector v12 = new ArrayRealVector(v2, vec3);
+        assertEquals("testData len", 8, v12.getDimension());
+        assertEquals("testData is 1.23 ", 1.23, v12.getEntry(4));
+        assertEquals("testData is 7.0 ", 7.0, v12.getEntry(5));
+
+        ArrayRealVector v13 = new ArrayRealVector(vec3, v2);
+        assertEquals("testData len", 8, v13.getDimension());
+        assertEquals("testData is 9.0 ", 9.0, v13.getEntry(2));
+        assertEquals("testData is 1.23 ", 1.23, v13.getEntry(3));
+
+        ArrayRealVector v14 = new ArrayRealVector(vec3, vec4);
+        assertEquals("testData len", 12, v14.getDimension());
+        assertEquals("testData is 9.0 ", 9.0, v14.getEntry(2));
+        assertEquals("testData is 1.0 ", 1.0, v14.getEntry(3));
+
+   }
+
+    public void testDataInOut() {
+
+        ArrayRealVector v1 = new ArrayRealVector(vec1);
+        ArrayRealVector v2 = new ArrayRealVector(vec2);
+        ArrayRealVector v4 = new ArrayRealVector(vec4);
+        RealVectorTestImpl v2_t = new RealVectorTestImpl(vec2);
+
+        RealVector v_append_1 = v1.append(v2);
+        assertEquals("testData len", 6, v_append_1.getDimension());
+        assertEquals("testData is 4.0 ", 4.0, v_append_1.getEntry(3));
+
+        RealVector v_append_2 = v1.append(2.0);
+        assertEquals("testData len", 4, v_append_2.getDimension());
+        assertEquals("testData is 2.0 ", 2.0, v_append_2.getEntry(3));
+
+        RealVector v_append_3 = v1.append(vec2);
+        assertEquals("testData len", 6, v_append_3.getDimension());
+        assertEquals("testData is  ", 4.0, v_append_3.getEntry(3));
+
+        RealVector v_append_4 = v1.append(v2_t);
+        assertEquals("testData len", 6, v_append_4.getDimension());
+        assertEquals("testData is 4.0 ", 4.0, v_append_4.getEntry(3));
+
+        RealVector v_append_5 = v1.append((RealVector) v2);
+        assertEquals("testData len", 6, v_append_5.getDimension());
+        assertEquals("testData is 4.0 ", 4.0, v_append_5.getEntry(3));
+
+        RealVector v_copy = v1.copy();
+        assertEquals("testData len", 3, v_copy.getDimension());
+        assertNotSame("testData not same object ", v1.data, v_copy.getData());
+
+        double[] a_double = v1.toArray();
+        assertEquals("testData len", 3, a_double.length);
+        assertNotSame("testData not same object ", v1.data, a_double);
+
+
+//      ArrayRealVector vout4 = (ArrayRealVector) v1.clone();
+//      assertEquals("testData len", 3, vout4.getDimension());
+//      assertEquals("testData not same object ", v1.data, vout4.data);
+
+
+        RealVector vout5 = v4.getSubVector(3, 3);
+        assertEquals("testData len", 3, vout5.getDimension());
+        assertEquals("testData is 4.0 ", 5.0, vout5.getEntry(1));
+        try {
+            v4.getSubVector(3, 7);
+            fail("MatrixIndexException expected");
+        } catch (MatrixIndexException ex) {
+            // expected behavior
+        }
+
+        ArrayRealVector v_set1 = (ArrayRealVector) v1.copy();
+        v_set1.setEntry(1, 11.0);
+        assertEquals("testData is 11.0 ", 11.0, v_set1.getEntry(1));
+        try {
+            v_set1.setEntry(3, 11.0);
+            fail("MatrixIndexException expected");
+        } catch (MatrixIndexException ex) {
+            // expected behavior
+        }
+
+        ArrayRealVector v_set2 = (ArrayRealVector) v4.copy();
+        v_set2.set(3, v1);
+        assertEquals("testData is 1.0 ", 1.0, v_set2.getEntry(3));
+        assertEquals("testData is 7.0 ", 7.0, v_set2.getEntry(6));
+        try {
+            v_set2.set(7, v1);
+            fail("MatrixIndexException expected");
+        } catch (MatrixIndexException ex) {
+            // expected behavior
+        }
+
+        ArrayRealVector v_set3 = (ArrayRealVector) v1.copy();
+        v_set3.set(13.0);
+        assertEquals("testData is 13.0 ", 13.0, v_set3.getEntry(2));
+
+        try {
+            v_set3.getEntry(23);
+            fail("ArrayIndexOutOfBoundsException expected");
+        } catch (ArrayIndexOutOfBoundsException ex) {
+            // expected behavior
+        }
+
+        ArrayRealVector v_set4 = (ArrayRealVector) v4.copy();
+        v_set4.setSubVector(3, v2_t);
+        assertEquals("testData is 1.0 ", 4.0, v_set4.getEntry(3));
+        assertEquals("testData is 7.0 ", 7.0, v_set4.getEntry(6));
+        try {
+            v_set4.setSubVector(7, v2_t);
+            fail("MatrixIndexException expected");
+        } catch (MatrixIndexException ex) {
+            // expected behavior
+        }
+
+
+        ArrayRealVector vout10 = (ArrayRealVector) v1.copy();
+        ArrayRealVector vout10_2 = (ArrayRealVector) v1.copy();
+        assertEquals(vout10, vout10_2);
+        vout10_2.setEntry(0, 1.1);
+        assertNotSame(vout10, vout10_2);
+
+    }
+
+    public void testMapFunctions() {
+        ArrayRealVector v1 = new ArrayRealVector(vec1);
+
+        //octave =  v1 .+ 2.0
+        RealVector v_mapAdd = v1.mapAdd(2.0d);
+        double[] result_mapAdd = {3d, 4d, 5d};
+        assertClose("compare vectors" ,result_mapAdd,v_mapAdd.getData(),normTolerance);
+
+        //octave =  v1 .+ 2.0
+        RealVector v_mapAddToSelf = v1.copy();
+        v_mapAddToSelf.mapAddToSelf(2.0d);
+        double[] result_mapAddToSelf = {3d, 4d, 5d};
+        assertClose("compare vectors" ,result_mapAddToSelf,v_mapAddToSelf.getData(),normTolerance);
+
+        //octave =  v1 .- 2.0
+        RealVector v_mapSubtract = v1.mapSubtract(2.0d);
+        double[] result_mapSubtract = {-1d, 0d, 1d};
+        assertClose("compare vectors" ,result_mapSubtract,v_mapSubtract.getData(),normTolerance);
+
+        //octave =  v1 .- 2.0
+        RealVector v_mapSubtractToSelf = v1.copy();
+        v_mapSubtractToSelf.mapSubtractToSelf(2.0d);
+        double[] result_mapSubtractToSelf = {-1d, 0d, 1d};
+        assertClose("compare vectors" ,result_mapSubtractToSelf,v_mapSubtractToSelf.getData(),normTolerance);
+
+        //octave =  v1 .* 2.0
+        RealVector v_mapMultiply = v1.mapMultiply(2.0d);
+        double[] result_mapMultiply = {2d, 4d, 6d};
+        assertClose("compare vectors" ,result_mapMultiply,v_mapMultiply.getData(),normTolerance);
+
+        //octave =  v1 .* 2.0
+        RealVector v_mapMultiplyToSelf = v1.copy();
+        v_mapMultiplyToSelf.mapMultiplyToSelf(2.0d);
+        double[] result_mapMultiplyToSelf = {2d, 4d, 6d};
+        assertClose("compare vectors" ,result_mapMultiplyToSelf,v_mapMultiplyToSelf.getData(),normTolerance);
+
+        //octave =  v1 ./ 2.0
+        RealVector v_mapDivide = v1.mapDivide(2.0d);
+        double[] result_mapDivide = {.5d, 1d, 1.5d};
+        assertClose("compare vectors" ,result_mapDivide,v_mapDivide.getData(),normTolerance);
+
+        //octave =  v1 ./ 2.0
+        RealVector v_mapDivideToSelf = v1.copy();
+        v_mapDivideToSelf.mapDivideToSelf(2.0d);
+        double[] result_mapDivideToSelf = {.5d, 1d, 1.5d};
+        assertClose("compare vectors" ,result_mapDivideToSelf,v_mapDivideToSelf.getData(),normTolerance);
+
+        //octave =  v1 .^ 2.0
+        RealVector v_mapPow = v1.mapPow(2.0d);
+        double[] result_mapPow = {1d, 4d, 9d};
+        assertClose("compare vectors" ,result_mapPow,v_mapPow.getData(),normTolerance);
+
+        //octave =  v1 .^ 2.0
+        RealVector v_mapPowToSelf = v1.copy();
+        v_mapPowToSelf.mapPowToSelf(2.0d);
+        double[] result_mapPowToSelf = {1d, 4d, 9d};
+        assertClose("compare vectors" ,result_mapPowToSelf,v_mapPowToSelf.getData(),normTolerance);
+
+        //octave =  exp(v1)
+        RealVector v_mapExp = v1.mapExp();
+        double[] result_mapExp = {2.718281828459045e+00d,7.389056098930650e+00d, 2.008553692318767e+01d};
+        assertClose("compare vectors" ,result_mapExp,v_mapExp.getData(),normTolerance);
+
+        //octave =  exp(v1)
+        RealVector v_mapExpToSelf = v1.copy();
+        v_mapExpToSelf.mapExpToSelf();
+        double[] result_mapExpToSelf = {2.718281828459045e+00d,7.389056098930650e+00d, 2.008553692318767e+01d};
+        assertClose("compare vectors" ,result_mapExpToSelf,v_mapExpToSelf.getData(),normTolerance);
+
+
+        //octave =  ???
+        RealVector v_mapExpm1 = v1.mapExpm1();
+        double[] result_mapExpm1 = {1.718281828459045d,6.38905609893065d, 19.085536923187668d};
+        assertClose("compare vectors" ,result_mapExpm1,v_mapExpm1.getData(),normTolerance);
+
+        //octave =  ???
+        RealVector v_mapExpm1ToSelf = v1.copy();
+        v_mapExpm1ToSelf.mapExpm1ToSelf();
+        double[] result_mapExpm1ToSelf = {1.718281828459045d,6.38905609893065d, 19.085536923187668d};
+        assertClose("compare vectors" ,result_mapExpm1ToSelf,v_mapExpm1ToSelf.getData(),normTolerance);
+
+        //octave =  log(v1)
+        RealVector v_mapLog = v1.mapLog();
+        double[] result_mapLog = {0d,6.931471805599453e-01d, 1.098612288668110e+00d};
+        assertClose("compare vectors" ,result_mapLog,v_mapLog.getData(),normTolerance);
+
+        //octave =  log(v1)
+        RealVector v_mapLogToSelf = v1.copy();
+        v_mapLogToSelf.mapLogToSelf();
+        double[] result_mapLogToSelf = {0d,6.931471805599453e-01d, 1.098612288668110e+00d};
+        assertClose("compare vectors" ,result_mapLogToSelf,v_mapLogToSelf.getData(),normTolerance);
+
+        //octave =  log10(v1)
+        RealVector v_mapLog10 = v1.mapLog10();
+        double[] result_mapLog10 = {0d,3.010299956639812e-01d, 4.771212547196624e-01d};
+        assertClose("compare vectors" ,result_mapLog10,v_mapLog10.getData(),normTolerance);
+
+        //octave =  log(v1)
+        RealVector v_mapLog10ToSelf = v1.copy();
+        v_mapLog10ToSelf.mapLog10ToSelf();
+        double[] result_mapLog10ToSelf = {0d,3.010299956639812e-01d, 4.771212547196624e-01d};
+        assertClose("compare vectors" ,result_mapLog10ToSelf,v_mapLog10ToSelf.getData(),normTolerance);
+
+        //octave =  ???
+        RealVector v_mapLog1p = v1.mapLog1p();
+        double[] result_mapLog1p = {0.6931471805599453d,1.0986122886681096d,1.3862943611198906d};
+        assertClose("compare vectors" ,result_mapLog1p,v_mapLog1p.getData(),normTolerance);
+
+        //octave =  ???
+        RealVector v_mapLog1pToSelf = v1.copy();
+        v_mapLog1pToSelf.mapLog1pToSelf();
+        double[] result_mapLog1pToSelf = {0.6931471805599453d,1.0986122886681096d,1.3862943611198906d};
+        assertClose("compare vectors" ,result_mapLog1pToSelf,v_mapLog1pToSelf.getData(),normTolerance);
+
+        //octave =  cosh(v1)
+        RealVector v_mapCosh = v1.mapCosh();
+        double[] result_mapCosh = {1.543080634815244e+00d,3.762195691083631e+00d, 1.006766199577777e+01d};
+        assertClose("compare vectors" ,result_mapCosh,v_mapCosh.getData(),normTolerance);
+
+        //octave =  cosh(v1)
+        RealVector v_mapCoshToSelf = v1.copy();
+        v_mapCoshToSelf.mapCoshToSelf();
+        double[] result_mapCoshToSelf = {1.543080634815244e+00d,3.762195691083631e+00d, 1.006766199577777e+01d};
+        assertClose("compare vectors" ,result_mapCoshToSelf,v_mapCoshToSelf.getData(),normTolerance);
+
+        //octave =  sinh(v1)
+        RealVector v_mapSinh = v1.mapSinh();
+        double[] result_mapSinh = {1.175201193643801e+00d,3.626860407847019e+00d, 1.001787492740990e+01d};
+        assertClose("compare vectors" ,result_mapSinh,v_mapSinh.getData(),normTolerance);
+
+        //octave =  sinh(v1)
+        RealVector v_mapSinhToSelf = v1.copy();
+        v_mapSinhToSelf.mapSinhToSelf();
+        double[] result_mapSinhToSelf = {1.175201193643801e+00d,3.626860407847019e+00d, 1.001787492740990e+01d};
+        assertClose("compare vectors" ,result_mapSinhToSelf,v_mapSinhToSelf.getData(),normTolerance);
+
+        //octave =  tanh(v1)
+        RealVector v_mapTanh = v1.mapTanh();
+        double[] result_mapTanh = {7.615941559557649e-01d,9.640275800758169e-01d,9.950547536867305e-01d};
+        assertClose("compare vectors" ,result_mapTanh,v_mapTanh.getData(),normTolerance);
+
+        //octave =  tanh(v1)
+        RealVector v_mapTanhToSelf = v1.copy();
+        v_mapTanhToSelf.mapTanhToSelf();
+        double[] result_mapTanhToSelf = {7.615941559557649e-01d,9.640275800758169e-01d,9.950547536867305e-01d};
+        assertClose("compare vectors" ,result_mapTanhToSelf,v_mapTanhToSelf.getData(),normTolerance);
+
+        //octave =  cos(v1)
+        RealVector v_mapCos = v1.mapCos();
+        double[] result_mapCos = {5.403023058681398e-01d,-4.161468365471424e-01d, -9.899924966004454e-01d};
+        assertClose("compare vectors" ,result_mapCos,v_mapCos.getData(),normTolerance);
+
+        //octave =  cos(v1)
+        RealVector v_mapCosToSelf = v1.copy();
+        v_mapCosToSelf.mapCosToSelf();
+        double[] result_mapCosToSelf = {5.403023058681398e-01d,-4.161468365471424e-01d, -9.899924966004454e-01d};
+        assertClose("compare vectors" ,result_mapCosToSelf,v_mapCosToSelf.getData(),normTolerance);
+
+        //octave =  sin(v1)
+        RealVector v_mapSin = v1.mapSin();
+        double[] result_mapSin = {8.414709848078965e-01d,9.092974268256817e-01d,1.411200080598672e-01d};
+        assertClose("compare vectors" ,result_mapSin,v_mapSin.getData(),normTolerance);
+
+        //octave =  sin(v1)
+        RealVector v_mapSinToSelf = v1.copy();
+        v_mapSinToSelf.mapSinToSelf();
+        double[] result_mapSinToSelf = {8.414709848078965e-01d,9.092974268256817e-01d,1.411200080598672e-01d};
+        assertClose("compare vectors" ,result_mapSinToSelf,v_mapSinToSelf.getData(),normTolerance);
+
+        //octave =  tan(v1)
+        RealVector v_mapTan = v1.mapTan();
+        double[] result_mapTan = {1.557407724654902e+00d,-2.185039863261519e+00d,-1.425465430742778e-01d};
+        assertClose("compare vectors" ,result_mapTan,v_mapTan.getData(),normTolerance);
+
+        //octave =  tan(v1)
+        RealVector v_mapTanToSelf = v1.copy();
+        v_mapTanToSelf.mapTanToSelf();
+        double[] result_mapTanToSelf = {1.557407724654902e+00d,-2.185039863261519e+00d,-1.425465430742778e-01d};
+        assertClose("compare vectors" ,result_mapTanToSelf,v_mapTanToSelf.getData(),normTolerance);
+
+        double[] vat_a = {0d, 0.5d, 1.0d};
+        ArrayRealVector vat = new ArrayRealVector(vat_a);
+
+        //octave =  acos(vat)
+        RealVector v_mapAcos = vat.mapAcos();
+        double[] result_mapAcos = {1.570796326794897e+00d,1.047197551196598e+00d, 0.0d};
+        assertClose("compare vectors" ,result_mapAcos,v_mapAcos.getData(),normTolerance);
+
+        //octave =  acos(vat)
+        RealVector v_mapAcosToSelf = vat.copy();
+        v_mapAcosToSelf.mapAcosToSelf();
+        double[] result_mapAcosToSelf = {1.570796326794897e+00d,1.047197551196598e+00d, 0.0d};
+        assertClose("compare vectors" ,result_mapAcosToSelf,v_mapAcosToSelf.getData(),normTolerance);
+
+        //octave =  asin(vat)
+        RealVector v_mapAsin = vat.mapAsin();
+        double[] result_mapAsin = {0.0d,5.235987755982989e-01d,1.570796326794897e+00d};
+        assertClose("compare vectors" ,result_mapAsin,v_mapAsin.getData(),normTolerance);
+
+        //octave =  asin(vat)
+        RealVector v_mapAsinToSelf = vat.copy();
+        v_mapAsinToSelf.mapAsinToSelf();
+        double[] result_mapAsinToSelf = {0.0d,5.235987755982989e-01d,1.570796326794897e+00d};
+        assertClose("compare vectors" ,result_mapAsinToSelf,v_mapAsinToSelf.getData(),normTolerance);
+
+        //octave =  atan(vat)
+        RealVector v_mapAtan = vat.mapAtan();
+        double[] result_mapAtan = {0.0d,4.636476090008061e-01d,7.853981633974483e-01d};
+        assertClose("compare vectors" ,result_mapAtan,v_mapAtan.getData(),normTolerance);
+
+        //octave =  atan(vat)
+        RealVector v_mapAtanToSelf = vat.copy();
+        v_mapAtanToSelf.mapAtanToSelf();
+        double[] result_mapAtanToSelf = {0.0d,4.636476090008061e-01d,7.853981633974483e-01d};
+        assertClose("compare vectors" ,result_mapAtanToSelf,v_mapAtanToSelf.getData(),normTolerance);
+
+        //octave =  v1 .^-1
+        RealVector v_mapInv = v1.mapInv();
+        double[] result_mapInv = {1d,0.5d,3.333333333333333e-01d};
+        assertClose("compare vectors" ,result_mapInv,v_mapInv.getData(),normTolerance);
+
+        //octave =  v1 .^-1
+        RealVector v_mapInvToSelf = v1.copy();
+        v_mapInvToSelf.mapInvToSelf();
+        double[] result_mapInvToSelf = {1d,0.5d,3.333333333333333e-01d};
+        assertClose("compare vectors" ,result_mapInvToSelf,v_mapInvToSelf.getData(),normTolerance);
+
+        double[] abs_a = {-1.0d, 0.0d, 1.0d};
+        ArrayRealVector abs_v = new ArrayRealVector(abs_a);
+
+        //octave =  abs(abs_v)
+        RealVector v_mapAbs = abs_v.mapAbs();
+        double[] result_mapAbs = {1d,0d,1d};
+        assertClose("compare vectors" ,result_mapAbs,v_mapAbs.getData(),normTolerance);
+
+        //octave = abs(abs_v)
+        RealVector v_mapAbsToSelf = abs_v.copy();
+        v_mapAbsToSelf.mapAbsToSelf();
+        double[] result_mapAbsToSelf = {1d,0d,1d};
+        assertClose("compare vectors" ,result_mapAbsToSelf,v_mapAbsToSelf.getData(),normTolerance);
+
+        //octave =   sqrt(v1)
+        RealVector v_mapSqrt = v1.mapSqrt();
+        double[] result_mapSqrt = {1d,1.414213562373095e+00d,1.732050807568877e+00d};
+        assertClose("compare vectors" ,result_mapSqrt,v_mapSqrt.getData(),normTolerance);
+
+        //octave =  sqrt(v1)
+        RealVector v_mapSqrtToSelf = v1.copy();
+        v_mapSqrtToSelf.mapSqrtToSelf();
+        double[] result_mapSqrtToSelf = {1d,1.414213562373095e+00d,1.732050807568877e+00d};
+        assertClose("compare vectors" ,result_mapSqrtToSelf,v_mapSqrtToSelf.getData(),normTolerance);
+
+        double[] cbrt_a = {-2.0d, 0.0d, 2.0d};
+        ArrayRealVector cbrt_v = new ArrayRealVector(cbrt_a);
+
+        //octave =  ???
+        RealVector v_mapCbrt = cbrt_v.mapCbrt();
+        double[] result_mapCbrt = {-1.2599210498948732d,0d,1.2599210498948732d};
+        assertClose("compare vectors" ,result_mapCbrt,v_mapCbrt.getData(),normTolerance);
+
+        //octave = ???
+        RealVector v_mapCbrtToSelf = cbrt_v.copy();
+        v_mapCbrtToSelf.mapCbrtToSelf();
+        double[] result_mapCbrtToSelf =  {-1.2599210498948732d,0d,1.2599210498948732d};
+        assertClose("compare vectors" ,result_mapCbrtToSelf,v_mapCbrtToSelf.getData(),normTolerance);
+
+        double[] ceil_a = {-1.1d, 0.9d, 1.1d};
+        ArrayRealVector ceil_v = new ArrayRealVector(ceil_a);
+
+        //octave =  ceil(ceil_v)
+        RealVector v_mapCeil = ceil_v.mapCeil();
+        double[] result_mapCeil = {-1d,1d,2d};
+        assertClose("compare vectors" ,result_mapCeil,v_mapCeil.getData(),normTolerance);
+
+        //octave = ceil(ceil_v)
+        RealVector v_mapCeilToSelf = ceil_v.copy();
+        v_mapCeilToSelf.mapCeilToSelf();
+        double[] result_mapCeilToSelf =  {-1d,1d,2d};
+        assertClose("compare vectors" ,result_mapCeilToSelf,v_mapCeilToSelf.getData(),normTolerance);
+
+        //octave =  floor(ceil_v)
+        RealVector v_mapFloor = ceil_v.mapFloor();
+        double[] result_mapFloor = {-2d,0d,1d};
+        assertClose("compare vectors" ,result_mapFloor,v_mapFloor.getData(),normTolerance);
+
+        //octave = floor(ceil_v)
+        RealVector v_mapFloorToSelf = ceil_v.copy();
+        v_mapFloorToSelf.mapFloorToSelf();
+        double[] result_mapFloorToSelf =  {-2d,0d,1d};
+        assertClose("compare vectors" ,result_mapFloorToSelf,v_mapFloorToSelf.getData(),normTolerance);
+
+        //octave =  ???
+        RealVector v_mapRint = ceil_v.mapRint();
+        double[] result_mapRint = {-1d,1d,1d};
+        assertClose("compare vectors" ,result_mapRint,v_mapRint.getData(),normTolerance);
+
+        //octave = ???
+        RealVector v_mapRintToSelf = ceil_v.copy();
+        v_mapRintToSelf.mapRintToSelf();
+        double[] result_mapRintToSelf =  {-1d,1d,1d};
+        assertClose("compare vectors" ,result_mapRintToSelf,v_mapRintToSelf.getData(),normTolerance);
+
+        //octave =  ???
+        RealVector v_mapSignum = ceil_v.mapSignum();
+        double[] result_mapSignum = {-1d,1d,1d};
+        assertClose("compare vectors" ,result_mapSignum,v_mapSignum.getData(),normTolerance);
+
+        //octave = ???
+        RealVector v_mapSignumToSelf = ceil_v.copy();
+        v_mapSignumToSelf.mapSignumToSelf();
+        double[] result_mapSignumToSelf =  {-1d,1d,1d};
+        assertClose("compare vectors" ,result_mapSignumToSelf,v_mapSignumToSelf.getData(),normTolerance);
+
+
+        // Is with the used resolutions of limited value as test
+        //octave =  ???
+        RealVector v_mapUlp = ceil_v.mapUlp();
+        double[] result_mapUlp = {2.220446049250313E-16d,1.1102230246251565E-16d,2.220446049250313E-16d};
+        assertClose("compare vectors" ,result_mapUlp,v_mapUlp.getData(),normTolerance);
+
+        //octave = ???
+        RealVector v_mapUlpToSelf = ceil_v.copy();
+        v_mapUlpToSelf.mapUlpToSelf();
+        double[] result_mapUlpToSelf = {2.220446049250313E-16d,1.1102230246251565E-16d,2.220446049250313E-16d};
+        assertClose("compare vectors" ,result_mapUlpToSelf,v_mapUlpToSelf.getData(),normTolerance);
+
+    }
+
+    public void testBasicFunctions() {
+        ArrayRealVector v1 = new ArrayRealVector(vec1);
+        ArrayRealVector v2 = new ArrayRealVector(vec2);
+        ArrayRealVector v5 = new ArrayRealVector(vec5);
+        ArrayRealVector v_null = new ArrayRealVector(vec_null);
+
+        RealVectorTestImpl v2_t = new RealVectorTestImpl(vec2);
+
+        // emacs calc: [-4, 0, 3, 1, -6, 3] A --> 8.4261497731763586307
+        double d_getNorm = v5.getNorm();
+        assertEquals("compare values  ", 8.4261497731763586307, d_getNorm);
+
+        // emacs calc: [-4, 0, 3, 1, -6, 3] vN --> 17
+        double d_getL1Norm = v5.getL1Norm();
+        assertEquals("compare values  ", 17.0, d_getL1Norm);
+
+        // emacs calc: [-4, 0, 3, 1, -6, 3] vn --> 6
+        double d_getLInfNorm = v5.getLInfNorm();
+        assertEquals("compare values  ", 6.0, d_getLInfNorm);
+
+
+        //octave =  sqrt(sumsq(v1-v2))
+        double dist = v1.getDistance(v2);
+        assertEquals("compare values  ",v1.subtract(v2).getNorm(), dist );
+
+        //octave =  sqrt(sumsq(v1-v2))
+        double dist_2 = v1.getDistance(v2_t);
+        assertEquals("compare values  ", v1.subtract(v2).getNorm(),dist_2 );
+
+        //octave =  sqrt(sumsq(v1-v2))
+        double dist_3 = v1.getDistance((RealVector) v2);
+        assertEquals("compare values  ", v1.subtract(v2).getNorm(),dist_3 );
+
+        //octave =  ???
+        double d_getL1Distance = v1. getL1Distance(v2);
+        assertEquals("compare values  ",9d, d_getL1Distance );
+
+        double d_getL1Distance_2 = v1. getL1Distance(v2_t);
+        assertEquals("compare values  ",9d, d_getL1Distance_2 );
+
+        double d_getL1Distance_3 = v1. getL1Distance((RealVector) v2);
+        assertEquals("compare values  ",9d, d_getL1Distance_3 );
+
+        //octave =  ???
+        double d_getLInfDistance = v1. getLInfDistance(v2);
+        assertEquals("compare values  ",3d, d_getLInfDistance );
+
+        double d_getLInfDistance_2 = v1. getLInfDistance(v2_t);
+        assertEquals("compare values  ",3d, d_getLInfDistance_2 );
+
+        double d_getLInfDistance_3 = v1. getLInfDistance((RealVector) v2);
+        assertEquals("compare values  ",3d, d_getLInfDistance_3 );
+
+        //octave =  v1 + v2
+        ArrayRealVector v_add = v1.add(v2);
+        double[] result_add = {5d, 7d, 9d};
+        assertClose("compare vect" ,v_add.getData(),result_add,normTolerance);
+
+        RealVectorTestImpl vt2 = new RealVectorTestImpl(vec2);
+        RealVector v_add_i = v1.add(vt2);
+        double[] result_add_i = {5d, 7d, 9d};
+        assertClose("compare vect" ,v_add_i.getData(),result_add_i,normTolerance);
+
+        //octave =  v1 - v2
+        ArrayRealVector v_subtract = v1.subtract(v2);
+        double[] result_subtract = {-3d, -3d, -3d};
+        assertClose("compare vect" ,v_subtract.getData(),result_subtract,normTolerance);
+
+        RealVector v_subtract_i = v1.subtract(vt2);
+        double[] result_subtract_i = {-3d, -3d, -3d};
+        assertClose("compare vect" ,v_subtract_i.getData(),result_subtract_i,normTolerance);
+
+        // octave v1 .* v2
+        ArrayRealVector  v_ebeMultiply = v1.ebeMultiply(v2);
+        double[] result_ebeMultiply = {4d, 10d, 18d};
+        assertClose("compare vect" ,v_ebeMultiply.getData(),result_ebeMultiply,normTolerance);
+
+        RealVector  v_ebeMultiply_2 = v1.ebeMultiply(v2_t);
+        double[] result_ebeMultiply_2 = {4d, 10d, 18d};
+        assertClose("compare vect" ,v_ebeMultiply_2.getData(),result_ebeMultiply_2,normTolerance);
+
+        RealVector  v_ebeMultiply_3 = v1.ebeMultiply((RealVector) v2);
+        double[] result_ebeMultiply_3 = {4d, 10d, 18d};
+        assertClose("compare vect" ,v_ebeMultiply_3.getData(),result_ebeMultiply_3,normTolerance);
+
+        // octave v1 ./ v2
+        ArrayRealVector  v_ebeDivide = v1.ebeDivide(v2);
+        double[] result_ebeDivide = {0.25d, 0.4d, 0.5d};
+        assertClose("compare vect" ,v_ebeDivide.getData(),result_ebeDivide,normTolerance);
+
+        RealVector  v_ebeDivide_2 = v1.ebeDivide(v2_t);
+        double[] result_ebeDivide_2 = {0.25d, 0.4d, 0.5d};
+        assertClose("compare vect" ,v_ebeDivide_2.getData(),result_ebeDivide_2,normTolerance);
+
+        RealVector  v_ebeDivide_3 = v1.ebeDivide((RealVector) v2);
+        double[] result_ebeDivide_3 = {0.25d, 0.4d, 0.5d};
+        assertClose("compare vect" ,v_ebeDivide_3.getData(),result_ebeDivide_3,normTolerance);
+
+        // octave  dot(v1,v2)
+        double dot =  v1.dotProduct(v2);
+        assertEquals("compare val ",32d, dot);
+
+        // octave  dot(v1,v2_t)
+        double dot_2 =  v1.dotProduct(v2_t);
+        assertEquals("compare val ",32d, dot_2);
+
+        RealMatrix m_outerProduct = v1.outerProduct(v2);
+        assertEquals("compare val ",4d, m_outerProduct.getEntry(0,0));
+
+        RealMatrix m_outerProduct_2 = v1.outerProduct(v2_t);
+        assertEquals("compare val ",4d, m_outerProduct_2.getEntry(0,0));
+
+        RealMatrix m_outerProduct_3 = v1.outerProduct((RealVector) v2);
+        assertEquals("compare val ",4d, m_outerProduct_3.getEntry(0,0));
+
+        RealVector v_unitVector = v1.unitVector();
+        RealVector v_unitVector_2 = v1.mapDivide(v1.getNorm());
+        assertClose("compare vect" ,v_unitVector.getData(),v_unitVector_2.getData(),normTolerance);
+
+        try {
+            v_null.unitVector();
+            fail("Expecting ArithmeticException");
+        } catch (ArithmeticException ex) {
+            // expected behavior
+        }
+
+        ArrayRealVector v_unitize = (ArrayRealVector)v1.copy();
+        v_unitize.unitize();
+        assertClose("compare vect" ,v_unitVector_2.getData(),v_unitize.getData(),normTolerance);
+        try {
+            v_null.unitize();
+            fail("Expecting ArithmeticException");
+        } catch (ArithmeticException ex) {
+            // expected behavior
+        }
+
+        ArrayRealVector v_projection = v1.projection(v2);
+        double[] result_projection = {1.662337662337662, 2.0779220779220777, 2.493506493506493};
+        assertClose("compare vect", v_projection.getData(), result_projection, normTolerance);
+
+        RealVector v_projection_2 = v1.projection(v2_t);
+        double[] result_projection_2 = {1.662337662337662, 2.0779220779220777, 2.493506493506493};
+        assertClose("compare vect", v_projection_2.getData(), result_projection_2, normTolerance);
+
+        RealVector v_projection_3 = v1.projection(v2.getData());
+        double[] result_projection_3 = {1.662337662337662, 2.0779220779220777, 2.493506493506493};
+        assertClose("compare vect", v_projection_3.getData(), result_projection_3, normTolerance);
+
+    }
+
+    public void testMisc() {
+        ArrayRealVector v1 = new ArrayRealVector(vec1);
+        ArrayRealVector v4 = new ArrayRealVector(vec4);
+        RealVector v4_2 = new ArrayRealVector(vec4);
+
+        String out1 = v1.toString();
+        assertTrue("some output ",  out1.length()!=0);
+        /*
+         double[] dout1 = v1.copyOut();
+        assertEquals("testData len", 3, dout1.length);
+        assertNotSame("testData not same object ", v1.data, dout1);
+         */
+        try {
+            v1.checkVectorDimensions(2);
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected behavior
+        }
+
+       try {
+            v1.checkVectorDimensions(v4);
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected behavior
+        }
+
+        try {
+            v1.checkVectorDimensions(v4_2);
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected behavior
+        }
+
+    }
+
+    public void testPredicates() {
+
+        ArrayRealVector v = new ArrayRealVector(new double[] { 0, 1, 2 });
+
+        assertFalse(v.isNaN());
+        v.setEntry(1, Double.NaN);
+        assertTrue(v.isNaN());
+
+        assertFalse(v.isInfinite());
+        v.setEntry(0, Double.POSITIVE_INFINITY);
+        assertFalse(v.isInfinite());
+        v.setEntry(1, 1);
+        assertTrue(v.isInfinite());
+        v.setEntry(0, 1);
+        assertFalse(v.isInfinite());
+
+        v.setEntry(0, 0);
+        assertEquals(v, new ArrayRealVector(new double[] { 0, 1, 2 }));
+        assertNotSame(v, new ArrayRealVector(new double[] { 0, 1, 2 + FastMath.ulp(2)}));
+        assertNotSame(v, new ArrayRealVector(new double[] { 0, 1, 2, 3 }));
+
+        assertEquals(new ArrayRealVector(new double[] { Double.NaN, 1, 2 }).hashCode(),
+                     new ArrayRealVector(new double[] { 0, Double.NaN, 2 }).hashCode());
+
+        assertTrue(new ArrayRealVector(new double[] { Double.NaN, 1, 2 }).hashCode() !=
+                   new ArrayRealVector(new double[] { 0, 1, 2 }).hashCode());
+
+        assertTrue(v.equals(v));
+        assertTrue(v.equals(v.copy()));
+        assertFalse(v.equals(null));
+        assertFalse(v.equals(v.getDataRef()));
+        assertFalse(v.equals(v.getSubVector(0, v.getDimension() - 1)));
+        assertTrue(v.equals(v.getSubVector(0, v.getDimension())));
+
+    }
+
+    public void testSerial()  {
+        ArrayRealVector v = new ArrayRealVector(new double[] { 0, 1, 2 });
+        assertEquals(v,TestUtils.serializeAndRecover(v));
+    }
+
+    public void testZeroVectors() {
+        assertEquals(0, new ArrayRealVector(new double[0]).getDimension());
+        assertEquals(0, new ArrayRealVector(new double[0], true).getDimension());
+        assertEquals(0, new ArrayRealVector(new double[0], false).getDimension());
+    }
+
+    public void testMinMax()  {
+        ArrayRealVector v1 = new ArrayRealVector(new double[] { 0, -6, 4, 12, 7 });
+        assertEquals(1,  v1.getMinIndex());
+        assertEquals(-6, v1.getMinValue(), 1.0e-12);
+        assertEquals(3,  v1.getMaxIndex());
+        assertEquals(12, v1.getMaxValue(), 1.0e-12);
+        ArrayRealVector v2 = new ArrayRealVector(new double[] { Double.NaN, 3, Double.NaN, -2 });
+        assertEquals(3,  v2.getMinIndex());
+        assertEquals(-2, v2.getMinValue(), 1.0e-12);
+        assertEquals(1,  v2.getMaxIndex());
+        assertEquals(3, v2.getMaxValue(), 1.0e-12);
+        ArrayRealVector v3 = new ArrayRealVector(new double[] { Double.NaN, Double.NaN });
+        assertEquals(-1,  v3.getMinIndex());
+        assertTrue(Double.isNaN(v3.getMinValue()));
+        assertEquals(-1,  v3.getMaxIndex());
+        assertTrue(Double.isNaN(v3.getMaxValue()));
+        ArrayRealVector v4 = new ArrayRealVector(new double[0]);
+        assertEquals(-1,  v4.getMinIndex());
+        assertTrue(Double.isNaN(v4.getMinValue()));
+        assertEquals(-1,  v4.getMaxIndex());
+        assertTrue(Double.isNaN(v4.getMaxValue()));
+    }
+
+
+    /** verifies that two vectors are close (sup norm) */
+    protected void assertClose(String msg, double[] m, double[] n,
+            double tolerance) {
+        if (m.length != n.length) {
+            fail("vectors have different lengths");
+        }
+        for (int i = 0; i < m.length; i++) {
+            assertEquals(msg + " " +  i + " elements differ", m[i],n[i],tolerance);
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/linear/BiDiagonalTransformerTest.java b/src/test/java/org/apache/commons/math/linear/BiDiagonalTransformerTest.java
new file mode 100644
index 0000000..ce732ae
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/linear/BiDiagonalTransformerTest.java
@@ -0,0 +1,204 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.linear;
+
+import org.apache.commons.math.linear.BiDiagonalTransformer;
+import org.apache.commons.math.linear.MatrixUtils;
+import org.apache.commons.math.linear.RealMatrix;
+import org.apache.commons.math.util.FastMath;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class BiDiagonalTransformerTest {
+
+    private double[][] testSquare = {
+            { 24.0 / 25.0, 43.0 / 25.0 },
+            { 57.0 / 25.0, 24.0 / 25.0 }
+    };
+
+    private double[][] testNonSquare = {
+        {  -540.0 / 625.0,  963.0 / 625.0, -216.0 / 625.0 },
+        { -1730.0 / 625.0, -744.0 / 625.0, 1008.0 / 625.0 },
+        {  -720.0 / 625.0, 1284.0 / 625.0, -288.0 / 625.0 },
+        {  -360.0 / 625.0,  192.0 / 625.0, 1756.0 / 625.0 },
+    };
+
+    @Test
+    public void testDimensions() {
+        checkdimensions(MatrixUtils.createRealMatrix(testSquare));
+        checkdimensions(MatrixUtils.createRealMatrix(testNonSquare));
+        checkdimensions(MatrixUtils.createRealMatrix(testNonSquare).transpose());
+    }
+
+    private void checkdimensions(RealMatrix matrix) {
+        final int m = matrix.getRowDimension();
+        final int n = matrix.getColumnDimension();
+        BiDiagonalTransformer transformer = new BiDiagonalTransformer(matrix);
+        Assert.assertEquals(m, transformer.getU().getRowDimension());
+        Assert.assertEquals(m, transformer.getU().getColumnDimension());
+        Assert.assertEquals(m, transformer.getB().getRowDimension());
+        Assert.assertEquals(n, transformer.getB().getColumnDimension());
+        Assert.assertEquals(n, transformer.getV().getRowDimension());
+        Assert.assertEquals(n, transformer.getV().getColumnDimension());
+
+    }
+
+    @Test
+    public void testAEqualUSVt() {
+        checkAEqualUSVt(MatrixUtils.createRealMatrix(testSquare));
+        checkAEqualUSVt(MatrixUtils.createRealMatrix(testNonSquare));
+        checkAEqualUSVt(MatrixUtils.createRealMatrix(testNonSquare).transpose());
+    }
+
+    private void checkAEqualUSVt(RealMatrix matrix) {
+        BiDiagonalTransformer transformer = new BiDiagonalTransformer(matrix);
+        RealMatrix u = transformer.getU();
+        RealMatrix b = transformer.getB();
+        RealMatrix v = transformer.getV();
+        double norm = u.multiply(b).multiply(v.transpose()).subtract(matrix).getNorm();
+        Assert.assertEquals(0, norm, 1.0e-14);
+    }
+
+    @Test
+    public void testUOrthogonal() {
+        checkOrthogonal(new BiDiagonalTransformer(MatrixUtils.createRealMatrix(testSquare)).getU());
+        checkOrthogonal(new BiDiagonalTransformer(MatrixUtils.createRealMatrix(testNonSquare)).getU());
+        checkOrthogonal(new BiDiagonalTransformer(MatrixUtils.createRealMatrix(testNonSquare).transpose()).getU());
+    }
+
+    @Test
+    public void testVOrthogonal() {
+        checkOrthogonal(new BiDiagonalTransformer(MatrixUtils.createRealMatrix(testSquare)).getV());
+        checkOrthogonal(new BiDiagonalTransformer(MatrixUtils.createRealMatrix(testNonSquare)).getV());
+        checkOrthogonal(new BiDiagonalTransformer(MatrixUtils.createRealMatrix(testNonSquare).transpose()).getV());
+    }
+
+    private void checkOrthogonal(RealMatrix m) {
+        RealMatrix mTm = m.transpose().multiply(m);
+        RealMatrix id  = MatrixUtils.createRealIdentityMatrix(mTm.getRowDimension());
+        Assert.assertEquals(0, mTm.subtract(id).getNorm(), 1.0e-14);
+    }
+
+    @Test
+    public void testBBiDiagonal() {
+        checkBiDiagonal(new BiDiagonalTransformer(MatrixUtils.createRealMatrix(testSquare)).getB());
+        checkBiDiagonal(new BiDiagonalTransformer(MatrixUtils.createRealMatrix(testNonSquare)).getB());
+        checkBiDiagonal(new BiDiagonalTransformer(MatrixUtils.createRealMatrix(testNonSquare).transpose()).getB());
+    }
+
+    private void checkBiDiagonal(RealMatrix m) {
+        final int rows = m.getRowDimension();
+        final int cols = m.getColumnDimension();
+        for (int i = 0; i < rows; ++i) {
+            for (int j = 0; j < cols; ++j) {
+                if (rows < cols) {
+                    if ((i < j) || (i > j + 1)) {
+                        Assert.assertEquals(0, m.getEntry(i, j), 1.0e-16);
+                    }
+                } else {
+                    if ((i < j - 1) || (i > j)) {
+                        Assert.assertEquals(0, m.getEntry(i, j), 1.0e-16);
+                    }
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testSingularMatrix() {
+       BiDiagonalTransformer transformer =
+            new BiDiagonalTransformer(MatrixUtils.createRealMatrix(new double[][] {
+                { 1.0, 2.0, 3.0 },
+                { 2.0, 3.0, 4.0 },
+                { 3.0, 5.0, 7.0 }
+            }));
+       final double s3  = FastMath.sqrt(3.0);
+       final double s14 = FastMath.sqrt(14.0);
+       final double s1553 = FastMath.sqrt(1553.0);
+       RealMatrix uRef = MatrixUtils.createRealMatrix(new double[][] {
+           {  -1.0 / s14,  5.0 / (s3 * s14),  1.0 / s3 },
+           {  -2.0 / s14, -4.0 / (s3 * s14),  1.0 / s3 },
+           {  -3.0 / s14,  1.0 / (s3 * s14), -1.0 / s3 }
+       });
+       RealMatrix bRef = MatrixUtils.createRealMatrix(new double[][] {
+           { -s14, s1553 / s14,   0.0 },
+           {  0.0, -87 * s3 / (s14 * s1553), -s3 * s14 / s1553 },
+           {  0.0, 0.0, 0.0 }
+       });
+       RealMatrix vRef = MatrixUtils.createRealMatrix(new double[][] {
+           { 1.0,   0.0,         0.0        },
+           { 0.0,  -23 / s1553,  32 / s1553 },
+           { 0.0,  -32 / s1553, -23 / s1553 }
+       });
+
+       // check values against known references
+       RealMatrix u = transformer.getU();
+       Assert.assertEquals(0, u.subtract(uRef).getNorm(), 1.0e-14);
+       RealMatrix b = transformer.getB();
+       Assert.assertEquals(0, b.subtract(bRef).getNorm(), 1.0e-14);
+       RealMatrix v = transformer.getV();
+       Assert.assertEquals(0, v.subtract(vRef).getNorm(), 1.0e-14);
+
+       // check the same cached instance is returned the second time
+       Assert.assertTrue(u == transformer.getU());
+       Assert.assertTrue(b == transformer.getB());
+       Assert.assertTrue(v == transformer.getV());
+
+    }
+
+    @Test
+    public void testMatricesValues() {
+       BiDiagonalTransformer transformer =
+            new BiDiagonalTransformer(MatrixUtils.createRealMatrix(testSquare));
+       final double s17 = FastMath.sqrt(17.0);
+        RealMatrix uRef = MatrixUtils.createRealMatrix(new double[][] {
+                {  -8 / (5 * s17), 19 / (5 * s17) },
+                { -19 / (5 * s17), -8 / (5 * s17) }
+        });
+        RealMatrix bRef = MatrixUtils.createRealMatrix(new double[][] {
+                { -3 * s17 / 5, 32 * s17 / 85 },
+                {      0.0,     -5 * s17 / 17 }
+        });
+        RealMatrix vRef = MatrixUtils.createRealMatrix(new double[][] {
+                { 1.0,  0.0 },
+                { 0.0, -1.0 }
+        });
+
+        // check values against known references
+        RealMatrix u = transformer.getU();
+        Assert.assertEquals(0, u.subtract(uRef).getNorm(), 1.0e-14);
+        RealMatrix b = transformer.getB();
+        Assert.assertEquals(0, b.subtract(bRef).getNorm(), 1.0e-14);
+        RealMatrix v = transformer.getV();
+        Assert.assertEquals(0, v.subtract(vRef).getNorm(), 1.0e-14);
+
+        // check the same cached instance is returned the second time
+        Assert.assertTrue(u == transformer.getU());
+        Assert.assertTrue(b == transformer.getB());
+        Assert.assertTrue(v == transformer.getV());
+
+    }
+
+    @Test
+    public void testUpperOrLower() {
+        Assert.assertTrue(new BiDiagonalTransformer(MatrixUtils.createRealMatrix(testSquare)).isUpperBiDiagonal());
+        Assert.assertTrue(new BiDiagonalTransformer(MatrixUtils.createRealMatrix(testNonSquare)).isUpperBiDiagonal());
+        Assert.assertFalse(new BiDiagonalTransformer(MatrixUtils.createRealMatrix(testNonSquare).transpose()).isUpperBiDiagonal());
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/linear/BigMatrixImplTest.java b/src/test/java/org/apache/commons/math/linear/BigMatrixImplTest.java
new file mode 100644
index 0000000..4ed2e6b
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/linear/BigMatrixImplTest.java
@@ -0,0 +1,836 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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;
+
+import junit.framework.TestCase;
+
+
+/**
+ * Test cases for the {@link BigMatrixImpl} class.
+ *
+ * @version $Revision: 902201 $ $Date: 2010-01-22 19:18:16 +0100 (ven. 22 janv. 2010) $
+ */
+ at Deprecated
+public final class BigMatrixImplTest extends TestCase {
+
+    // Test data for String constructors
+    protected  String[][] testDataString = { {"1","2","3"}, {"2","5","3"}, {"1","0","8"} };
+
+    // 3 x 3 identity matrix
+    protected double[][] id = { {1d,0d,0d}, {0d,1d,0d}, {0d,0d,1d} };
+
+    // Test data for group operations
+    protected double[][] testData = { {1d,2d,3d}, {2d,5d,3d}, {1d,0d,8d} };
+    protected double[][] testDataLU = {{2d, 5d, 3d}, {.5d, -2.5d, 6.5d}, {0.5d, 0.2d, .2d}};
+    protected double[][] testDataPlus2 = { {3d,4d,5d}, {4d,7d,5d}, {3d,2d,10d} };
+    protected double[][] testDataMinus = { {-1d,-2d,-3d}, {-2d,-5d,-3d},
+            {-1d,0d,-8d} };
+    protected double[] testDataRow1 = {1d,2d,3d};
+    protected double[] testDataCol3 = {3d,3d,8d};
+    protected double[][] testDataInv =
+        { {-40d,16d,9d}, {13d,-5d,-3d}, {5d,-2d,-1d} };
+    protected double[] preMultTest = {8,12,33};
+    protected double[][] testData2 ={ {1d,2d,3d}, {2d,5d,3d}};
+    protected double[][] testData2T = { {1d,2d}, {2d,5d}, {3d,3d}};
+    protected double[][] testDataPlusInv =
+        { {-39d,18d,12d}, {15d,0d,0d}, {6d,-2d,7d} };
+
+    // lu decomposition tests
+    protected double[][] luData = { {2d,3d,3d}, {0d,5d,7d}, {6d,9d,8d} };
+    protected double[][] luDataLUDecomposition = { {6d,9d,8d}, {0d,5d,7d},
+            {0.33333333333333,0d,0.33333333333333} };
+
+    // singular matrices
+    protected double[][] singular = { {2d,3d}, {2d,3d} };
+    protected double[][] bigSingular = {{1d,2d,3d,4d}, {2d,5d,3d,4d},
+            {7d,3d,256d,1930d}, {3d,7d,6d,8d}}; // 4th row = 1st + 2nd
+    protected double[][] detData = { {1d,2d,3d}, {4d,5d,6d}, {7d,8d,10d} };
+    protected double[][] detData2 = { {1d, 3d}, {2d, 4d}};
+
+    // vectors
+    protected double[] testVector = {1,2,3};
+    protected double[] testVector2 = {1,2,3,4};
+
+    // submatrix accessor tests
+    protected double[][] subTestData = {{1, 2, 3, 4}, {1.5, 2.5, 3.5, 4.5},
+            {2, 4, 6, 8}, {4, 5, 6, 7}};
+    // array selections
+    protected double[][] subRows02Cols13 = { {2, 4}, {4, 8}};
+    protected double[][] subRows03Cols12 = { {2, 3}, {5, 6}};
+    protected double[][] subRows03Cols123 = { {2, 3, 4} , {5, 6, 7}};
+    // effective permutations
+    protected double[][] subRows20Cols123 = { {4, 6, 8} , {2, 3, 4}};
+    protected double[][] subRows31Cols31 = {{7, 5}, {4.5, 2.5}};
+    // contiguous ranges
+    protected double[][] subRows01Cols23 = {{3,4} , {3.5, 4.5}};
+    protected double[][] subRows23Cols00 = {{2} , {4}};
+    protected double[][] subRows00Cols33 = {{4}};
+    // row matrices
+    protected double[][] subRow0 = {{1,2,3,4}};
+    protected double[][] subRow3 = {{4,5,6,7}};
+    // column matrices
+    protected double[][] subColumn1 = {{2}, {2.5}, {4}, {5}};
+    protected double[][] subColumn3 = {{4}, {4.5}, {8}, {7}};
+
+    // tolerances
+    protected double entryTolerance = 10E-16;
+    protected double normTolerance = 10E-14;
+
+    public BigMatrixImplTest(String name) {
+        super(name);
+    }
+
+    public static final double[] asDouble(BigDecimal[] data) {
+        double d[] = new double[data.length];
+        for (int i=0;i<d.length;i++) {
+            d[i] = data[i].doubleValue();
+        }
+        return d;
+    }
+
+    public static final double[][] asDouble(BigDecimal[][] data) {
+        double d[][] = new double[data.length][data[0].length];
+        for (int i=0;i<d.length;i++) {
+            for (int j=0;j<d[i].length;j++)
+            d[i][j] = data[i][j].doubleValue();
+        }
+        return d;
+    }
+
+    public static final BigDecimal[] asBigDecimal(double [] data) {
+        BigDecimal d[] = new BigDecimal[data.length];
+        for (int i=0;i<d.length;i++) {
+            d[i] = new BigDecimal(data[i]);
+        }
+        return d;
+    }
+
+    public static final BigDecimal[][] asBigDecimal(double [][] data) {
+        BigDecimal d[][] = new BigDecimal[data.length][data[0].length];
+        for (int i=0;i<d.length;i++) {
+            for (int j=0;j<data[i].length;j++) {
+                d[i][j] = new BigDecimal(data[i][j]);
+            }
+        }
+        return d;
+    }
+
+    /** test dimensions */
+    public void testDimensions() {
+        BigMatrixImpl m = new BigMatrixImpl(testData);
+        BigMatrixImpl m2 = new BigMatrixImpl(testData2);
+        assertEquals("testData row dimension",3,m.getRowDimension());
+        assertEquals("testData column dimension",3,m.getColumnDimension());
+        assertTrue("testData is square",m.isSquare());
+        assertEquals("testData2 row dimension",m2.getRowDimension(),2);
+        assertEquals("testData2 column dimension",m2.getColumnDimension(),3);
+        assertTrue("testData2 is not square",!m2.isSquare());
+    }
+
+    /** test copy functions */
+    public void testCopyFunctions() {
+        BigMatrixImpl m1 = new BigMatrixImpl(testData);
+        BigMatrixImpl m2 = new BigMatrixImpl(m1.getData());
+        assertEquals(m2,m1);
+        BigMatrixImpl m3 = new BigMatrixImpl(testData);
+        BigMatrixImpl m4 = new BigMatrixImpl(m3.getData(), false);
+        assertEquals(m4,m3);
+    }
+
+    /** test constructors */
+    public void testConstructors() {
+        BigMatrix m1 = new BigMatrixImpl(testData);
+        BigMatrix m2 = new BigMatrixImpl(testDataString);
+        BigMatrix m3 = new BigMatrixImpl(asBigDecimal(testData));
+        BigMatrix m4 = new BigMatrixImpl(asBigDecimal(testData), true);
+        BigMatrix m5 = new BigMatrixImpl(asBigDecimal(testData), false);
+        assertClose("double, string", m1, m2, Double.MIN_VALUE);
+        assertClose("double, BigDecimal", m1, m3, Double.MIN_VALUE);
+        assertClose("string, BigDecimal", m2, m3, Double.MIN_VALUE);
+        assertClose("double, BigDecimal/true", m1, m4, Double.MIN_VALUE);
+        assertClose("double, BigDecimal/false", m1, m5, Double.MIN_VALUE);
+        try {
+            new BigMatrixImpl(new String[][] {{"0", "hello", "1"}});
+            fail("Expecting NumberFormatException");
+        } catch (NumberFormatException ex) {
+            // expected
+        }
+        try {
+            new BigMatrixImpl(new String[][] {});
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            new BigMatrixImpl(new String[][] {{},{}});
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            new BigMatrixImpl(new String[][] {{"a", "b"},{"c"}});
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        try {
+            new BigMatrixImpl(0, 1);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            new BigMatrixImpl(1, 0);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+
+    /** test add */
+    public void testAdd() {
+        BigMatrixImpl m = new BigMatrixImpl(testData);
+        BigMatrixImpl mInv = new BigMatrixImpl(testDataInv);
+        BigMatrix mPlusMInv = m.add(mInv);
+        double[][] sumEntries = asDouble(mPlusMInv.getData());
+        for (int row = 0; row < m.getRowDimension(); row++) {
+            for (int col = 0; col < m.getColumnDimension(); col++) {
+                assertEquals("sum entry entry",
+                    testDataPlusInv[row][col],sumEntries[row][col],
+                        entryTolerance);
+            }
+        }
+    }
+
+    /** test add failure */
+    public void testAddFail() {
+        BigMatrixImpl m = new BigMatrixImpl(testData);
+        BigMatrixImpl m2 = new BigMatrixImpl(testData2);
+        try {
+            m.add(m2);
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    /** test norm */
+    public void testNorm() {
+        BigMatrixImpl m = new BigMatrixImpl(testData);
+        BigMatrixImpl m2 = new BigMatrixImpl(testData2);
+        assertEquals("testData norm",14d,m.getNorm().doubleValue(),entryTolerance);
+        assertEquals("testData2 norm",7d,m2.getNorm().doubleValue(),entryTolerance);
+    }
+
+     /** test m-n = m + -n */
+    public void testPlusMinus() {
+        BigMatrixImpl m = new BigMatrixImpl(testData);
+        BigMatrixImpl m2 = new BigMatrixImpl(testDataInv);
+        assertClose("m-n = m + -n",m.subtract(m2),
+            m2.scalarMultiply(new BigDecimal(-1d)).add(m),entryTolerance);
+        try {
+            m.subtract(new BigMatrixImpl(testData2));
+            fail("Expecting illegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    /** test multiply */
+     public void testMultiply() {
+        BigMatrixImpl m = new BigMatrixImpl(testData);
+        BigMatrixImpl mInv = new BigMatrixImpl(testDataInv);
+        BigMatrixImpl identity = new BigMatrixImpl(id);
+        BigMatrixImpl m2 = new BigMatrixImpl(testData2);
+        assertClose("inverse multiply",m.multiply(mInv),
+            identity,entryTolerance);
+        assertClose("inverse multiply",mInv.multiply(m),
+            identity,entryTolerance);
+        assertClose("identity multiply",m.multiply(identity),
+            m,entryTolerance);
+        assertClose("identity multiply",identity.multiply(mInv),
+            mInv,entryTolerance);
+        assertClose("identity multiply",m2.multiply(identity),
+            m2,entryTolerance);
+        try {
+            m.multiply(new BigMatrixImpl(bigSingular));
+            fail("Expecting illegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    //Additional Test for BigMatrixImplTest.testMultiply
+
+    private double[][] d3 = new double[][] {{1,2,3,4},{5,6,7,8}};
+    private double[][] d4 = new double[][] {{1},{2},{3},{4}};
+    private double[][] d5 = new double[][] {{30},{70}};
+
+    public void testMultiply2() {
+       BigMatrix m3 = new BigMatrixImpl(d3);
+       BigMatrix m4 = new BigMatrixImpl(d4);
+       BigMatrix m5 = new BigMatrixImpl(d5);
+       assertClose("m3*m4=m5", m3.multiply(m4), m5, entryTolerance);
+   }
+
+    /** test isSingular */
+    public void testIsSingular() {
+        BigMatrixImpl m = new BigMatrixImpl(singular);
+        assertTrue("singular",m.isSingular());
+        m = new BigMatrixImpl(bigSingular);
+        assertTrue("big singular",m.isSingular());
+        m = new BigMatrixImpl(id);
+        assertTrue("identity nonsingular",!m.isSingular());
+        m = new BigMatrixImpl(testData);
+        assertTrue("testData nonsingular",!m.isSingular());
+    }
+
+    /** test inverse */
+    public void testInverse() {
+        BigMatrixImpl m = new BigMatrixImpl(testData);
+        BigMatrix mInv = new BigMatrixImpl(testDataInv);
+        assertClose("inverse",mInv,m.inverse(),normTolerance);
+        assertClose("inverse^2",m,m.inverse().inverse(),10E-12);
+
+        // Not square
+        m = new BigMatrixImpl(testData2);
+        try {
+            m.inverse();
+            fail("Expecting InvalidMatrixException");
+        } catch (InvalidMatrixException ex) {
+            // expected
+        }
+
+        // Singular
+        m = new BigMatrixImpl(singular);
+        try {
+            m.inverse();
+            fail("Expecting InvalidMatrixException");
+        } catch (InvalidMatrixException ex) {
+            // expected
+        }
+    }
+
+    /** test solve */
+    public void testSolve() {
+        BigMatrixImpl m = new BigMatrixImpl(testData);
+        BigMatrix mInv = new BigMatrixImpl(testDataInv);
+        // being a bit slothful here -- actually testing that X = A^-1 * B
+        assertClose("inverse-operate",
+                    asDouble(mInv.operate(asBigDecimal(testVector))),
+                    asDouble(m.solve(asBigDecimal(testVector))),
+                    normTolerance);
+        try {
+            asDouble(m.solve(asBigDecimal(testVector2)));
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+        BigMatrix bs = new BigMatrixImpl(bigSingular);
+        try {
+            bs.solve(bs);
+            fail("Expecting InvalidMatrixException");
+        } catch (InvalidMatrixException ex) {
+            // ignored
+        }
+        try {
+            m.solve(bs);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+        try {
+            new BigMatrixImpl(testData2).solve(bs);
+            fail("Expecting illegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+        try {
+            (new BigMatrixImpl(testData2)).luDecompose();
+            fail("Expecting InvalidMatrixException");
+        } catch (InvalidMatrixException ex) {
+            // ignored
+        }
+    }
+
+    /** test determinant */
+    public void testDeterminant() {
+        BigMatrix m = new BigMatrixImpl(bigSingular);
+        assertEquals("singular determinant",0,m.getDeterminant().doubleValue(),0);
+        m = new BigMatrixImpl(detData);
+        assertEquals("nonsingular test",-3d,m.getDeterminant().doubleValue(),normTolerance);
+
+        // Examples verified against R (version 1.8.1, Red Hat Linux 9)
+        m = new BigMatrixImpl(detData2);
+        assertEquals("nonsingular R test 1",-2d,m.getDeterminant().doubleValue(),normTolerance);
+        m = new BigMatrixImpl(testData);
+        assertEquals("nonsingular  R test 2",-1d,m.getDeterminant().doubleValue(),normTolerance);
+
+        try {
+            double d = new BigMatrixImpl(testData2).getDeterminant().doubleValue();
+            fail("Expecting InvalidMatrixException, got " + d);
+        } catch (InvalidMatrixException ex) {
+            // ignored
+        }
+    }
+
+    /** test trace */
+    public void testTrace() {
+        BigMatrix m = new BigMatrixImpl(id);
+        assertEquals("identity trace",3d,m.getTrace().doubleValue(),entryTolerance);
+        m = new BigMatrixImpl(testData2);
+        try {
+            double t = m.getTrace().doubleValue();
+            fail("Expecting NonSquareMatrixException, got " + t);
+        } catch (NonSquareMatrixException ex) {
+            // ignored
+        }
+    }
+
+    /** test sclarAdd */
+    public void testScalarAdd() {
+        BigMatrix m = new BigMatrixImpl(testData);
+        assertClose("scalar add",new BigMatrixImpl(testDataPlus2),
+            m.scalarAdd(new BigDecimal(2d)),entryTolerance);
+    }
+
+    /** test operate */
+    public void testOperate() {
+        BigMatrix m = new BigMatrixImpl(id);
+        double[] x = asDouble(m.operate(asBigDecimal(testVector)));
+        assertClose("identity operate",testVector,x,entryTolerance);
+        m = new BigMatrixImpl(bigSingular);
+        try {
+            asDouble(m.operate(asBigDecimal(testVector)));
+            fail("Expecting illegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    /** test issue MATH-209 */
+    public void testMath209() {
+        BigMatrix a = new BigMatrixImpl(new BigDecimal[][] {
+                { new BigDecimal(1), new BigDecimal(2) },
+                { new BigDecimal(3), new BigDecimal(4) },
+                { new BigDecimal(5), new BigDecimal(6) }
+        }, false);
+        BigDecimal[] b = a.operate(new BigDecimal[] { new BigDecimal(1), new BigDecimal(1) });
+        assertEquals(a.getRowDimension(), b.length);
+        assertEquals( 3.0, b[0].doubleValue(), 1.0e-12);
+        assertEquals( 7.0, b[1].doubleValue(), 1.0e-12);
+        assertEquals(11.0, b[2].doubleValue(), 1.0e-12);
+    }
+
+    /** test transpose */
+    public void testTranspose() {
+        BigMatrix m = new BigMatrixImpl(testData);
+        assertClose("inverse-transpose",m.inverse().transpose(),
+            m.transpose().inverse(),normTolerance);
+        m = new BigMatrixImpl(testData2);
+        BigMatrix mt = new BigMatrixImpl(testData2T);
+        assertClose("transpose",mt,m.transpose(),normTolerance);
+    }
+
+    /** test preMultiply by vector */
+    public void testPremultiplyVector() {
+        BigMatrix m = new BigMatrixImpl(testData);
+        assertClose("premultiply",asDouble(m.preMultiply(asBigDecimal(testVector))),preMultTest,normTolerance);
+        m = new BigMatrixImpl(bigSingular);
+        try {
+            m.preMultiply(asBigDecimal(testVector));
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    public void testPremultiply() {
+        BigMatrix m3 = new BigMatrixImpl(d3);
+        BigMatrix m4 = new BigMatrixImpl(d4);
+        BigMatrix m5 = new BigMatrixImpl(d5);
+        assertClose("m3*m4=m5", m4.preMultiply(m3), m5, entryTolerance);
+
+        BigMatrixImpl m = new BigMatrixImpl(testData);
+        BigMatrixImpl mInv = new BigMatrixImpl(testDataInv);
+        BigMatrixImpl identity = new BigMatrixImpl(id);
+        new BigMatrixImpl(testData2);
+        assertClose("inverse multiply",m.preMultiply(mInv),
+                identity,entryTolerance);
+        assertClose("inverse multiply",mInv.preMultiply(m),
+                identity,entryTolerance);
+        assertClose("identity multiply",m.preMultiply(identity),
+                m,entryTolerance);
+        assertClose("identity multiply",identity.preMultiply(mInv),
+                mInv,entryTolerance);
+        try {
+            m.preMultiply(new BigMatrixImpl(bigSingular));
+            fail("Expecting illegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    public void testGetVectors() {
+        BigMatrix m = new BigMatrixImpl(testData);
+        assertClose("get row",m.getRowAsDoubleArray(0),testDataRow1,entryTolerance);
+        assertClose("get col",m.getColumnAsDoubleArray(2),testDataCol3,entryTolerance);
+        try {
+            m.getRowAsDoubleArray(10);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // ignored
+        }
+        try {
+            m.getColumnAsDoubleArray(-1);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // ignored
+        }
+    }
+
+    public void testLUDecomposition() throws Exception {
+        BigMatrixImpl m = new BigMatrixImpl(testData);
+        BigMatrix lu = m.getLUMatrix();
+        assertClose("LU decomposition", lu, new BigMatrixImpl(testDataLU), normTolerance);
+        verifyDecomposition(m, lu);
+        m = new BigMatrixImpl(luData);
+        lu = m.getLUMatrix();
+        assertClose("LU decomposition", lu, new BigMatrixImpl(luDataLUDecomposition), normTolerance);
+        verifyDecomposition(m, lu);
+        m = new BigMatrixImpl(testDataMinus);
+        lu = m.getLUMatrix();
+        verifyDecomposition(m, lu);
+        m = new BigMatrixImpl(id);
+        lu = m.getLUMatrix();
+        verifyDecomposition(m, lu);
+        try {
+            m = new BigMatrixImpl(bigSingular); // singular
+            lu = m.getLUMatrix();
+            fail("Expecting InvalidMatrixException");
+        } catch (InvalidMatrixException ex) {
+            // expected
+        }
+        try {
+            m = new BigMatrixImpl(testData2);  // not square
+            lu = m.getLUMatrix();
+            fail("Expecting InvalidMatrixException");
+        } catch (InvalidMatrixException ex) {
+            // expected
+        }
+    }
+
+   /**
+    * test submatrix accessors
+    */
+    public void testSubMatrix() {
+        BigMatrix m = new BigMatrixImpl(subTestData);
+        BigMatrix mRows23Cols00 = new BigMatrixImpl(subRows23Cols00);
+        BigMatrix mRows00Cols33 = new BigMatrixImpl(subRows00Cols33);
+        BigMatrix mRows01Cols23 = new BigMatrixImpl(subRows01Cols23);
+        BigMatrix mRows02Cols13 = new BigMatrixImpl(subRows02Cols13);
+        BigMatrix mRows03Cols12 = new BigMatrixImpl(subRows03Cols12);
+        BigMatrix mRows03Cols123 = new BigMatrixImpl(subRows03Cols123);
+        BigMatrix mRows20Cols123 = new BigMatrixImpl(subRows20Cols123);
+        BigMatrix mRows31Cols31 = new BigMatrixImpl(subRows31Cols31);
+        assertEquals("Rows23Cols00", mRows23Cols00,
+                m.getSubMatrix(2 , 3 , 0, 0));
+        assertEquals("Rows00Cols33", mRows00Cols33,
+                m.getSubMatrix(0 , 0 , 3, 3));
+        assertEquals("Rows01Cols23", mRows01Cols23,
+                m.getSubMatrix(0 , 1 , 2, 3));
+        assertEquals("Rows02Cols13", mRows02Cols13,
+                m.getSubMatrix(new int[] {0,2}, new int[] {1,3}));
+        assertEquals("Rows03Cols12", mRows03Cols12,
+                m.getSubMatrix(new int[] {0,3}, new int[] {1,2}));
+        assertEquals("Rows03Cols123", mRows03Cols123,
+                m.getSubMatrix(new int[] {0,3}, new int[] {1,2,3}));
+        assertEquals("Rows20Cols123", mRows20Cols123,
+                m.getSubMatrix(new int[] {2,0}, new int[] {1,2,3}));
+        assertEquals("Rows31Cols31", mRows31Cols31,
+                m.getSubMatrix(new int[] {3,1}, new int[] {3,1}));
+        assertEquals("Rows31Cols31", mRows31Cols31,
+                m.getSubMatrix(new int[] {3,1}, new int[] {3,1}));
+
+        try {
+            m.getSubMatrix(1,0,2,4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getSubMatrix(-1,1,2,2);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getSubMatrix(1,0,2,2);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getSubMatrix(1,0,2,4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getSubMatrix(new int[] {}, new int[] {0});
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getSubMatrix(new int[] {0}, new int[] {4});
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testGetColumnMatrix() {
+        BigMatrix m = new BigMatrixImpl(subTestData);
+        BigMatrix mColumn1 = new BigMatrixImpl(subColumn1);
+        BigMatrix mColumn3 = new BigMatrixImpl(subColumn3);
+        assertEquals("Column1", mColumn1,
+                m.getColumnMatrix(1));
+        assertEquals("Column3", mColumn3,
+                m.getColumnMatrix(3));
+        try {
+            m.getColumnMatrix(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getColumnMatrix(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testGetRowMatrix() {
+        BigMatrix m = new BigMatrixImpl(subTestData);
+        BigMatrix mRow0 = new BigMatrixImpl(subRow0);
+        BigMatrix mRow3 = new BigMatrixImpl(subRow3);
+        assertEquals("Row0", mRow0,
+                m.getRowMatrix(0));
+        assertEquals("Row3", mRow3,
+                m.getRowMatrix(3));
+        try {
+            m.getRowMatrix(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getRowMatrix(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testEqualsAndHashCode() {
+        BigMatrixImpl m = new BigMatrixImpl(testData);
+        BigMatrixImpl m1 = (BigMatrixImpl) m.copy();
+        BigMatrixImpl mt = (BigMatrixImpl) m.transpose();
+        assertTrue(m.hashCode() != mt.hashCode());
+        assertEquals(m.hashCode(), m1.hashCode());
+        assertEquals(m, m);
+        assertEquals(m, m1);
+        assertFalse(m.equals(null));
+        assertFalse(m.equals(mt));
+        assertFalse(m.equals(new BigMatrixImpl(bigSingular)));
+        // Different scales make BigDecimals, so matrices unequal
+        m = new BigMatrixImpl(new String[][] {{"2.0"}});
+        m1 = new BigMatrixImpl(new String[][] {{"2.00"}});
+        assertTrue(m.hashCode() != m1.hashCode());
+        assertFalse(m.equals(m1));
+    }
+
+    public void testToString() {
+        BigMatrixImpl m = new BigMatrixImpl(testData);
+        assertEquals("BigMatrixImpl{{1,2,3},{2,5,3},{1,0,8}}",
+                m.toString());
+        m = new BigMatrixImpl();
+        assertEquals("BigMatrixImpl{}",
+                m.toString());
+    }
+
+    public void testSetSubMatrix() throws Exception {
+        BigDecimal[][] detData3 =
+            MatrixUtils.createBigMatrix(detData2).getData();
+        BigMatrixImpl m = new BigMatrixImpl(testData);
+        m.setSubMatrix(detData3,1,1);
+        BigMatrix expected = MatrixUtils.createBigMatrix
+            (new double[][] {{1.0,2.0,3.0},{2.0,1.0,3.0},{1.0,2.0,4.0}});
+        assertEquals(expected, m);
+
+        m.setSubMatrix(detData3,0,0);
+        expected = MatrixUtils.createBigMatrix
+            (new double[][] {{1.0,3.0,3.0},{2.0,4.0,3.0},{1.0,2.0,4.0}});
+        assertEquals(expected, m);
+
+        BigDecimal[][] testDataPlus3 =
+            MatrixUtils.createBigMatrix(testDataPlus2).getData();
+        m.setSubMatrix(testDataPlus3,0,0);
+        expected = MatrixUtils.createBigMatrix
+        (new double[][] {{3.0,4.0,5.0},{4.0,7.0,5.0},{3.0,2.0,10.0}});
+        assertEquals(expected, m);
+
+        // javadoc example
+        BigMatrixImpl matrix = (BigMatrixImpl) MatrixUtils.createBigMatrix
+            (new double[][] {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 0, 1 , 2}});
+        matrix.setSubMatrix(new BigDecimal[][] {{new BigDecimal(3),
+            new BigDecimal(4)}, {new BigDecimal(5), new BigDecimal(6)}}, 1, 1);
+        expected = MatrixUtils.createBigMatrix
+            (new BigDecimal[][] {{new BigDecimal(1), new BigDecimal(2),
+             new BigDecimal(3), new BigDecimal(4)}, {new BigDecimal(5),
+             new BigDecimal(3), new BigDecimal(4), new BigDecimal(8)},
+             {new BigDecimal(9), new BigDecimal(5) , new BigDecimal(6),
+              new BigDecimal(2)}});
+        assertEquals(expected, matrix);
+
+        // dimension overflow
+        try {
+            m.setSubMatrix(matrix.getData(),1,1);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException e) {
+            // expected
+        }
+
+        // null
+        try {
+            m.setSubMatrix(null,1,1);
+            fail("expecting NullPointerException");
+        } catch (NullPointerException e) {
+            // expected
+        }
+
+        // ragged
+        try {
+            m.setSubMatrix(new BigDecimal[][] {{new BigDecimal(1)},
+                    {new BigDecimal(2), new BigDecimal(3)}}, 0, 0);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        // empty
+        try {
+            m.setSubMatrix(new BigDecimal[][] {{}}, 0, 0);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+    }
+
+    //--------------- -----------------Protected methods
+
+    /** verifies that two matrices are close (1-norm) */
+    protected void assertClose(String msg, BigMatrix m, BigMatrix n,
+        double tolerance) {
+        assertTrue(msg,m.subtract(n).getNorm().doubleValue() < tolerance);
+    }
+
+    /** verifies that two vectors are close (sup norm) */
+    protected void assertClose(String msg, double[] m, double[] n,
+        double tolerance) {
+        if (m.length != n.length) {
+            fail("vectors not same length");
+        }
+        for (int i = 0; i < m.length; i++) {
+            assertEquals(msg + " " +  i + " elements differ",
+                m[i],n[i],tolerance);
+        }
+    }
+
+    /** extracts the l  and u matrices from compact lu representation */
+    protected void splitLU(BigMatrix lu, BigDecimal[][] lowerData, BigDecimal[][] upperData) throws InvalidMatrixException {
+        if (!lu.isSquare() || lowerData.length != lowerData[0].length || upperData.length != upperData[0].length ||
+                lowerData.length != upperData.length
+                || lowerData.length != lu.getRowDimension()) {
+            throw new InvalidMatrixException("incorrect dimensions");
+        }
+        int n = lu.getRowDimension();
+        for (int i = 0; i < n; i++) {
+            for (int j = 0; j < n; j++) {
+                if (j < i) {
+                    lowerData[i][j] = lu.getEntry(i, j);
+                    upperData[i][j] = new BigDecimal(0);
+                } else if (i == j) {
+                    lowerData[i][j] = new BigDecimal(1);
+                    upperData[i][j] = lu.getEntry(i, j);
+                } else {
+                    lowerData[i][j] = new BigDecimal(0);
+                    upperData[i][j] = lu.getEntry(i, j);
+                }
+            }
+        }
+    }
+
+    /** Returns the result of applying the given row permutation to the matrix */
+    protected BigMatrix permuteRows(BigMatrix matrix, int[] permutation) {
+        if (!matrix.isSquare() || matrix.getRowDimension() != permutation.length) {
+            throw new IllegalArgumentException("dimension mismatch");
+        }
+        int n = matrix.getRowDimension();
+        int m = matrix.getColumnDimension();
+        BigDecimal out[][] = new BigDecimal[m][n];
+        for (int i = 0; i < n; i++) {
+            for (int j = 0; j < m; j++) {
+                out[i][j] = matrix.getEntry(permutation[i], j);
+            }
+        }
+        return new BigMatrixImpl(out);
+    }
+
+    /** Extracts l and u matrices from lu and verifies that matrix = l times u modulo permutation */
+    protected void verifyDecomposition(BigMatrix matrix, BigMatrix lu) throws Exception{
+        int n = matrix.getRowDimension();
+        BigDecimal[][] lowerData = new BigDecimal[n][n];
+        BigDecimal[][] upperData = new BigDecimal[n][n];
+        splitLU(lu, lowerData, upperData);
+        BigMatrix lower =new BigMatrixImpl(lowerData);
+        BigMatrix upper = new BigMatrixImpl(upperData);
+        int[] permutation = ((BigMatrixImpl) matrix).getPermutation();
+        BigMatrix permuted = permuteRows(matrix, permutation);
+        assertClose("lu decomposition does not work", permuted,
+                lower.multiply(upper), normTolerance);
+    }
+
+//    /** Useful for debugging */
+//    private void dumpMatrix(BigMatrix m) {
+//          for (int i = 0; i < m.getRowDimension(); i++) {
+//              String os = "";
+//              for (int j = 0; j < m.getColumnDimension(); j++) {
+//                  os += m.getEntry(i, j) + " ";
+//              }
+//              System.out.println(os);
+//          }
+//    }
+
+}
+
diff --git a/src/test/java/org/apache/commons/math/linear/BlockFieldMatrixTest.java b/src/test/java/org/apache/commons/math/linear/BlockFieldMatrixTest.java
new file mode 100644
index 0000000..e7e4fd0
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/linear/BlockFieldMatrixTest.java
@@ -0,0 +1,1288 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 java.util.Random;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.linear.MatrixVisitorException;
+import org.apache.commons.math.fraction.Fraction;
+import org.apache.commons.math.fraction.FractionField;
+
+/**
+ * Test cases for the {@link BlockFieldMatrix} class.
+ *
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ */
+
+public final class BlockFieldMatrixTest extends TestCase {
+
+    // 3 x 3 identity matrix
+    protected Fraction[][] id = {
+            {new Fraction(1),new Fraction(0),new Fraction(0)},
+            {new Fraction(0),new Fraction(1),new Fraction(0)},
+            {new Fraction(0),new Fraction(0),new Fraction(1)}
+    };
+
+    // Test data for group operations
+    protected Fraction[][] testData = {
+            {new Fraction(1),new Fraction(2),new Fraction(3)},
+            {new Fraction(2),new Fraction(5),new Fraction(3)},
+            {new Fraction(1),new Fraction(0),new Fraction(8)}
+    };
+    protected Fraction[][] testDataLU = {
+            {new Fraction(2), new Fraction(5), new Fraction(3)},
+            {new Fraction(1, 2), new Fraction(-5, 2), new Fraction(13, 2)},
+            {new Fraction(1, 2), new Fraction(1, 5), new Fraction(1, 5)}
+    };
+    protected Fraction[][] testDataPlus2 = {
+            {new Fraction(3),new Fraction(4),new Fraction(5)},
+            {new Fraction(4),new Fraction(7),new Fraction(5)},
+            {new Fraction(3),new Fraction(2),new Fraction(10)}
+    };
+    protected Fraction[][] testDataMinus = {
+            {new Fraction(-1),new Fraction(-2),new Fraction(-3)},
+            {new Fraction(-2),new Fraction(-5),new Fraction(-3)},
+            {new Fraction(-1),new Fraction(0),new Fraction(-8)}
+    };
+    protected Fraction[] testDataRow1 = {new Fraction(1),new Fraction(2),new Fraction(3)};
+    protected Fraction[] testDataCol3 = {new Fraction(3),new Fraction(3),new Fraction(8)};
+    protected Fraction[][] testDataInv = {
+            {new Fraction(-40),new Fraction(16),new Fraction(9)},
+            {new Fraction(13),new Fraction(-5),new Fraction(-3)},
+            {new Fraction(5),new Fraction(-2),new Fraction(-1)}
+    };
+    protected Fraction[] preMultTest = {new Fraction(8), new Fraction(12), new Fraction(33)};
+    protected Fraction[][] testData2 = {
+            {new Fraction(1),new Fraction(2),new Fraction(3)},
+            {new Fraction(2),new Fraction(5),new Fraction(3)}
+    };
+    protected Fraction[][] testData2T = {
+            {new Fraction(1),new Fraction(2)},
+            {new Fraction(2),new Fraction(5)},
+            {new Fraction(3),new Fraction(3)}
+    };
+    protected Fraction[][] testDataPlusInv = {
+            {new Fraction(-39),new Fraction(18),new Fraction(12)},
+            {new Fraction(15),new Fraction(0),new Fraction(0)},
+            {new Fraction(6),new Fraction(-2),new Fraction(7)}
+    };
+
+    // lu decomposition tests
+    protected Fraction[][] luData = {
+            {new Fraction(2),new Fraction(3),new Fraction(3)},
+            {new Fraction(0),new Fraction(5),new Fraction(7)},
+            {new Fraction(6),new Fraction(9),new Fraction(8)}
+    };
+    protected Fraction[][] luDataLUDecomposition = {
+            {new Fraction(6),new Fraction(9),new Fraction(8)},
+            {new Fraction(0),new Fraction(5),new Fraction(7)},
+            {new Fraction(1, 3),new Fraction(0),new Fraction(1, 3)}
+    };
+
+    // singular matrices
+    protected Fraction[][] singular = { {new Fraction(2),new Fraction(3)}, {new Fraction(2),new Fraction(3)} };
+    protected Fraction[][] bigSingular = {
+            {new Fraction(1),new Fraction(2),new Fraction(3),new Fraction(4)},
+            {new Fraction(2),new Fraction(5),new Fraction(3),new Fraction(4)},
+            {new Fraction(7),new Fraction(3),new Fraction(256),new Fraction(1930)},
+            {new Fraction(3),new Fraction(7),new Fraction(6),new Fraction(8)}
+    }; // 4th row = 1st + 2nd
+    protected Fraction[][] detData = {
+            {new Fraction(1),new Fraction(2),new Fraction(3)},
+            {new Fraction(4),new Fraction(5),new Fraction(6)},
+            {new Fraction(7),new Fraction(8),new Fraction(10)}
+    };
+    protected Fraction[][] detData2 = { {new Fraction(1), new Fraction(3)}, {new Fraction(2), new Fraction(4)}};
+
+    // vectors
+    protected Fraction[] testVector = {new Fraction(1),new Fraction(2),new Fraction(3)};
+    protected Fraction[] testVector2 = {new Fraction(1),new Fraction(2),new Fraction(3),new Fraction(4)};
+
+    // submatrix accessor tests
+    protected Fraction[][] subTestData = {
+            {new Fraction(1), new Fraction(2), new Fraction(3), new Fraction(4)},
+            {new Fraction(3, 2), new Fraction(5, 2), new Fraction(7, 2), new Fraction(9, 2)},
+            {new Fraction(2), new Fraction(4), new Fraction(6), new Fraction(8)},
+            {new Fraction(4), new Fraction(5), new Fraction(6), new Fraction(7)}
+    };
+    // array selections
+    protected Fraction[][] subRows02Cols13 = { {new Fraction(2), new Fraction(4)}, {new Fraction(4), new Fraction(8)}};
+    protected Fraction[][] subRows03Cols12 = { {new Fraction(2), new Fraction(3)}, {new Fraction(5), new Fraction(6)}};
+    protected Fraction[][] subRows03Cols123 = {
+            {new Fraction(2), new Fraction(3), new Fraction(4)},
+            {new Fraction(5), new Fraction(6), new Fraction(7)}
+    };
+    // effective permutations
+    protected Fraction[][] subRows20Cols123 = {
+            {new Fraction(4), new Fraction(6), new Fraction(8)},
+            {new Fraction(2), new Fraction(3), new Fraction(4)}
+    };
+    protected Fraction[][] subRows31Cols31 = {{new Fraction(7), new Fraction(5)}, {new Fraction(9, 2), new Fraction(5, 2)}};
+    // contiguous ranges
+    protected Fraction[][] subRows01Cols23 = {{new Fraction(3),new Fraction(4)} , {new Fraction(7, 2), new Fraction(9, 2)}};
+    protected Fraction[][] subRows23Cols00 = {{new Fraction(2)} , {new Fraction(4)}};
+    protected Fraction[][] subRows00Cols33 = {{new Fraction(4)}};
+    // row matrices
+    protected Fraction[][] subRow0 = {{new Fraction(1),new Fraction(2),new Fraction(3),new Fraction(4)}};
+    protected Fraction[][] subRow3 = {{new Fraction(4),new Fraction(5),new Fraction(6),new Fraction(7)}};
+    // column matrices
+    protected Fraction[][] subColumn1 = {{new Fraction(2)}, {new Fraction(5, 2)}, {new Fraction(4)}, {new Fraction(5)}};
+    protected Fraction[][] subColumn3 = {{new Fraction(4)}, {new Fraction(9, 2)}, {new Fraction(8)}, {new Fraction(7)}};
+
+    // tolerances
+    protected double entryTolerance = 10E-16;
+    protected double normTolerance = 10E-14;
+
+    public BlockFieldMatrixTest(String name) {
+        super(name);
+    }
+
+    /** test dimensions */
+    public void testDimensions() {
+        BlockFieldMatrix<Fraction> m = new BlockFieldMatrix<Fraction>(testData);
+        BlockFieldMatrix<Fraction> m2 = new BlockFieldMatrix<Fraction>(testData2);
+        assertEquals("testData row dimension",3,m.getRowDimension());
+        assertEquals("testData column dimension",3,m.getColumnDimension());
+        assertTrue("testData is square",m.isSquare());
+        assertEquals("testData2 row dimension",m2.getRowDimension(),2);
+        assertEquals("testData2 column dimension",m2.getColumnDimension(),3);
+        assertTrue("testData2 is not square",!m2.isSquare());
+    }
+
+    /** test copy functions */
+    public void testCopyFunctions() {
+        Random r = new Random(66636328996002l);
+        BlockFieldMatrix<Fraction> m1 = createRandomMatrix(r, 47, 83);
+        BlockFieldMatrix<Fraction> m2 = new BlockFieldMatrix<Fraction>(m1.getData());
+        assertEquals(m1, m2);
+        BlockFieldMatrix<Fraction> m3 = new BlockFieldMatrix<Fraction>(testData);
+        BlockFieldMatrix<Fraction> m4 = new BlockFieldMatrix<Fraction>(m3.getData());
+        assertEquals(m3, m4);
+    }
+
+    /** test add */
+    public void testAdd() {
+        BlockFieldMatrix<Fraction> m = new BlockFieldMatrix<Fraction>(testData);
+        BlockFieldMatrix<Fraction> mInv = new BlockFieldMatrix<Fraction>(testDataInv);
+        FieldMatrix<Fraction> mPlusMInv = m.add(mInv);
+        Fraction[][] sumEntries = mPlusMInv.getData();
+        for (int row = 0; row < m.getRowDimension(); row++) {
+            for (int col = 0; col < m.getColumnDimension(); col++) {
+                assertEquals(testDataPlusInv[row][col],sumEntries[row][col]);
+            }
+        }
+    }
+
+    /** test add failure */
+    public void testAddFail() {
+        BlockFieldMatrix<Fraction> m = new BlockFieldMatrix<Fraction>(testData);
+        BlockFieldMatrix<Fraction> m2 = new BlockFieldMatrix<Fraction>(testData2);
+        try {
+            m.add(m2);
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+     /** test m-n = m + -n */
+    public void testPlusMinus() {
+        BlockFieldMatrix<Fraction> m = new BlockFieldMatrix<Fraction>(testData);
+        BlockFieldMatrix<Fraction> m2 = new BlockFieldMatrix<Fraction>(testDataInv);
+        TestUtils.assertEquals(m.subtract(m2), m2.scalarMultiply(new Fraction(-1)).add(m));
+        try {
+            m.subtract(new BlockFieldMatrix<Fraction>(testData2));
+            fail("Expecting illegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    /** test multiply */
+     public void testMultiply() {
+        BlockFieldMatrix<Fraction> m = new BlockFieldMatrix<Fraction>(testData);
+        BlockFieldMatrix<Fraction> mInv = new BlockFieldMatrix<Fraction>(testDataInv);
+        BlockFieldMatrix<Fraction> identity = new BlockFieldMatrix<Fraction>(id);
+        BlockFieldMatrix<Fraction> m2 = new BlockFieldMatrix<Fraction>(testData2);
+        TestUtils.assertEquals(m.multiply(mInv), identity);
+        TestUtils.assertEquals(mInv.multiply(m), identity);
+        TestUtils.assertEquals(m.multiply(identity), m);
+        TestUtils.assertEquals(identity.multiply(mInv), mInv);
+        TestUtils.assertEquals(m2.multiply(identity), m2);
+        try {
+            m.multiply(new BlockFieldMatrix<Fraction>(bigSingular));
+            fail("Expecting illegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+
+    public void testSeveralBlocks() {
+
+        FieldMatrix<Fraction> m =
+            new BlockFieldMatrix<Fraction>(FractionField.getInstance(), 37, 41);
+        for (int i = 0; i < m.getRowDimension(); ++i) {
+            for (int j = 0; j < m.getColumnDimension(); ++j) {
+                m.setEntry(i, j, new Fraction(i * 11 + j, 11));
+            }
+        }
+
+        FieldMatrix<Fraction> mT = m.transpose();
+        assertEquals(m.getRowDimension(), mT.getColumnDimension());
+        assertEquals(m.getColumnDimension(), mT.getRowDimension());
+        for (int i = 0; i < mT.getRowDimension(); ++i) {
+            for (int j = 0; j < mT.getColumnDimension(); ++j) {
+                assertEquals(m.getEntry(j, i), mT.getEntry(i, j));
+            }
+        }
+
+        FieldMatrix<Fraction> mPm = m.add(m);
+        for (int i = 0; i < mPm.getRowDimension(); ++i) {
+            for (int j = 0; j < mPm.getColumnDimension(); ++j) {
+                assertEquals(m.getEntry(i, j).multiply(new Fraction(2)), mPm.getEntry(i, j));
+            }
+        }
+
+        FieldMatrix<Fraction> mPmMm = mPm.subtract(m);
+        for (int i = 0; i < mPmMm.getRowDimension(); ++i) {
+            for (int j = 0; j < mPmMm.getColumnDimension(); ++j) {
+                assertEquals(m.getEntry(i, j), mPmMm.getEntry(i, j));
+            }
+        }
+
+        FieldMatrix<Fraction> mTm = mT.multiply(m);
+        for (int i = 0; i < mTm.getRowDimension(); ++i) {
+            for (int j = 0; j < mTm.getColumnDimension(); ++j) {
+                Fraction sum = Fraction.ZERO;
+                for (int k = 0; k < mT.getColumnDimension(); ++k) {
+                    sum = sum.add(new Fraction(k * 11 + i, 11).multiply(new Fraction(k * 11 + j, 11)));
+                }
+                assertEquals(sum, mTm.getEntry(i, j));
+            }
+        }
+
+        FieldMatrix<Fraction> mmT = m.multiply(mT);
+        for (int i = 0; i < mmT.getRowDimension(); ++i) {
+            for (int j = 0; j < mmT.getColumnDimension(); ++j) {
+                Fraction sum = Fraction.ZERO;
+                for (int k = 0; k < m.getColumnDimension(); ++k) {
+                    sum = sum.add(new Fraction(i * 11 + k, 11).multiply(new Fraction(j * 11 + k, 11)));
+                }
+                assertEquals(sum, mmT.getEntry(i, j));
+            }
+        }
+
+        FieldMatrix<Fraction> sub1 = m.getSubMatrix(2, 9, 5, 20);
+        for (int i = 0; i < sub1.getRowDimension(); ++i) {
+            for (int j = 0; j < sub1.getColumnDimension(); ++j) {
+                assertEquals(new Fraction((i + 2) * 11 + (j + 5), 11), sub1.getEntry(i, j));
+            }
+        }
+
+        FieldMatrix<Fraction> sub2 = m.getSubMatrix(10, 12, 3, 40);
+        for (int i = 0; i < sub2.getRowDimension(); ++i) {
+            for (int j = 0; j < sub2.getColumnDimension(); ++j) {
+                assertEquals(new Fraction((i + 10) * 11 + (j + 3), 11), sub2.getEntry(i, j));
+            }
+        }
+
+        FieldMatrix<Fraction> sub3 = m.getSubMatrix(30, 34, 0, 5);
+        for (int i = 0; i < sub3.getRowDimension(); ++i) {
+            for (int j = 0; j < sub3.getColumnDimension(); ++j) {
+                assertEquals(new Fraction((i + 30) * 11 + (j + 0), 11), sub3.getEntry(i, j));
+            }
+        }
+
+        FieldMatrix<Fraction> sub4 = m.getSubMatrix(30, 32, 32, 35);
+        for (int i = 0; i < sub4.getRowDimension(); ++i) {
+            for (int j = 0; j < sub4.getColumnDimension(); ++j) {
+                assertEquals(new Fraction((i + 30) * 11 + (j + 32), 11), sub4.getEntry(i, j));
+            }
+        }
+
+    }
+
+    //Additional Test for BlockFieldMatrix<Fraction>Test.testMultiply
+
+    private Fraction[][] d3 = new Fraction[][] {
+            {new Fraction(1),new Fraction(2),new Fraction(3),new Fraction(4)},
+            {new Fraction(5),new Fraction(6),new Fraction(7),new Fraction(8)}
+    };
+    private Fraction[][] d4 = new Fraction[][] {
+            {new Fraction(1)},
+            {new Fraction(2)},
+            {new Fraction(3)},
+            {new Fraction(4)}
+    };
+    private Fraction[][] d5 = new Fraction[][] {{new Fraction(30)},{new Fraction(70)}};
+
+    public void testMultiply2() {
+       FieldMatrix<Fraction> m3 = new BlockFieldMatrix<Fraction>(d3);
+       FieldMatrix<Fraction> m4 = new BlockFieldMatrix<Fraction>(d4);
+       FieldMatrix<Fraction> m5 = new BlockFieldMatrix<Fraction>(d5);
+       TestUtils.assertEquals(m3.multiply(m4), m5);
+   }
+
+    /** test trace */
+    public void testTrace() {
+        FieldMatrix<Fraction> m = new BlockFieldMatrix<Fraction>(id);
+        assertEquals(new Fraction(3),m.getTrace());
+        m = new BlockFieldMatrix<Fraction>(testData2);
+        try {
+            m.getTrace();
+            fail("Expecting NonSquareMatrixException");
+        } catch (NonSquareMatrixException ex) {
+            // ignored
+        }
+    }
+
+    /** test scalarAdd */
+    public void testScalarAdd() {
+        FieldMatrix<Fraction> m = new BlockFieldMatrix<Fraction>(testData);
+        TestUtils.assertEquals(new BlockFieldMatrix<Fraction>(testDataPlus2),
+                               m.scalarAdd(new Fraction(2)));
+    }
+
+    /** test operate */
+    public void testOperate() {
+        FieldMatrix<Fraction> m = new BlockFieldMatrix<Fraction>(id);
+        TestUtils.assertEquals(testVector, m.operate(testVector));
+        TestUtils.assertEquals(testVector, m.operate(new ArrayFieldVector<Fraction>(testVector)).getData());
+        m = new BlockFieldMatrix<Fraction>(bigSingular);
+        try {
+            m.operate(testVector);
+            fail("Expecting illegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    public void testOperateLarge() {
+        int p = (11 * BlockFieldMatrix.BLOCK_SIZE) / 10;
+        int q = (11 * BlockFieldMatrix.BLOCK_SIZE) / 10;
+        int r =  BlockFieldMatrix.BLOCK_SIZE / 2;
+        Random random = new Random(111007463902334l);
+        FieldMatrix<Fraction> m1 = createRandomMatrix(random, p, q);
+        FieldMatrix<Fraction> m2 = createRandomMatrix(random, q, r);
+        FieldMatrix<Fraction> m1m2 = m1.multiply(m2);
+        for (int i = 0; i < r; ++i) {
+            TestUtils.assertEquals(m1m2.getColumn(i), m1.operate(m2.getColumn(i)));
+        }
+    }
+
+    public void testOperatePremultiplyLarge() {
+        int p = (11 * BlockFieldMatrix.BLOCK_SIZE) / 10;
+        int q = (11 * BlockFieldMatrix.BLOCK_SIZE) / 10;
+        int r =  BlockFieldMatrix.BLOCK_SIZE / 2;
+        Random random = new Random(111007463902334l);
+        FieldMatrix<Fraction> m1 = createRandomMatrix(random, p, q);
+        FieldMatrix<Fraction> m2 = createRandomMatrix(random, q, r);
+        FieldMatrix<Fraction> m1m2 = m1.multiply(m2);
+        for (int i = 0; i < p; ++i) {
+            TestUtils.assertEquals(m1m2.getRow(i), m2.preMultiply(m1.getRow(i)));
+        }
+    }
+
+    /** test issue MATH-209 */
+    public void testMath209() {
+        FieldMatrix<Fraction> a = new BlockFieldMatrix<Fraction>(new Fraction[][] {
+                { new Fraction(1), new Fraction(2) },
+                { new Fraction(3), new Fraction(4) },
+                { new Fraction(5), new Fraction(6) }
+        });
+        Fraction[] b = a.operate(new Fraction[] { new Fraction(1), new Fraction(1) });
+        assertEquals(a.getRowDimension(), b.length);
+        assertEquals( new Fraction(3), b[0]);
+        assertEquals( new Fraction(7), b[1]);
+        assertEquals(new Fraction(11), b[2]);
+    }
+
+    /** test transpose */
+    public void testTranspose() {
+        FieldMatrix<Fraction> m = new BlockFieldMatrix<Fraction>(testData);
+        FieldMatrix<Fraction> mIT = new FieldLUDecompositionImpl<Fraction>(m).getSolver().getInverse().transpose();
+        FieldMatrix<Fraction> mTI = new FieldLUDecompositionImpl<Fraction>(m.transpose()).getSolver().getInverse();
+        TestUtils.assertEquals(mIT, mTI);
+        m = new BlockFieldMatrix<Fraction>(testData2);
+        FieldMatrix<Fraction> mt = new BlockFieldMatrix<Fraction>(testData2T);
+        TestUtils.assertEquals(mt, m.transpose());
+    }
+
+    /** test preMultiply by vector */
+    public void testPremultiplyVector() {
+        FieldMatrix<Fraction> m = new BlockFieldMatrix<Fraction>(testData);
+        TestUtils.assertEquals(m.preMultiply(testVector), preMultTest);
+        TestUtils.assertEquals(m.preMultiply(new ArrayFieldVector<Fraction>(testVector).getData()),
+                               preMultTest);
+        m = new BlockFieldMatrix<Fraction>(bigSingular);
+        try {
+            m.preMultiply(testVector);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    public void testPremultiply() {
+        FieldMatrix<Fraction> m3 = new BlockFieldMatrix<Fraction>(d3);
+        FieldMatrix<Fraction> m4 = new BlockFieldMatrix<Fraction>(d4);
+        FieldMatrix<Fraction> m5 = new BlockFieldMatrix<Fraction>(d5);
+        TestUtils.assertEquals(m4.preMultiply(m3), m5);
+
+        BlockFieldMatrix<Fraction> m = new BlockFieldMatrix<Fraction>(testData);
+        BlockFieldMatrix<Fraction> mInv = new BlockFieldMatrix<Fraction>(testDataInv);
+        BlockFieldMatrix<Fraction> identity = new BlockFieldMatrix<Fraction>(id);
+        TestUtils.assertEquals(m.preMultiply(mInv), identity);
+        TestUtils.assertEquals(mInv.preMultiply(m), identity);
+        TestUtils.assertEquals(m.preMultiply(identity), m);
+        TestUtils.assertEquals(identity.preMultiply(mInv), mInv);
+        try {
+            m.preMultiply(new BlockFieldMatrix<Fraction>(bigSingular));
+            fail("Expecting illegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    public void testGetVectors() {
+        FieldMatrix<Fraction> m = new BlockFieldMatrix<Fraction>(testData);
+        TestUtils.assertEquals(m.getRow(0), testDataRow1);
+        TestUtils.assertEquals(m.getColumn(2), testDataCol3);
+        try {
+            m.getRow(10);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // ignored
+        }
+        try {
+            m.getColumn(-1);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // ignored
+        }
+    }
+
+    public void testGetEntry() {
+        FieldMatrix<Fraction> m = new BlockFieldMatrix<Fraction>(testData);
+        assertEquals(m.getEntry(0,1),new Fraction(2));
+        try {
+            m.getEntry(10, 4);
+            fail ("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    /** test examples in user guide */
+    public void testExamples() {
+        // Create a real matrix with two rows and three columns
+        Fraction[][] matrixData = {
+                {new Fraction(1),new Fraction(2),new Fraction(3)},
+                {new Fraction(2),new Fraction(5),new Fraction(3)}
+        };
+        FieldMatrix<Fraction> m = new BlockFieldMatrix<Fraction>(matrixData);
+        // One more with three rows, two columns
+        Fraction[][] matrixData2 = {
+                {new Fraction(1),new Fraction(2)},
+                {new Fraction(2),new Fraction(5)},
+                {new Fraction(1), new Fraction(7)}
+        };
+        FieldMatrix<Fraction> n = new BlockFieldMatrix<Fraction>(matrixData2);
+        // Now multiply m by n
+        FieldMatrix<Fraction> p = m.multiply(n);
+        assertEquals(2, p.getRowDimension());
+        assertEquals(2, p.getColumnDimension());
+        // Invert p
+        FieldMatrix<Fraction> pInverse = new FieldLUDecompositionImpl<Fraction>(p).getSolver().getInverse();
+        assertEquals(2, pInverse.getRowDimension());
+        assertEquals(2, pInverse.getColumnDimension());
+
+        // Solve example
+        Fraction[][] coefficientsData = {
+                {new Fraction(2), new Fraction(3), new Fraction(-2)},
+                {new Fraction(-1), new Fraction(7), new Fraction(6)},
+                {new Fraction(4), new Fraction(-3), new Fraction(-5)}
+        };
+        FieldMatrix<Fraction> coefficients = new BlockFieldMatrix<Fraction>(coefficientsData);
+        Fraction[] constants = {new Fraction(1), new Fraction(-2), new Fraction(1)};
+        Fraction[] solution = new FieldLUDecompositionImpl<Fraction>(coefficients).getSolver().solve(constants);
+        assertEquals(new Fraction(2).multiply(solution[0]).
+                     add(new Fraction(3).multiply(solution[1])).
+                     subtract(new Fraction(2).multiply(solution[2])),
+                     constants[0]);
+        assertEquals(new Fraction(-1).multiply(solution[0]).
+                     add(new Fraction(7).multiply(solution[1])).
+                     add(new Fraction(6).multiply(solution[2])),
+                     constants[1]);
+        assertEquals(new Fraction(4).multiply(solution[0]).
+                     subtract(new Fraction(3).multiply(solution[1])).
+                     subtract(new Fraction(5).multiply(solution[2])),
+                     constants[2]);
+
+    }
+
+    // test submatrix accessors
+    public void testGetSubMatrix() {
+        FieldMatrix<Fraction> m = new BlockFieldMatrix<Fraction>(subTestData);
+        checkGetSubMatrix(m, subRows23Cols00,  2 , 3 , 0, 0);
+        checkGetSubMatrix(m, subRows00Cols33,  0 , 0 , 3, 3);
+        checkGetSubMatrix(m, subRows01Cols23,  0 , 1 , 2, 3);
+        checkGetSubMatrix(m, subRows02Cols13,  new int[] { 0, 2 }, new int[] { 1, 3 });
+        checkGetSubMatrix(m, subRows03Cols12,  new int[] { 0, 3 }, new int[] { 1, 2 });
+        checkGetSubMatrix(m, subRows03Cols123, new int[] { 0, 3 }, new int[] { 1, 2, 3 });
+        checkGetSubMatrix(m, subRows20Cols123, new int[] { 2, 0 }, new int[] { 1, 2, 3 });
+        checkGetSubMatrix(m, subRows31Cols31,  new int[] { 3, 1 }, new int[] { 3, 1 });
+        checkGetSubMatrix(m, subRows31Cols31,  new int[] { 3, 1 }, new int[] { 3, 1 });
+        checkGetSubMatrix(m, null,  1, 0, 2, 4);
+        checkGetSubMatrix(m, null, -1, 1, 2, 2);
+        checkGetSubMatrix(m, null,  1, 0, 2, 2);
+        checkGetSubMatrix(m, null,  1, 0, 2, 4);
+        checkGetSubMatrix(m, null, new int[] {},    new int[] { 0 });
+        checkGetSubMatrix(m, null, new int[] { 0 }, new int[] { 4 });
+    }
+
+    private void checkGetSubMatrix(FieldMatrix<Fraction> m, Fraction[][] reference,
+                                   int startRow, int endRow, int startColumn, int endColumn) {
+        try {
+            FieldMatrix<Fraction> sub = m.getSubMatrix(startRow, endRow, startColumn, endColumn);
+            if (reference != null) {
+                assertEquals(new BlockFieldMatrix<Fraction>(reference), sub);
+            } else {
+                fail("Expecting MatrixIndexException");
+            }
+        } catch (MatrixIndexException e) {
+            if (reference != null) {
+                throw e;
+            }
+        }
+    }
+
+    private void checkGetSubMatrix(FieldMatrix<Fraction> m, Fraction[][] reference,
+                                   int[] selectedRows, int[] selectedColumns) {
+        try {
+            FieldMatrix<Fraction> sub = m.getSubMatrix(selectedRows, selectedColumns);
+            if (reference != null) {
+                assertEquals(new BlockFieldMatrix<Fraction>(reference), sub);
+            } else {
+                fail("Expecting MatrixIndexException");
+            }
+        } catch (MatrixIndexException e) {
+            if (reference != null) {
+                throw e;
+            }
+        }
+    }
+
+    public void testGetSetMatrixLarge() {
+        int n = 3 * BlockFieldMatrix.BLOCK_SIZE;
+        FieldMatrix<Fraction> m =
+            new BlockFieldMatrix<Fraction>(FractionField.getInstance(), n, n);
+        FieldMatrix<Fraction> sub =
+            new BlockFieldMatrix<Fraction>(FractionField.getInstance(), n - 4, n - 4).scalarAdd(new Fraction(1));
+
+        m.setSubMatrix(sub.getData(), 2, 2);
+        for (int i = 0; i < n; ++i) {
+            for (int j = 0; j < n; ++j) {
+                if ((i < 2) || (i > n - 3) || (j < 2) || (j > n - 3)) {
+                    assertEquals(new Fraction(0), m.getEntry(i, j));
+                } else {
+                    assertEquals(new Fraction(1), m.getEntry(i, j));
+                }
+            }
+        }
+        assertEquals(sub, m.getSubMatrix(2, n - 3, 2, n - 3));
+
+    }
+
+    public void testCopySubMatrix() {
+        FieldMatrix<Fraction> m = new BlockFieldMatrix<Fraction>(subTestData);
+        checkCopy(m, subRows23Cols00,  2 , 3 , 0, 0);
+        checkCopy(m, subRows00Cols33,  0 , 0 , 3, 3);
+        checkCopy(m, subRows01Cols23,  0 , 1 , 2, 3);
+        checkCopy(m, subRows02Cols13,  new int[] { 0, 2 }, new int[] { 1, 3 });
+        checkCopy(m, subRows03Cols12,  new int[] { 0, 3 }, new int[] { 1, 2 });
+        checkCopy(m, subRows03Cols123, new int[] { 0, 3 }, new int[] { 1, 2, 3 });
+        checkCopy(m, subRows20Cols123, new int[] { 2, 0 }, new int[] { 1, 2, 3 });
+        checkCopy(m, subRows31Cols31,  new int[] { 3, 1 }, new int[] { 3, 1 });
+        checkCopy(m, subRows31Cols31,  new int[] { 3, 1 }, new int[] { 3, 1 });
+
+        checkCopy(m, null,  1, 0, 2, 4);
+        checkCopy(m, null, -1, 1, 2, 2);
+        checkCopy(m, null,  1, 0, 2, 2);
+        checkCopy(m, null,  1, 0, 2, 4);
+        checkCopy(m, null, new int[] {},    new int[] { 0 });
+        checkCopy(m, null, new int[] { 0 }, new int[] { 4 });
+    }
+
+    private void checkCopy(FieldMatrix<Fraction> m, Fraction[][] reference,
+                           int startRow, int endRow, int startColumn, int endColumn) {
+        try {
+            Fraction[][] sub = (reference == null) ?
+                             new Fraction[1][1] :
+                             new Fraction[reference.length][reference[0].length];
+            m.copySubMatrix(startRow, endRow, startColumn, endColumn, sub);
+            if (reference != null) {
+                assertEquals(new BlockFieldMatrix<Fraction>(reference), new BlockFieldMatrix<Fraction>(sub));
+            } else {
+                fail("Expecting MatrixIndexException");
+            }
+        } catch (MatrixIndexException e) {
+            if (reference != null) {
+                throw e;
+            }
+        }
+    }
+
+    private void checkCopy(FieldMatrix<Fraction> m, Fraction[][] reference,
+                           int[] selectedRows, int[] selectedColumns) {
+        try {
+            Fraction[][] sub = (reference == null) ?
+                    new Fraction[1][1] :
+                    new Fraction[reference.length][reference[0].length];
+            m.copySubMatrix(selectedRows, selectedColumns, sub);
+            if (reference != null) {
+                assertEquals(new BlockFieldMatrix<Fraction>(reference), new BlockFieldMatrix<Fraction>(sub));
+            } else {
+                fail("Expecting MatrixIndexException");
+            }
+        } catch (MatrixIndexException e) {
+            if (reference != null) {
+                throw e;
+            }
+        }
+    }
+
+    public void testGetRowMatrix() {
+        FieldMatrix<Fraction> m     = new BlockFieldMatrix<Fraction>(subTestData);
+        FieldMatrix<Fraction> mRow0 = new BlockFieldMatrix<Fraction>(subRow0);
+        FieldMatrix<Fraction> mRow3 = new BlockFieldMatrix<Fraction>(subRow3);
+        assertEquals("Row0", mRow0, m.getRowMatrix(0));
+        assertEquals("Row3", mRow3, m.getRowMatrix(3));
+        try {
+            m.getRowMatrix(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getRowMatrix(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testSetRowMatrix() {
+        FieldMatrix<Fraction> m = new BlockFieldMatrix<Fraction>(subTestData);
+        FieldMatrix<Fraction> mRow3 = new BlockFieldMatrix<Fraction>(subRow3);
+        assertNotSame(mRow3, m.getRowMatrix(0));
+        m.setRowMatrix(0, mRow3);
+        assertEquals(mRow3, m.getRowMatrix(0));
+        try {
+            m.setRowMatrix(-1, mRow3);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.setRowMatrix(0, m);
+            fail("Expecting InvalidMatrixException");
+        } catch (InvalidMatrixException ex) {
+            // expected
+        }
+    }
+
+    public void testGetSetRowMatrixLarge() {
+        int n = 3 * BlockFieldMatrix.BLOCK_SIZE;
+        FieldMatrix<Fraction> m =
+            new BlockFieldMatrix<Fraction>(FractionField.getInstance(), n, n);
+        FieldMatrix<Fraction> sub =
+            new BlockFieldMatrix<Fraction>(FractionField.getInstance(), 1, n).scalarAdd(new Fraction(1));
+
+        m.setRowMatrix(2, sub);
+        for (int i = 0; i < n; ++i) {
+            for (int j = 0; j < n; ++j) {
+                if (i != 2) {
+                    assertEquals(new Fraction(0), m.getEntry(i, j));
+                } else {
+                    assertEquals(new Fraction(1), m.getEntry(i, j));
+                }
+            }
+        }
+        assertEquals(sub, m.getRowMatrix(2));
+
+    }
+
+    public void testGetColumnMatrix() {
+        FieldMatrix<Fraction> m = new BlockFieldMatrix<Fraction>(subTestData);
+        FieldMatrix<Fraction> mColumn1 = new BlockFieldMatrix<Fraction>(subColumn1);
+        FieldMatrix<Fraction> mColumn3 = new BlockFieldMatrix<Fraction>(subColumn3);
+        assertEquals(mColumn1, m.getColumnMatrix(1));
+        assertEquals(mColumn3, m.getColumnMatrix(3));
+        try {
+            m.getColumnMatrix(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getColumnMatrix(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testSetColumnMatrix() {
+        FieldMatrix<Fraction> m = new BlockFieldMatrix<Fraction>(subTestData);
+        FieldMatrix<Fraction> mColumn3 = new BlockFieldMatrix<Fraction>(subColumn3);
+        assertNotSame(mColumn3, m.getColumnMatrix(1));
+        m.setColumnMatrix(1, mColumn3);
+        assertEquals(mColumn3, m.getColumnMatrix(1));
+        try {
+            m.setColumnMatrix(-1, mColumn3);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.setColumnMatrix(0, m);
+            fail("Expecting InvalidMatrixException");
+        } catch (InvalidMatrixException ex) {
+            // expected
+        }
+    }
+
+    public void testGetSetColumnMatrixLarge() {
+        int n = 3 * BlockFieldMatrix.BLOCK_SIZE;
+        FieldMatrix<Fraction> m =
+            new BlockFieldMatrix<Fraction>(FractionField.getInstance(), n, n);
+        FieldMatrix<Fraction> sub =
+            new BlockFieldMatrix<Fraction>(FractionField.getInstance(), n, 1).scalarAdd(new Fraction(1));
+
+        m.setColumnMatrix(2, sub);
+        for (int i = 0; i < n; ++i) {
+            for (int j = 0; j < n; ++j) {
+                if (j != 2) {
+                    assertEquals(new Fraction(0), m.getEntry(i, j));
+                } else {
+                    assertEquals(new Fraction(1), m.getEntry(i, j));
+                }
+            }
+        }
+        assertEquals(sub, m.getColumnMatrix(2));
+
+    }
+
+    public void testGetRowVector() {
+        FieldMatrix<Fraction> m = new BlockFieldMatrix<Fraction>(subTestData);
+        FieldVector<Fraction> mRow0 = new ArrayFieldVector<Fraction>(subRow0[0]);
+        FieldVector<Fraction> mRow3 = new ArrayFieldVector<Fraction>(subRow3[0]);
+        assertEquals(mRow0, m.getRowVector(0));
+        assertEquals(mRow3, m.getRowVector(3));
+        try {
+            m.getRowVector(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getRowVector(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testSetRowVector() {
+        FieldMatrix<Fraction> m = new BlockFieldMatrix<Fraction>(subTestData);
+        FieldVector<Fraction> mRow3 = new ArrayFieldVector<Fraction>(subRow3[0]);
+        assertNotSame(mRow3, m.getRowMatrix(0));
+        m.setRowVector(0, mRow3);
+        assertEquals(mRow3, m.getRowVector(0));
+        try {
+            m.setRowVector(-1, mRow3);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.setRowVector(0, new ArrayFieldVector<Fraction>(FractionField.getInstance(), 5));
+            fail("Expecting InvalidMatrixException");
+        } catch (InvalidMatrixException ex) {
+            // expected
+        }
+    }
+
+    public void testGetSetRowVectorLarge() {
+        int n = 3 * BlockFieldMatrix.BLOCK_SIZE;
+        FieldMatrix<Fraction> m = new BlockFieldMatrix<Fraction>(FractionField.getInstance(), n, n);
+        FieldVector<Fraction> sub = new ArrayFieldVector<Fraction>(n, new Fraction(1));
+
+        m.setRowVector(2, sub);
+        for (int i = 0; i < n; ++i) {
+            for (int j = 0; j < n; ++j) {
+                if (i != 2) {
+                    assertEquals(new Fraction(0), m.getEntry(i, j));
+                } else {
+                    assertEquals(new Fraction(1), m.getEntry(i, j));
+                }
+            }
+        }
+        assertEquals(sub, m.getRowVector(2));
+
+    }
+
+    public void testGetColumnVector() {
+        FieldMatrix<Fraction> m = new BlockFieldMatrix<Fraction>(subTestData);
+        FieldVector<Fraction> mColumn1 = columnToVector(subColumn1);
+        FieldVector<Fraction> mColumn3 = columnToVector(subColumn3);
+        assertEquals(mColumn1, m.getColumnVector(1));
+        assertEquals(mColumn3, m.getColumnVector(3));
+        try {
+            m.getColumnVector(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getColumnVector(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testSetColumnVector() {
+        FieldMatrix<Fraction> m = new BlockFieldMatrix<Fraction>(subTestData);
+        FieldVector<Fraction> mColumn3 = columnToVector(subColumn3);
+        assertNotSame(mColumn3, m.getColumnVector(1));
+        m.setColumnVector(1, mColumn3);
+        assertEquals(mColumn3, m.getColumnVector(1));
+        try {
+            m.setColumnVector(-1, mColumn3);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.setColumnVector(0, new ArrayFieldVector<Fraction>(FractionField.getInstance(), 5));
+            fail("Expecting InvalidMatrixException");
+        } catch (InvalidMatrixException ex) {
+            // expected
+        }
+    }
+
+    public void testGetSetColumnVectorLarge() {
+        int n = 3 * BlockFieldMatrix.BLOCK_SIZE;
+        FieldMatrix<Fraction> m = new BlockFieldMatrix<Fraction>(FractionField.getInstance(), n, n);
+        FieldVector<Fraction> sub = new ArrayFieldVector<Fraction>(n, new Fraction(1));
+
+        m.setColumnVector(2, sub);
+        for (int i = 0; i < n; ++i) {
+            for (int j = 0; j < n; ++j) {
+                if (j != 2) {
+                    assertEquals(new Fraction(0), m.getEntry(i, j));
+                } else {
+                    assertEquals(new Fraction(1), m.getEntry(i, j));
+                }
+            }
+        }
+        assertEquals(sub, m.getColumnVector(2));
+
+    }
+
+    private FieldVector<Fraction> columnToVector(Fraction[][] column) {
+        Fraction[] data = new Fraction[column.length];
+        for (int i = 0; i < data.length; ++i) {
+            data[i] = column[i][0];
+        }
+        return new ArrayFieldVector<Fraction>(data, false);
+    }
+
+    public void testGetRow() {
+        FieldMatrix<Fraction> m = new BlockFieldMatrix<Fraction>(subTestData);
+        checkArrays(subRow0[0], m.getRow(0));
+        checkArrays(subRow3[0], m.getRow(3));
+        try {
+            m.getRow(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getRow(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testSetRow() {
+        FieldMatrix<Fraction> m = new BlockFieldMatrix<Fraction>(subTestData);
+        assertTrue(subRow3[0][0] != m.getRow(0)[0]);
+        m.setRow(0, subRow3[0]);
+        checkArrays(subRow3[0], m.getRow(0));
+        try {
+            m.setRow(-1, subRow3[0]);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.setRow(0, new Fraction[5]);
+            fail("Expecting InvalidMatrixException");
+        } catch (InvalidMatrixException ex) {
+            // expected
+        }
+    }
+
+    public void testGetSetRowLarge() {
+        int n = 3 * BlockFieldMatrix.BLOCK_SIZE;
+        FieldMatrix<Fraction> m = new BlockFieldMatrix<Fraction>(FractionField.getInstance(), n, n);
+        Fraction[] sub = new Fraction[n];
+        Arrays.fill(sub, new Fraction(1));
+
+        m.setRow(2, sub);
+        for (int i = 0; i < n; ++i) {
+            for (int j = 0; j < n; ++j) {
+                if (i != 2) {
+                    assertEquals(new Fraction(0), m.getEntry(i, j));
+                } else {
+                    assertEquals(new Fraction(1), m.getEntry(i, j));
+                }
+            }
+        }
+        checkArrays(sub, m.getRow(2));
+
+    }
+
+    public void testGetColumn() {
+        FieldMatrix<Fraction> m = new BlockFieldMatrix<Fraction>(subTestData);
+        Fraction[] mColumn1 = columnToArray(subColumn1);
+        Fraction[] mColumn3 = columnToArray(subColumn3);
+        checkArrays(mColumn1, m.getColumn(1));
+        checkArrays(mColumn3, m.getColumn(3));
+        try {
+            m.getColumn(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getColumn(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testSetColumn() {
+        FieldMatrix<Fraction> m = new BlockFieldMatrix<Fraction>(subTestData);
+        Fraction[] mColumn3 = columnToArray(subColumn3);
+        assertTrue(mColumn3[0] != m.getColumn(1)[0]);
+        m.setColumn(1, mColumn3);
+        checkArrays(mColumn3, m.getColumn(1));
+        try {
+            m.setColumn(-1, mColumn3);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.setColumn(0, new Fraction[5]);
+            fail("Expecting InvalidMatrixException");
+        } catch (InvalidMatrixException ex) {
+            // expected
+        }
+    }
+
+    public void testGetSetColumnLarge() {
+        int n = 3 * BlockFieldMatrix.BLOCK_SIZE;
+        FieldMatrix<Fraction> m = new BlockFieldMatrix<Fraction>(FractionField.getInstance(), n, n);
+        Fraction[] sub = new Fraction[n];
+        Arrays.fill(sub, new Fraction(1));
+
+        m.setColumn(2, sub);
+        for (int i = 0; i < n; ++i) {
+            for (int j = 0; j < n; ++j) {
+                if (j != 2) {
+                    assertEquals(new Fraction(0), m.getEntry(i, j));
+                } else {
+                    assertEquals(new Fraction(1), m.getEntry(i, j));
+                }
+            }
+        }
+        checkArrays(sub, m.getColumn(2));
+
+    }
+
+    private Fraction[] columnToArray(Fraction[][] column) {
+        Fraction[] data = new Fraction[column.length];
+        for (int i = 0; i < data.length; ++i) {
+            data[i] = column[i][0];
+        }
+        return data;
+    }
+
+    private void checkArrays(Fraction[] expected, Fraction[] actual) {
+        assertEquals(expected.length, actual.length);
+        for (int i = 0; i < expected.length; ++i) {
+            assertEquals(expected[i], actual[i]);
+        }
+    }
+
+    public void testEqualsAndHashCode() {
+        BlockFieldMatrix<Fraction> m = new BlockFieldMatrix<Fraction>(testData);
+        BlockFieldMatrix<Fraction> m1 = (BlockFieldMatrix<Fraction>) m.copy();
+        BlockFieldMatrix<Fraction> mt = (BlockFieldMatrix<Fraction>) m.transpose();
+        assertTrue(m.hashCode() != mt.hashCode());
+        assertEquals(m.hashCode(), m1.hashCode());
+        assertEquals(m, m);
+        assertEquals(m, m1);
+        assertFalse(m.equals(null));
+        assertFalse(m.equals(mt));
+        assertFalse(m.equals(new BlockFieldMatrix<Fraction>(bigSingular)));
+    }
+
+    public void testToString() {
+        BlockFieldMatrix<Fraction> m = new BlockFieldMatrix<Fraction>(testData);
+        assertEquals("BlockFieldMatrix{{1,2,3},{2,5,3},{1,0,8}}", m.toString());
+    }
+
+    public void testSetSubMatrix() throws Exception {
+        BlockFieldMatrix<Fraction> m = new BlockFieldMatrix<Fraction>(testData);
+        m.setSubMatrix(detData2,1,1);
+        FieldMatrix<Fraction> expected = new BlockFieldMatrix<Fraction>
+            (new Fraction[][] {{new Fraction(1),new Fraction(2),new Fraction(3)},{new Fraction(2),new Fraction(1),new Fraction(3)},{new Fraction(1),new Fraction(2),new Fraction(4)}});
+        assertEquals(expected, m);
+
+        m.setSubMatrix(detData2,0,0);
+        expected = new BlockFieldMatrix<Fraction>
+            (new Fraction[][] {{new Fraction(1),new Fraction(3),new Fraction(3)},{new Fraction(2),new Fraction(4),new Fraction(3)},{new Fraction(1),new Fraction(2),new Fraction(4)}});
+        assertEquals(expected, m);
+
+        m.setSubMatrix(testDataPlus2,0,0);
+        expected = new BlockFieldMatrix<Fraction>
+            (new Fraction[][] {{new Fraction(3),new Fraction(4),new Fraction(5)},{new Fraction(4),new Fraction(7),new Fraction(5)},{new Fraction(3),new Fraction(2),new Fraction(10)}});
+        assertEquals(expected, m);
+
+        // javadoc example
+        BlockFieldMatrix<Fraction> matrix =
+            new BlockFieldMatrix<Fraction>(new Fraction[][] {
+                    {new Fraction(1), new Fraction(2), new Fraction(3), new Fraction(4)},
+                    {new Fraction(5), new Fraction(6), new Fraction(7), new Fraction(8)},
+                    {new Fraction(9), new Fraction(0), new Fraction(1) , new Fraction(2)}
+            });
+        matrix.setSubMatrix(new Fraction[][] {
+                {new Fraction(3), new Fraction(4)},
+                {new Fraction(5), new Fraction(6)}
+        }, 1, 1);
+        expected =
+            new BlockFieldMatrix<Fraction>(new Fraction[][] {
+                    {new Fraction(1), new Fraction(2), new Fraction(3),new Fraction(4)},
+                    {new Fraction(5), new Fraction(3), new Fraction(4), new Fraction(8)},
+                    {new Fraction(9), new Fraction(5) ,new Fraction(6), new Fraction(2)}
+            });
+        assertEquals(expected, matrix);
+
+        // dimension overflow
+        try {
+            m.setSubMatrix(testData,1,1);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException e) {
+            // expected
+        }
+        // dimension underflow
+        try {
+            m.setSubMatrix(testData,-1,1);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException e) {
+            // expected
+        }
+        try {
+            m.setSubMatrix(testData,1,-1);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException e) {
+            // expected
+        }
+
+        // null
+        try {
+            m.setSubMatrix(null,1,1);
+            fail("expecting NullPointerException");
+        } catch (NullPointerException e) {
+            // expected
+        }
+
+        // ragged
+        try {
+            m.setSubMatrix(new Fraction[][] {{new Fraction(1)}, {new Fraction(2), new Fraction(3)}}, 0, 0);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        // empty
+        try {
+            m.setSubMatrix(new Fraction[][] {{}}, 0, 0);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+    }
+
+    public void testWalk() throws MatrixVisitorException {
+        int rows    = 150;
+        int columns = 75;
+
+        FieldMatrix<Fraction> m = new BlockFieldMatrix<Fraction>(FractionField.getInstance(), rows, columns);
+        m.walkInRowOrder(new SetVisitor());
+        GetVisitor getVisitor = new GetVisitor();
+        m.walkInOptimizedOrder(getVisitor);
+        assertEquals(rows * columns, getVisitor.getCount());
+
+        m = new BlockFieldMatrix<Fraction>(FractionField.getInstance(), rows, columns);
+        m.walkInRowOrder(new SetVisitor(), 1, rows - 2, 1, columns - 2);
+        getVisitor = new GetVisitor();
+        m.walkInOptimizedOrder(getVisitor, 1, rows - 2, 1, columns - 2);
+        assertEquals((rows - 2) * (columns - 2), getVisitor.getCount());
+        for (int i = 0; i < rows; ++i) {
+            assertEquals(new Fraction(0), m.getEntry(i, 0));
+            assertEquals(new Fraction(0), m.getEntry(i, columns - 1));
+        }
+        for (int j = 0; j < columns; ++j) {
+            assertEquals(new Fraction(0), m.getEntry(0, j));
+            assertEquals(new Fraction(0), m.getEntry(rows - 1, j));
+        }
+
+        m = new BlockFieldMatrix<Fraction>(FractionField.getInstance(), rows, columns);
+        m.walkInColumnOrder(new SetVisitor());
+        getVisitor = new GetVisitor();
+        m.walkInOptimizedOrder(getVisitor);
+        assertEquals(rows * columns, getVisitor.getCount());
+
+        m = new BlockFieldMatrix<Fraction>(FractionField.getInstance(), rows, columns);
+        m.walkInColumnOrder(new SetVisitor(), 1, rows - 2, 1, columns - 2);
+        getVisitor = new GetVisitor();
+        m.walkInOptimizedOrder(getVisitor, 1, rows - 2, 1, columns - 2);
+        assertEquals((rows - 2) * (columns - 2), getVisitor.getCount());
+        for (int i = 0; i < rows; ++i) {
+            assertEquals(new Fraction(0), m.getEntry(i, 0));
+            assertEquals(new Fraction(0), m.getEntry(i, columns - 1));
+        }
+        for (int j = 0; j < columns; ++j) {
+            assertEquals(new Fraction(0), m.getEntry(0, j));
+            assertEquals(new Fraction(0), m.getEntry(rows - 1, j));
+        }
+
+        m = new BlockFieldMatrix<Fraction>(FractionField.getInstance(), rows, columns);
+        m.walkInOptimizedOrder(new SetVisitor());
+        getVisitor = new GetVisitor();
+        m.walkInRowOrder(getVisitor);
+        assertEquals(rows * columns, getVisitor.getCount());
+
+        m = new BlockFieldMatrix<Fraction>(FractionField.getInstance(), rows, columns);
+        m.walkInOptimizedOrder(new SetVisitor(), 1, rows - 2, 1, columns - 2);
+        getVisitor = new GetVisitor();
+        m.walkInRowOrder(getVisitor, 1, rows - 2, 1, columns - 2);
+        assertEquals((rows - 2) * (columns - 2), getVisitor.getCount());
+        for (int i = 0; i < rows; ++i) {
+            assertEquals(new Fraction(0), m.getEntry(i, 0));
+            assertEquals(new Fraction(0), m.getEntry(i, columns - 1));
+        }
+        for (int j = 0; j < columns; ++j) {
+            assertEquals(new Fraction(0), m.getEntry(0, j));
+            assertEquals(new Fraction(0), m.getEntry(rows - 1, j));
+        }
+
+        m = new BlockFieldMatrix<Fraction>(FractionField.getInstance(), rows, columns);
+        m.walkInOptimizedOrder(new SetVisitor());
+        getVisitor = new GetVisitor();
+        m.walkInColumnOrder(getVisitor);
+        assertEquals(rows * columns, getVisitor.getCount());
+
+        m = new BlockFieldMatrix<Fraction>(FractionField.getInstance(), rows, columns);
+        m.walkInOptimizedOrder(new SetVisitor(), 1, rows - 2, 1, columns - 2);
+        getVisitor = new GetVisitor();
+        m.walkInColumnOrder(getVisitor, 1, rows - 2, 1, columns - 2);
+        assertEquals((rows - 2) * (columns - 2), getVisitor.getCount());
+        for (int i = 0; i < rows; ++i) {
+            assertEquals(new Fraction(0), m.getEntry(i, 0));
+            assertEquals(new Fraction(0), m.getEntry(i, columns - 1));
+        }
+        for (int j = 0; j < columns; ++j) {
+            assertEquals(new Fraction(0), m.getEntry(0, j));
+            assertEquals(new Fraction(0), m.getEntry(rows - 1, j));
+        }
+
+    }
+
+    public void testSerial()  {
+        BlockFieldMatrix<Fraction> m = new BlockFieldMatrix<Fraction>(testData);
+        assertEquals(m,TestUtils.serializeAndRecover(m));
+    }
+
+    private static class SetVisitor extends DefaultFieldMatrixChangingVisitor<Fraction> {
+        public SetVisitor() {
+            super(Fraction.ZERO);
+        }
+        @Override
+        public Fraction visit(int i, int j, Fraction value) {
+            return new Fraction(i * 11 + j, 11);
+        }
+    }
+
+    private static class GetVisitor extends DefaultFieldMatrixPreservingVisitor<Fraction> {
+        private int count;
+        public GetVisitor() {
+            super(Fraction.ZERO);
+            count = 0;
+        }
+        @Override
+        public void visit(int i, int j, Fraction value) {
+            ++count;
+            assertEquals(new Fraction(i * 11 + j, 11), value);
+        }
+        public int getCount() {
+            return count;
+        }
+    }
+
+    private BlockFieldMatrix<Fraction> createRandomMatrix(Random r, int rows, int columns) {
+        BlockFieldMatrix<Fraction> m =
+            new BlockFieldMatrix<Fraction>(FractionField.getInstance(), rows, columns);
+        for (int i = 0; i < rows; ++i) {
+            for (int j = 0; j < columns; ++j) {
+                int p = r.nextInt(20) - 10;
+                int q = r.nextInt(20) - 10;
+                if (q == 0) {
+                    q = 1;
+                }
+                m.setEntry(i, j, new Fraction(p, q));
+            }
+        }
+        return m;
+    }
+
+}
+
diff --git a/src/test/java/org/apache/commons/math/linear/BlockRealMatrixTest.java b/src/test/java/org/apache/commons/math/linear/BlockRealMatrixTest.java
new file mode 100644
index 0000000..d572c10
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/linear/BlockRealMatrixTest.java
@@ -0,0 +1,1204 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 java.util.Random;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.linear.MatrixVisitorException;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Test cases for the {@link BlockRealMatrix} class.
+ *
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ */
+
+public final class BlockRealMatrixTest extends TestCase {
+
+    // 3 x 3 identity matrix
+    protected double[][] id = { {1d,0d,0d}, {0d,1d,0d}, {0d,0d,1d} };
+
+    // Test data for group operations
+    protected double[][] testData = { {1d,2d,3d}, {2d,5d,3d}, {1d,0d,8d} };
+    protected double[][] testDataLU = {{2d, 5d, 3d}, {.5d, -2.5d, 6.5d}, {0.5d, 0.2d, .2d}};
+    protected double[][] testDataPlus2 = { {3d,4d,5d}, {4d,7d,5d}, {3d,2d,10d} };
+    protected double[][] testDataMinus = { {-1d,-2d,-3d}, {-2d,-5d,-3d},
+       {-1d,0d,-8d} };
+    protected double[] testDataRow1 = {1d,2d,3d};
+    protected double[] testDataCol3 = {3d,3d,8d};
+    protected double[][] testDataInv =
+        { {-40d,16d,9d}, {13d,-5d,-3d}, {5d,-2d,-1d} };
+    protected double[] preMultTest = {8,12,33};
+    protected double[][] testData2 ={ {1d,2d,3d}, {2d,5d,3d}};
+    protected double[][] testData2T = { {1d,2d}, {2d,5d}, {3d,3d}};
+    protected double[][] testDataPlusInv =
+        { {-39d,18d,12d}, {15d,0d,0d}, {6d,-2d,7d} };
+
+    // lu decomposition tests
+    protected double[][] luData = { {2d,3d,3d}, {0d,5d,7d}, {6d,9d,8d} };
+    protected double[][] luDataLUDecomposition = { {6d,9d,8d}, {0d,5d,7d},
+            {0.33333333333333,0d,0.33333333333333} };
+
+    // singular matrices
+    protected double[][] singular = { {2d,3d}, {2d,3d} };
+    protected double[][] bigSingular = {{1d,2d,3d,4d}, {2d,5d,3d,4d},
+        {7d,3d,256d,1930d}, {3d,7d,6d,8d}}; // 4th row = 1st + 2nd
+    protected double[][] detData = { {1d,2d,3d}, {4d,5d,6d}, {7d,8d,10d} };
+    protected double[][] detData2 = { {1d, 3d}, {2d, 4d}};
+
+    // vectors
+    protected double[] testVector = {1,2,3};
+    protected double[] testVector2 = {1,2,3,4};
+
+    // submatrix accessor tests
+    protected double[][] subTestData = {{1, 2, 3, 4}, {1.5, 2.5, 3.5, 4.5},
+            {2, 4, 6, 8}, {4, 5, 6, 7}};
+    // array selections
+    protected double[][] subRows02Cols13 = { {2, 4}, {4, 8}};
+    protected double[][] subRows03Cols12 = { {2, 3}, {5, 6}};
+    protected double[][] subRows03Cols123 = { {2, 3, 4} , {5, 6, 7}};
+    // effective permutations
+    protected double[][] subRows20Cols123 = { {4, 6, 8} , {2, 3, 4}};
+    protected double[][] subRows31Cols31 = {{7, 5}, {4.5, 2.5}};
+    // contiguous ranges
+    protected double[][] subRows01Cols23 = {{3,4} , {3.5, 4.5}};
+    protected double[][] subRows23Cols00 = {{2} , {4}};
+    protected double[][] subRows00Cols33 = {{4}};
+    // row matrices
+    protected double[][] subRow0 = {{1,2,3,4}};
+    protected double[][] subRow3 = {{4,5,6,7}};
+    // column matrices
+    protected double[][] subColumn1 = {{2}, {2.5}, {4}, {5}};
+    protected double[][] subColumn3 = {{4}, {4.5}, {8}, {7}};
+
+    // tolerances
+    protected double entryTolerance = 10E-16;
+    protected double normTolerance = 10E-14;
+
+    public BlockRealMatrixTest(String name) {
+        super(name);
+    }
+
+    /** test dimensions */
+    public void testDimensions() {
+        BlockRealMatrix m = new BlockRealMatrix(testData);
+        BlockRealMatrix m2 = new BlockRealMatrix(testData2);
+        assertEquals("testData row dimension",3,m.getRowDimension());
+        assertEquals("testData column dimension",3,m.getColumnDimension());
+        assertTrue("testData is square",m.isSquare());
+        assertEquals("testData2 row dimension",m2.getRowDimension(),2);
+        assertEquals("testData2 column dimension",m2.getColumnDimension(),3);
+        assertTrue("testData2 is not square",!m2.isSquare());
+    }
+
+    /** test copy functions */
+    public void testCopyFunctions() {
+        Random r = new Random(66636328996002l);
+        BlockRealMatrix m1 = createRandomMatrix(r, 47, 83);
+        BlockRealMatrix m2 = new BlockRealMatrix(m1.getData());
+        assertEquals(m1, m2);
+        BlockRealMatrix m3 = new BlockRealMatrix(testData);
+        BlockRealMatrix m4 = new BlockRealMatrix(m3.getData());
+        assertEquals(m3, m4);
+    }
+
+    /** test add */
+    public void testAdd() {
+        BlockRealMatrix m = new BlockRealMatrix(testData);
+        BlockRealMatrix mInv = new BlockRealMatrix(testDataInv);
+        RealMatrix mPlusMInv = m.add(mInv);
+        double[][] sumEntries = mPlusMInv.getData();
+        for (int row = 0; row < m.getRowDimension(); row++) {
+            for (int col = 0; col < m.getColumnDimension(); col++) {
+                assertEquals("sum entry entry",
+                    testDataPlusInv[row][col],sumEntries[row][col],
+                        entryTolerance);
+            }
+        }
+    }
+
+    /** test add failure */
+    public void testAddFail() {
+        BlockRealMatrix m = new BlockRealMatrix(testData);
+        BlockRealMatrix m2 = new BlockRealMatrix(testData2);
+        try {
+            m.add(m2);
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    /** test norm */
+    public void testNorm() {
+        BlockRealMatrix m = new BlockRealMatrix(testData);
+        BlockRealMatrix m2 = new BlockRealMatrix(testData2);
+        assertEquals("testData norm",14d,m.getNorm(),entryTolerance);
+        assertEquals("testData2 norm",7d,m2.getNorm(),entryTolerance);
+    }
+
+    /** test Frobenius norm */
+    public void testFrobeniusNorm() {
+        BlockRealMatrix m = new BlockRealMatrix(testData);
+        BlockRealMatrix m2 = new BlockRealMatrix(testData2);
+        assertEquals("testData Frobenius norm", FastMath.sqrt(117.0), m.getFrobeniusNorm(), entryTolerance);
+        assertEquals("testData2 Frobenius norm", FastMath.sqrt(52.0), m2.getFrobeniusNorm(), entryTolerance);
+    }
+
+     /** test m-n = m + -n */
+    public void testPlusMinus() {
+        BlockRealMatrix m = new BlockRealMatrix(testData);
+        BlockRealMatrix m2 = new BlockRealMatrix(testDataInv);
+        assertClose(m.subtract(m2), m2.scalarMultiply(-1d).add(m), entryTolerance);
+        try {
+            m.subtract(new BlockRealMatrix(testData2));
+            fail("Expecting illegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    /** test multiply */
+     public void testMultiply() {
+        BlockRealMatrix m = new BlockRealMatrix(testData);
+        BlockRealMatrix mInv = new BlockRealMatrix(testDataInv);
+        BlockRealMatrix identity = new BlockRealMatrix(id);
+        BlockRealMatrix m2 = new BlockRealMatrix(testData2);
+        assertClose(m.multiply(mInv), identity, entryTolerance);
+        assertClose(mInv.multiply(m), identity, entryTolerance);
+        assertClose(m.multiply(identity), m, entryTolerance);
+        assertClose(identity.multiply(mInv), mInv, entryTolerance);
+        assertClose(m2.multiply(identity), m2, entryTolerance);
+        try {
+            m.multiply(new BlockRealMatrix(bigSingular));
+            fail("Expecting illegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+
+    public void testSeveralBlocks() {
+
+        RealMatrix m = new BlockRealMatrix(35, 71);
+        for (int i = 0; i < m.getRowDimension(); ++i) {
+            for (int j = 0; j < m.getColumnDimension(); ++j) {
+                m.setEntry(i, j, i + j / 1024.0);
+            }
+        }
+
+        RealMatrix mT = m.transpose();
+        assertEquals(m.getRowDimension(), mT.getColumnDimension());
+        assertEquals(m.getColumnDimension(), mT.getRowDimension());
+        for (int i = 0; i < mT.getRowDimension(); ++i) {
+            for (int j = 0; j < mT.getColumnDimension(); ++j) {
+                assertEquals(m.getEntry(j, i), mT.getEntry(i, j), 0);
+            }
+        }
+
+        RealMatrix mPm = m.add(m);
+        for (int i = 0; i < mPm.getRowDimension(); ++i) {
+            for (int j = 0; j < mPm.getColumnDimension(); ++j) {
+                assertEquals(2 * m.getEntry(i, j), mPm.getEntry(i, j), 0);
+            }
+        }
+
+        RealMatrix mPmMm = mPm.subtract(m);
+        for (int i = 0; i < mPmMm.getRowDimension(); ++i) {
+            for (int j = 0; j < mPmMm.getColumnDimension(); ++j) {
+                assertEquals(m.getEntry(i, j), mPmMm.getEntry(i, j), 0);
+            }
+        }
+
+        RealMatrix mTm = mT.multiply(m);
+        for (int i = 0; i < mTm.getRowDimension(); ++i) {
+            for (int j = 0; j < mTm.getColumnDimension(); ++j) {
+                double sum = 0;
+                for (int k = 0; k < mT.getColumnDimension(); ++k) {
+                    sum += (k + i / 1024.0) * (k + j / 1024.0);
+                }
+                assertEquals(sum, mTm.getEntry(i, j), 0);
+            }
+        }
+
+        RealMatrix mmT = m.multiply(mT);
+        for (int i = 0; i < mmT.getRowDimension(); ++i) {
+            for (int j = 0; j < mmT.getColumnDimension(); ++j) {
+                double sum = 0;
+                for (int k = 0; k < m.getColumnDimension(); ++k) {
+                    sum += (i + k / 1024.0) * (j + k / 1024.0);
+                }
+                assertEquals(sum, mmT.getEntry(i, j), 0);
+            }
+        }
+
+        RealMatrix sub1 = m.getSubMatrix(2, 9, 5, 20);
+        for (int i = 0; i < sub1.getRowDimension(); ++i) {
+            for (int j = 0; j < sub1.getColumnDimension(); ++j) {
+                assertEquals((i + 2) + (j + 5) / 1024.0, sub1.getEntry(i, j), 0);
+            }
+        }
+
+        RealMatrix sub2 = m.getSubMatrix(10, 12, 3, 70);
+        for (int i = 0; i < sub2.getRowDimension(); ++i) {
+            for (int j = 0; j < sub2.getColumnDimension(); ++j) {
+                assertEquals((i + 10) + (j + 3) / 1024.0, sub2.getEntry(i, j), 0);
+            }
+        }
+
+        RealMatrix sub3 = m.getSubMatrix(30, 34, 0, 5);
+        for (int i = 0; i < sub3.getRowDimension(); ++i) {
+            for (int j = 0; j < sub3.getColumnDimension(); ++j) {
+                assertEquals((i + 30) + (j + 0) / 1024.0, sub3.getEntry(i, j), 0);
+            }
+        }
+
+        RealMatrix sub4 = m.getSubMatrix(30, 32, 62, 65);
+        for (int i = 0; i < sub4.getRowDimension(); ++i) {
+            for (int j = 0; j < sub4.getColumnDimension(); ++j) {
+                assertEquals((i + 30) + (j + 62) / 1024.0, sub4.getEntry(i, j), 0);
+            }
+        }
+
+    }
+
+    //Additional Test for BlockRealMatrixTest.testMultiply
+
+    private double[][] d3 = new double[][] {{1,2,3,4},{5,6,7,8}};
+    private double[][] d4 = new double[][] {{1},{2},{3},{4}};
+    private double[][] d5 = new double[][] {{30},{70}};
+
+    public void testMultiply2() {
+       RealMatrix m3 = new BlockRealMatrix(d3);
+       RealMatrix m4 = new BlockRealMatrix(d4);
+       RealMatrix m5 = new BlockRealMatrix(d5);
+       assertClose(m3.multiply(m4), m5, entryTolerance);
+   }
+
+    /** test trace */
+    public void testTrace() {
+        RealMatrix m = new BlockRealMatrix(id);
+        assertEquals("identity trace",3d,m.getTrace(),entryTolerance);
+        m = new BlockRealMatrix(testData2);
+        try {
+            m.getTrace();
+            fail("Expecting NonSquareMatrixException");
+        } catch (NonSquareMatrixException ex) {
+            // ignored
+        }
+    }
+
+    /** test scalarAdd */
+    public void testScalarAdd() {
+        RealMatrix m = new BlockRealMatrix(testData);
+        assertClose(new BlockRealMatrix(testDataPlus2), m.scalarAdd(2d), entryTolerance);
+    }
+
+    /** test operate */
+    public void testOperate() {
+        RealMatrix m = new BlockRealMatrix(id);
+        assertClose(testVector, m.operate(testVector), entryTolerance);
+        assertClose(testVector, m.operate(new ArrayRealVector(testVector)).getData(), entryTolerance);
+        m = new BlockRealMatrix(bigSingular);
+        try {
+            m.operate(testVector);
+            fail("Expecting illegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    public void testOperateLarge() {
+        int p = (7 * BlockRealMatrix.BLOCK_SIZE) / 2;
+        int q = (5 * BlockRealMatrix.BLOCK_SIZE) / 2;
+        int r =  3 * BlockRealMatrix.BLOCK_SIZE;
+        Random random = new Random(111007463902334l);
+        RealMatrix m1 = createRandomMatrix(random, p, q);
+        RealMatrix m2 = createRandomMatrix(random, q, r);
+        RealMatrix m1m2 = m1.multiply(m2);
+        for (int i = 0; i < r; ++i) {
+            checkArrays(m1m2.getColumn(i), m1.operate(m2.getColumn(i)));
+        }
+    }
+
+    public void testOperatePremultiplyLarge() {
+        int p = (7 * BlockRealMatrix.BLOCK_SIZE) / 2;
+        int q = (5 * BlockRealMatrix.BLOCK_SIZE) / 2;
+        int r =  3 * BlockRealMatrix.BLOCK_SIZE;
+        Random random = new Random(111007463902334l);
+        RealMatrix m1 = createRandomMatrix(random, p, q);
+        RealMatrix m2 = createRandomMatrix(random, q, r);
+        RealMatrix m1m2 = m1.multiply(m2);
+        for (int i = 0; i < p; ++i) {
+            checkArrays(m1m2.getRow(i), m2.preMultiply(m1.getRow(i)));
+        }
+    }
+
+    /** test issue MATH-209 */
+    public void testMath209() {
+        RealMatrix a = new BlockRealMatrix(new double[][] {
+                { 1, 2 }, { 3, 4 }, { 5, 6 }
+        });
+        double[] b = a.operate(new double[] { 1, 1 });
+        assertEquals(a.getRowDimension(), b.length);
+        assertEquals( 3.0, b[0], 1.0e-12);
+        assertEquals( 7.0, b[1], 1.0e-12);
+        assertEquals(11.0, b[2], 1.0e-12);
+    }
+
+    /** test transpose */
+    public void testTranspose() {
+        RealMatrix m = new BlockRealMatrix(testData);
+        RealMatrix mIT = new LUDecompositionImpl(m).getSolver().getInverse().transpose();
+        RealMatrix mTI = new LUDecompositionImpl(m.transpose()).getSolver().getInverse();
+        assertClose(mIT, mTI, normTolerance);
+        m = new BlockRealMatrix(testData2);
+        RealMatrix mt = new BlockRealMatrix(testData2T);
+        assertClose(mt, m.transpose(), normTolerance);
+    }
+
+    /** test preMultiply by vector */
+    public void testPremultiplyVector() {
+        RealMatrix m = new BlockRealMatrix(testData);
+        assertClose(m.preMultiply(testVector), preMultTest, normTolerance);
+        assertClose(m.preMultiply(new ArrayRealVector(testVector).getData()),
+                    preMultTest, normTolerance);
+        m = new BlockRealMatrix(bigSingular);
+        try {
+            m.preMultiply(testVector);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    public void testPremultiply() {
+        RealMatrix m3 = new BlockRealMatrix(d3);
+        RealMatrix m4 = new BlockRealMatrix(d4);
+        RealMatrix m5 = new BlockRealMatrix(d5);
+        assertClose(m4.preMultiply(m3), m5, entryTolerance);
+
+        BlockRealMatrix m = new BlockRealMatrix(testData);
+        BlockRealMatrix mInv = new BlockRealMatrix(testDataInv);
+        BlockRealMatrix identity = new BlockRealMatrix(id);
+        assertClose(m.preMultiply(mInv), identity, entryTolerance);
+        assertClose(mInv.preMultiply(m), identity, entryTolerance);
+        assertClose(m.preMultiply(identity), m, entryTolerance);
+        assertClose(identity.preMultiply(mInv), mInv, entryTolerance);
+        try {
+            m.preMultiply(new BlockRealMatrix(bigSingular));
+            fail("Expecting illegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    public void testGetVectors() {
+        RealMatrix m = new BlockRealMatrix(testData);
+        assertClose(m.getRow(0), testDataRow1, entryTolerance);
+        assertClose(m.getColumn(2), testDataCol3, entryTolerance);
+        try {
+            m.getRow(10);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // ignored
+        }
+        try {
+            m.getColumn(-1);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // ignored
+        }
+    }
+
+    public void testGetEntry() {
+        RealMatrix m = new BlockRealMatrix(testData);
+        assertEquals("get entry",m.getEntry(0,1),2d,entryTolerance);
+        try {
+            m.getEntry(10, 4);
+            fail ("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    /** test examples in user guide */
+    public void testExamples() {
+        // Create a real matrix with two rows and three columns
+        double[][] matrixData = { {1d,2d,3d}, {2d,5d,3d}};
+        RealMatrix m = new BlockRealMatrix(matrixData);
+        // One more with three rows, two columns
+        double[][] matrixData2 = { {1d,2d}, {2d,5d}, {1d, 7d}};
+        RealMatrix n = new BlockRealMatrix(matrixData2);
+        // Now multiply m by n
+        RealMatrix p = m.multiply(n);
+        assertEquals(2, p.getRowDimension());
+        assertEquals(2, p.getColumnDimension());
+        // Invert p
+        RealMatrix pInverse = new LUDecompositionImpl(p).getSolver().getInverse();
+        assertEquals(2, pInverse.getRowDimension());
+        assertEquals(2, pInverse.getColumnDimension());
+
+        // Solve example
+        double[][] coefficientsData = {{2, 3, -2}, {-1, 7, 6}, {4, -3, -5}};
+        RealMatrix coefficients = new BlockRealMatrix(coefficientsData);
+        double[] constants = {1, -2, 1};
+        double[] solution = new LUDecompositionImpl(coefficients).getSolver().solve(constants);
+        assertEquals(2 * solution[0] + 3 * solution[1] -2 * solution[2], constants[0], 1E-12);
+        assertEquals(-1 * solution[0] + 7 * solution[1] + 6 * solution[2], constants[1], 1E-12);
+        assertEquals(4 * solution[0] - 3 * solution[1] -5 * solution[2], constants[2], 1E-12);
+
+    }
+
+    // test submatrix accessors
+    public void testGetSubMatrix() {
+        RealMatrix m = new BlockRealMatrix(subTestData);
+        checkGetSubMatrix(m, subRows23Cols00,  2 , 3 , 0, 0);
+        checkGetSubMatrix(m, subRows00Cols33,  0 , 0 , 3, 3);
+        checkGetSubMatrix(m, subRows01Cols23,  0 , 1 , 2, 3);
+        checkGetSubMatrix(m, subRows02Cols13,  new int[] { 0, 2 }, new int[] { 1, 3 });
+        checkGetSubMatrix(m, subRows03Cols12,  new int[] { 0, 3 }, new int[] { 1, 2 });
+        checkGetSubMatrix(m, subRows03Cols123, new int[] { 0, 3 }, new int[] { 1, 2, 3 });
+        checkGetSubMatrix(m, subRows20Cols123, new int[] { 2, 0 }, new int[] { 1, 2, 3 });
+        checkGetSubMatrix(m, subRows31Cols31,  new int[] { 3, 1 }, new int[] { 3, 1 });
+        checkGetSubMatrix(m, subRows31Cols31,  new int[] { 3, 1 }, new int[] { 3, 1 });
+        checkGetSubMatrix(m, null,  1, 0, 2, 4);
+        checkGetSubMatrix(m, null, -1, 1, 2, 2);
+        checkGetSubMatrix(m, null,  1, 0, 2, 2);
+        checkGetSubMatrix(m, null,  1, 0, 2, 4);
+        checkGetSubMatrix(m, null, new int[] {},    new int[] { 0 });
+        checkGetSubMatrix(m, null, new int[] { 0 }, new int[] { 4 });
+    }
+
+    private void checkGetSubMatrix(RealMatrix m, double[][] reference,
+                                   int startRow, int endRow, int startColumn, int endColumn) {
+        try {
+            RealMatrix sub = m.getSubMatrix(startRow, endRow, startColumn, endColumn);
+            if (reference != null) {
+                assertEquals(new BlockRealMatrix(reference), sub);
+            } else {
+                fail("Expecting MatrixIndexException");
+            }
+        } catch (MatrixIndexException e) {
+            if (reference != null) {
+                throw e;
+            }
+        }
+    }
+
+    private void checkGetSubMatrix(RealMatrix m, double[][] reference,
+                                   int[] selectedRows, int[] selectedColumns) {
+        try {
+            RealMatrix sub = m.getSubMatrix(selectedRows, selectedColumns);
+            if (reference != null) {
+                assertEquals(new BlockRealMatrix(reference), sub);
+            } else {
+                fail("Expecting MatrixIndexException");
+            }
+        } catch (MatrixIndexException e) {
+            if (reference != null) {
+                throw e;
+            }
+        }
+    }
+
+    public void testGetSetMatrixLarge() {
+        int n = 3 * BlockRealMatrix.BLOCK_SIZE;
+        RealMatrix m = new BlockRealMatrix(n, n);
+        RealMatrix sub = new BlockRealMatrix(n - 4, n - 4).scalarAdd(1);
+
+        m.setSubMatrix(sub.getData(), 2, 2);
+        for (int i = 0; i < n; ++i) {
+            for (int j = 0; j < n; ++j) {
+                if ((i < 2) || (i > n - 3) || (j < 2) || (j > n - 3)) {
+                    assertEquals(0.0, m.getEntry(i, j), 0.0);
+                } else {
+                    assertEquals(1.0, m.getEntry(i, j), 0.0);
+                }
+            }
+        }
+        assertEquals(sub, m.getSubMatrix(2, n - 3, 2, n - 3));
+
+    }
+
+    public void testCopySubMatrix() {
+        RealMatrix m = new BlockRealMatrix(subTestData);
+        checkCopy(m, subRows23Cols00,  2 , 3 , 0, 0);
+        checkCopy(m, subRows00Cols33,  0 , 0 , 3, 3);
+        checkCopy(m, subRows01Cols23,  0 , 1 , 2, 3);
+        checkCopy(m, subRows02Cols13,  new int[] { 0, 2 }, new int[] { 1, 3 });
+        checkCopy(m, subRows03Cols12,  new int[] { 0, 3 }, new int[] { 1, 2 });
+        checkCopy(m, subRows03Cols123, new int[] { 0, 3 }, new int[] { 1, 2, 3 });
+        checkCopy(m, subRows20Cols123, new int[] { 2, 0 }, new int[] { 1, 2, 3 });
+        checkCopy(m, subRows31Cols31,  new int[] { 3, 1 }, new int[] { 3, 1 });
+        checkCopy(m, subRows31Cols31,  new int[] { 3, 1 }, new int[] { 3, 1 });
+
+        checkCopy(m, null,  1, 0, 2, 4);
+        checkCopy(m, null, -1, 1, 2, 2);
+        checkCopy(m, null,  1, 0, 2, 2);
+        checkCopy(m, null,  1, 0, 2, 4);
+        checkCopy(m, null, new int[] {},    new int[] { 0 });
+        checkCopy(m, null, new int[] { 0 }, new int[] { 4 });
+    }
+
+    private void checkCopy(RealMatrix m, double[][] reference,
+                           int startRow, int endRow, int startColumn, int endColumn) {
+        try {
+            double[][] sub = (reference == null) ?
+                             new double[1][1] :
+                             new double[reference.length][reference[0].length];
+            m.copySubMatrix(startRow, endRow, startColumn, endColumn, sub);
+            if (reference != null) {
+                assertEquals(new BlockRealMatrix(reference), new BlockRealMatrix(sub));
+            } else {
+                fail("Expecting MatrixIndexException");
+            }
+        } catch (MatrixIndexException e) {
+            if (reference != null) {
+                throw e;
+            }
+        }
+    }
+
+    private void checkCopy(RealMatrix m, double[][] reference,
+                           int[] selectedRows, int[] selectedColumns) {
+        try {
+            double[][] sub = (reference == null) ?
+                    new double[1][1] :
+                    new double[reference.length][reference[0].length];
+            m.copySubMatrix(selectedRows, selectedColumns, sub);
+            if (reference != null) {
+                assertEquals(new BlockRealMatrix(reference), new BlockRealMatrix(sub));
+            } else {
+                fail("Expecting MatrixIndexException");
+            }
+        } catch (MatrixIndexException e) {
+            if (reference != null) {
+                throw e;
+            }
+        }
+    }
+
+    public void testGetRowMatrix() {
+        RealMatrix m     = new BlockRealMatrix(subTestData);
+        RealMatrix mRow0 = new BlockRealMatrix(subRow0);
+        RealMatrix mRow3 = new BlockRealMatrix(subRow3);
+        assertEquals("Row0", mRow0, m.getRowMatrix(0));
+        assertEquals("Row3", mRow3, m.getRowMatrix(3));
+        try {
+            m.getRowMatrix(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getRowMatrix(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testSetRowMatrix() {
+        RealMatrix m = new BlockRealMatrix(subTestData);
+        RealMatrix mRow3 = new BlockRealMatrix(subRow3);
+        assertNotSame(mRow3, m.getRowMatrix(0));
+        m.setRowMatrix(0, mRow3);
+        assertEquals(mRow3, m.getRowMatrix(0));
+        try {
+            m.setRowMatrix(-1, mRow3);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.setRowMatrix(0, m);
+            fail("Expecting InvalidMatrixException");
+        } catch (InvalidMatrixException ex) {
+            // expected
+        }
+    }
+
+    public void testGetSetRowMatrixLarge() {
+        int n = 3 * BlockRealMatrix.BLOCK_SIZE;
+        RealMatrix m = new BlockRealMatrix(n, n);
+        RealMatrix sub = new BlockRealMatrix(1, n).scalarAdd(1);
+
+        m.setRowMatrix(2, sub);
+        for (int i = 0; i < n; ++i) {
+            for (int j = 0; j < n; ++j) {
+                if (i != 2) {
+                    assertEquals(0.0, m.getEntry(i, j), 0.0);
+                } else {
+                    assertEquals(1.0, m.getEntry(i, j), 0.0);
+                }
+            }
+        }
+        assertEquals(sub, m.getRowMatrix(2));
+
+    }
+
+    public void testGetColumnMatrix() {
+        RealMatrix m = new BlockRealMatrix(subTestData);
+        RealMatrix mColumn1 = new BlockRealMatrix(subColumn1);
+        RealMatrix mColumn3 = new BlockRealMatrix(subColumn3);
+        assertEquals(mColumn1, m.getColumnMatrix(1));
+        assertEquals(mColumn3, m.getColumnMatrix(3));
+        try {
+            m.getColumnMatrix(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getColumnMatrix(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testSetColumnMatrix() {
+        RealMatrix m = new BlockRealMatrix(subTestData);
+        RealMatrix mColumn3 = new BlockRealMatrix(subColumn3);
+        assertNotSame(mColumn3, m.getColumnMatrix(1));
+        m.setColumnMatrix(1, mColumn3);
+        assertEquals(mColumn3, m.getColumnMatrix(1));
+        try {
+            m.setColumnMatrix(-1, mColumn3);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.setColumnMatrix(0, m);
+            fail("Expecting InvalidMatrixException");
+        } catch (InvalidMatrixException ex) {
+            // expected
+        }
+    }
+
+    public void testGetSetColumnMatrixLarge() {
+        int n = 3 * BlockRealMatrix.BLOCK_SIZE;
+        RealMatrix m = new BlockRealMatrix(n, n);
+        RealMatrix sub = new BlockRealMatrix(n, 1).scalarAdd(1);
+
+        m.setColumnMatrix(2, sub);
+        for (int i = 0; i < n; ++i) {
+            for (int j = 0; j < n; ++j) {
+                if (j != 2) {
+                    assertEquals(0.0, m.getEntry(i, j), 0.0);
+                } else {
+                    assertEquals(1.0, m.getEntry(i, j), 0.0);
+                }
+            }
+        }
+        assertEquals(sub, m.getColumnMatrix(2));
+
+    }
+
+    public void testGetRowVector() {
+        RealMatrix m = new BlockRealMatrix(subTestData);
+        RealVector mRow0 = new ArrayRealVector(subRow0[0]);
+        RealVector mRow3 = new ArrayRealVector(subRow3[0]);
+        assertEquals(mRow0, m.getRowVector(0));
+        assertEquals(mRow3, m.getRowVector(3));
+        try {
+            m.getRowVector(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getRowVector(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testSetRowVector() {
+        RealMatrix m = new BlockRealMatrix(subTestData);
+        RealVector mRow3 = new ArrayRealVector(subRow3[0]);
+        assertNotSame(mRow3, m.getRowMatrix(0));
+        m.setRowVector(0, mRow3);
+        assertEquals(mRow3, m.getRowVector(0));
+        try {
+            m.setRowVector(-1, mRow3);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.setRowVector(0, new ArrayRealVector(5));
+            fail("Expecting InvalidMatrixException");
+        } catch (InvalidMatrixException ex) {
+            // expected
+        }
+    }
+
+    public void testGetSetRowVectorLarge() {
+        int n = 3 * BlockRealMatrix.BLOCK_SIZE;
+        RealMatrix m = new BlockRealMatrix(n, n);
+        RealVector sub = new ArrayRealVector(n, 1.0);
+
+        m.setRowVector(2, sub);
+        for (int i = 0; i < n; ++i) {
+            for (int j = 0; j < n; ++j) {
+                if (i != 2) {
+                    assertEquals(0.0, m.getEntry(i, j), 0.0);
+                } else {
+                    assertEquals(1.0, m.getEntry(i, j), 0.0);
+                }
+            }
+        }
+        assertEquals(sub, m.getRowVector(2));
+
+    }
+
+    public void testGetColumnVector() {
+        RealMatrix m = new BlockRealMatrix(subTestData);
+        RealVector mColumn1 = columnToVector(subColumn1);
+        RealVector mColumn3 = columnToVector(subColumn3);
+        assertEquals(mColumn1, m.getColumnVector(1));
+        assertEquals(mColumn3, m.getColumnVector(3));
+        try {
+            m.getColumnVector(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getColumnVector(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testSetColumnVector() {
+        RealMatrix m = new BlockRealMatrix(subTestData);
+        RealVector mColumn3 = columnToVector(subColumn3);
+        assertNotSame(mColumn3, m.getColumnVector(1));
+        m.setColumnVector(1, mColumn3);
+        assertEquals(mColumn3, m.getColumnVector(1));
+        try {
+            m.setColumnVector(-1, mColumn3);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.setColumnVector(0, new ArrayRealVector(5));
+            fail("Expecting InvalidMatrixException");
+        } catch (InvalidMatrixException ex) {
+            // expected
+        }
+    }
+
+    public void testGetSetColumnVectorLarge() {
+        int n = 3 * BlockRealMatrix.BLOCK_SIZE;
+        RealMatrix m = new BlockRealMatrix(n, n);
+        RealVector sub = new ArrayRealVector(n, 1.0);
+
+        m.setColumnVector(2, sub);
+        for (int i = 0; i < n; ++i) {
+            for (int j = 0; j < n; ++j) {
+                if (j != 2) {
+                    assertEquals(0.0, m.getEntry(i, j), 0.0);
+                } else {
+                    assertEquals(1.0, m.getEntry(i, j), 0.0);
+                }
+            }
+        }
+        assertEquals(sub, m.getColumnVector(2));
+
+    }
+
+    private RealVector columnToVector(double[][] column) {
+        double[] data = new double[column.length];
+        for (int i = 0; i < data.length; ++i) {
+            data[i] = column[i][0];
+        }
+        return new ArrayRealVector(data, false);
+    }
+
+    public void testGetRow() {
+        RealMatrix m = new BlockRealMatrix(subTestData);
+        checkArrays(subRow0[0], m.getRow(0));
+        checkArrays(subRow3[0], m.getRow(3));
+        try {
+            m.getRow(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getRow(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testSetRow() {
+        RealMatrix m = new BlockRealMatrix(subTestData);
+        assertTrue(subRow3[0][0] != m.getRow(0)[0]);
+        m.setRow(0, subRow3[0]);
+        checkArrays(subRow3[0], m.getRow(0));
+        try {
+            m.setRow(-1, subRow3[0]);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.setRow(0, new double[5]);
+            fail("Expecting InvalidMatrixException");
+        } catch (InvalidMatrixException ex) {
+            // expected
+        }
+    }
+
+    public void testGetSetRowLarge() {
+        int n = 3 * BlockRealMatrix.BLOCK_SIZE;
+        RealMatrix m = new BlockRealMatrix(n, n);
+        double[] sub = new double[n];
+        Arrays.fill(sub, 1.0);
+
+        m.setRow(2, sub);
+        for (int i = 0; i < n; ++i) {
+            for (int j = 0; j < n; ++j) {
+                if (i != 2) {
+                    assertEquals(0.0, m.getEntry(i, j), 0.0);
+                } else {
+                    assertEquals(1.0, m.getEntry(i, j), 0.0);
+                }
+            }
+        }
+        checkArrays(sub, m.getRow(2));
+
+    }
+
+    public void testGetColumn() {
+        RealMatrix m = new BlockRealMatrix(subTestData);
+        double[] mColumn1 = columnToArray(subColumn1);
+        double[] mColumn3 = columnToArray(subColumn3);
+        checkArrays(mColumn1, m.getColumn(1));
+        checkArrays(mColumn3, m.getColumn(3));
+        try {
+            m.getColumn(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getColumn(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testSetColumn() {
+        RealMatrix m = new BlockRealMatrix(subTestData);
+        double[] mColumn3 = columnToArray(subColumn3);
+        assertTrue(mColumn3[0] != m.getColumn(1)[0]);
+        m.setColumn(1, mColumn3);
+        checkArrays(mColumn3, m.getColumn(1));
+        try {
+            m.setColumn(-1, mColumn3);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.setColumn(0, new double[5]);
+            fail("Expecting InvalidMatrixException");
+        } catch (InvalidMatrixException ex) {
+            // expected
+        }
+    }
+
+    public void testGetSetColumnLarge() {
+        int n = 3 * BlockRealMatrix.BLOCK_SIZE;
+        RealMatrix m = new BlockRealMatrix(n, n);
+        double[] sub = new double[n];
+        Arrays.fill(sub, 1.0);
+
+        m.setColumn(2, sub);
+        for (int i = 0; i < n; ++i) {
+            for (int j = 0; j < n; ++j) {
+                if (j != 2) {
+                    assertEquals(0.0, m.getEntry(i, j), 0.0);
+                } else {
+                    assertEquals(1.0, m.getEntry(i, j), 0.0);
+                }
+            }
+        }
+        checkArrays(sub, m.getColumn(2));
+
+    }
+
+    private double[] columnToArray(double[][] column) {
+        double[] data = new double[column.length];
+        for (int i = 0; i < data.length; ++i) {
+            data[i] = column[i][0];
+        }
+        return data;
+    }
+
+    private void checkArrays(double[] expected, double[] actual) {
+        assertEquals(expected.length, actual.length);
+        for (int i = 0; i < expected.length; ++i) {
+            assertEquals(expected[i], actual[i]);
+        }
+    }
+
+    public void testEqualsAndHashCode() {
+        BlockRealMatrix m = new BlockRealMatrix(testData);
+        BlockRealMatrix m1 = m.copy();
+        BlockRealMatrix mt = m.transpose();
+        assertTrue(m.hashCode() != mt.hashCode());
+        assertEquals(m.hashCode(), m1.hashCode());
+        assertEquals(m, m);
+        assertEquals(m, m1);
+        assertFalse(m.equals(null));
+        assertFalse(m.equals(mt));
+        assertFalse(m.equals(new BlockRealMatrix(bigSingular)));
+    }
+
+    public void testToString() {
+        BlockRealMatrix m = new BlockRealMatrix(testData);
+        assertEquals("BlockRealMatrix{{1.0,2.0,3.0},{2.0,5.0,3.0},{1.0,0.0,8.0}}",
+                m.toString());
+    }
+
+    public void testSetSubMatrix() throws Exception {
+        BlockRealMatrix m = new BlockRealMatrix(testData);
+        m.setSubMatrix(detData2,1,1);
+        RealMatrix expected = new BlockRealMatrix
+            (new double[][] {{1.0,2.0,3.0},{2.0,1.0,3.0},{1.0,2.0,4.0}});
+        assertEquals(expected, m);
+
+        m.setSubMatrix(detData2,0,0);
+        expected = new BlockRealMatrix
+            (new double[][] {{1.0,3.0,3.0},{2.0,4.0,3.0},{1.0,2.0,4.0}});
+        assertEquals(expected, m);
+
+        m.setSubMatrix(testDataPlus2,0,0);
+        expected = new BlockRealMatrix
+            (new double[][] {{3.0,4.0,5.0},{4.0,7.0,5.0},{3.0,2.0,10.0}});
+        assertEquals(expected, m);
+
+        // javadoc example
+        BlockRealMatrix matrix = new BlockRealMatrix
+            (new double[][] {{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 0, 1 , 2}});
+        matrix.setSubMatrix(new double[][] {{3, 4}, {5, 6}}, 1, 1);
+        expected = new BlockRealMatrix
+            (new double[][] {{1, 2, 3, 4}, {5, 3, 4, 8}, {9, 5 ,6, 2}});
+        assertEquals(expected, matrix);
+
+        // dimension overflow
+        try {
+            m.setSubMatrix(testData,1,1);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException e) {
+            // expected
+        }
+        // dimension underflow
+        try {
+            m.setSubMatrix(testData,-1,1);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException e) {
+            // expected
+        }
+        try {
+            m.setSubMatrix(testData,1,-1);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException e) {
+            // expected
+        }
+
+        // null
+        try {
+            m.setSubMatrix(null,1,1);
+            fail("expecting NullPointerException");
+        } catch (NullPointerException e) {
+            // expected
+        }
+
+        // ragged
+        try {
+            m.setSubMatrix(new double[][] {{1}, {2, 3}}, 0, 0);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        // empty
+        try {
+            m.setSubMatrix(new double[][] {{}}, 0, 0);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+    }
+
+    public void testWalk() throws MatrixVisitorException {
+        int rows    = 150;
+        int columns = 75;
+
+        RealMatrix m = new BlockRealMatrix(rows, columns);
+        m.walkInRowOrder(new SetVisitor());
+        GetVisitor getVisitor = new GetVisitor();
+        m.walkInOptimizedOrder(getVisitor);
+        assertEquals(rows * columns, getVisitor.getCount());
+
+        m = new BlockRealMatrix(rows, columns);
+        m.walkInRowOrder(new SetVisitor(), 1, rows - 2, 1, columns - 2);
+        getVisitor = new GetVisitor();
+        m.walkInOptimizedOrder(getVisitor, 1, rows - 2, 1, columns - 2);
+        assertEquals((rows - 2) * (columns - 2), getVisitor.getCount());
+        for (int i = 0; i < rows; ++i) {
+            assertEquals(0.0, m.getEntry(i, 0), 0);
+            assertEquals(0.0, m.getEntry(i, columns - 1), 0);
+        }
+        for (int j = 0; j < columns; ++j) {
+            assertEquals(0.0, m.getEntry(0, j), 0);
+            assertEquals(0.0, m.getEntry(rows - 1, j), 0);
+        }
+
+        m = new BlockRealMatrix(rows, columns);
+        m.walkInColumnOrder(new SetVisitor());
+        getVisitor = new GetVisitor();
+        m.walkInOptimizedOrder(getVisitor);
+        assertEquals(rows * columns, getVisitor.getCount());
+
+        m = new BlockRealMatrix(rows, columns);
+        m.walkInColumnOrder(new SetVisitor(), 1, rows - 2, 1, columns - 2);
+        getVisitor = new GetVisitor();
+        m.walkInOptimizedOrder(getVisitor, 1, rows - 2, 1, columns - 2);
+        assertEquals((rows - 2) * (columns - 2), getVisitor.getCount());
+        for (int i = 0; i < rows; ++i) {
+            assertEquals(0.0, m.getEntry(i, 0), 0);
+            assertEquals(0.0, m.getEntry(i, columns - 1), 0);
+        }
+        for (int j = 0; j < columns; ++j) {
+            assertEquals(0.0, m.getEntry(0, j), 0);
+            assertEquals(0.0, m.getEntry(rows - 1, j), 0);
+        }
+
+        m = new BlockRealMatrix(rows, columns);
+        m.walkInOptimizedOrder(new SetVisitor());
+        getVisitor = new GetVisitor();
+        m.walkInRowOrder(getVisitor);
+        assertEquals(rows * columns, getVisitor.getCount());
+
+        m = new BlockRealMatrix(rows, columns);
+        m.walkInOptimizedOrder(new SetVisitor(), 1, rows - 2, 1, columns - 2);
+        getVisitor = new GetVisitor();
+        m.walkInRowOrder(getVisitor, 1, rows - 2, 1, columns - 2);
+        assertEquals((rows - 2) * (columns - 2), getVisitor.getCount());
+        for (int i = 0; i < rows; ++i) {
+            assertEquals(0.0, m.getEntry(i, 0), 0);
+            assertEquals(0.0, m.getEntry(i, columns - 1), 0);
+        }
+        for (int j = 0; j < columns; ++j) {
+            assertEquals(0.0, m.getEntry(0, j), 0);
+            assertEquals(0.0, m.getEntry(rows - 1, j), 0);
+        }
+
+        m = new BlockRealMatrix(rows, columns);
+        m.walkInOptimizedOrder(new SetVisitor());
+        getVisitor = new GetVisitor();
+        m.walkInColumnOrder(getVisitor);
+        assertEquals(rows * columns, getVisitor.getCount());
+
+        m = new BlockRealMatrix(rows, columns);
+        m.walkInOptimizedOrder(new SetVisitor(), 1, rows - 2, 1, columns - 2);
+        getVisitor = new GetVisitor();
+        m.walkInColumnOrder(getVisitor, 1, rows - 2, 1, columns - 2);
+        assertEquals((rows - 2) * (columns - 2), getVisitor.getCount());
+        for (int i = 0; i < rows; ++i) {
+            assertEquals(0.0, m.getEntry(i, 0), 0);
+            assertEquals(0.0, m.getEntry(i, columns - 1), 0);
+        }
+        for (int j = 0; j < columns; ++j) {
+            assertEquals(0.0, m.getEntry(0, j), 0);
+            assertEquals(0.0, m.getEntry(rows - 1, j), 0);
+        }
+
+    }
+
+    public void testSerial()  {
+        BlockRealMatrix m = new BlockRealMatrix(testData);
+        assertEquals(m,TestUtils.serializeAndRecover(m));
+    }
+
+    private static class SetVisitor extends DefaultRealMatrixChangingVisitor {
+        @Override
+        public double visit(int i, int j, double value) {
+            return i + j / 1024.0;
+        }
+    }
+
+    private static class GetVisitor extends DefaultRealMatrixPreservingVisitor {
+        private int count = 0;
+        @Override
+        public void visit(int i, int j, double value) {
+            ++count;
+            assertEquals(i + j / 1024.0, value, 0.0);
+        }
+        public int getCount() {
+            return count;
+        }
+    }
+
+    //--------------- -----------------Protected methods
+
+    /** verifies that two matrices are close (1-norm) */
+    protected void assertClose(RealMatrix m, RealMatrix n, double tolerance) {
+        assertTrue(m.subtract(n).getNorm() < tolerance);
+    }
+
+    /** verifies that two vectors are close (sup norm) */
+    protected void assertClose(double[] m, double[] n, double tolerance) {
+        if (m.length != n.length) {
+            fail("vectors not same length");
+        }
+        for (int i = 0; i < m.length; i++) {
+            assertEquals(m[i], n[i], tolerance);
+        }
+    }
+
+    private BlockRealMatrix createRandomMatrix(Random r, int rows, int columns) {
+        BlockRealMatrix m = new BlockRealMatrix(rows, columns);
+        for (int i = 0; i < rows; ++i) {
+            for (int j = 0; j < columns; ++j) {
+                m.setEntry(i, j, 200 * r.nextDouble() - 100);
+            }
+        }
+        return m;
+    }
+
+}
+
diff --git a/src/test/java/org/apache/commons/math/linear/CholeskyDecompositionImplTest.java b/src/test/java/org/apache/commons/math/linear/CholeskyDecompositionImplTest.java
new file mode 100644
index 0000000..1b76eea
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/linear/CholeskyDecompositionImplTest.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.linear;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.linear.CholeskyDecomposition;
+import org.apache.commons.math.linear.CholeskyDecompositionImpl;
+import org.apache.commons.math.linear.MatrixUtils;
+import org.apache.commons.math.linear.NonSquareMatrixException;
+import org.apache.commons.math.linear.NotPositiveDefiniteMatrixException;
+import org.apache.commons.math.linear.NotSymmetricMatrixException;
+import org.apache.commons.math.linear.RealMatrix;
+import org.junit.Test;
+
+public class CholeskyDecompositionImplTest {
+
+    private double[][] testData = new double[][] {
+            {  1,  2,   4,   7,  11 },
+            {  2, 13,  23,  38,  58 },
+            {  4, 23,  77, 122, 182 },
+            {  7, 38, 122, 294, 430 },
+            { 11, 58, 182, 430, 855 }
+    };
+
+    /** test dimensions */
+    @Test
+    public void testDimensions() throws MathException {
+        CholeskyDecomposition llt =
+            new CholeskyDecompositionImpl(MatrixUtils.createRealMatrix(testData));
+        assertEquals(testData.length, llt.getL().getRowDimension());
+        assertEquals(testData.length, llt.getL().getColumnDimension());
+        assertEquals(testData.length, llt.getLT().getRowDimension());
+        assertEquals(testData.length, llt.getLT().getColumnDimension());
+    }
+
+    /** test non-square matrix */
+    @Test(expected = NonSquareMatrixException.class)
+    public void testNonSquare() throws MathException {
+        new CholeskyDecompositionImpl(MatrixUtils.createRealMatrix(new double[3][2]));
+    }
+
+    /** test non-symmetric matrix */
+    @Test(expected = NotSymmetricMatrixException.class)
+    public void testNotSymmetricMatrixException() throws MathException {
+        double[][] changed = testData.clone();
+        changed[0][changed[0].length - 1] += 1.0e-5;
+        new CholeskyDecompositionImpl(MatrixUtils.createRealMatrix(changed));
+    }
+
+    /** test non positive definite matrix */
+    @Test(expected = NotPositiveDefiniteMatrixException.class)
+    public void testNotPositiveDefinite() throws MathException {
+        new CholeskyDecompositionImpl(MatrixUtils.createRealMatrix(new double[][] {
+                { 14, 11, 13, 15, 24 },
+                { 11, 34, 13, 8,  25 },
+                { 13, 13, 14, 15, 21 },
+                { 15, 8,  15, 18, 23 },
+                { 24, 25, 21, 23, 45 }
+        }));
+    }
+
+    @Test(expected = NotPositiveDefiniteMatrixException.class)
+    public void testMath274() throws MathException {
+        new CholeskyDecompositionImpl(MatrixUtils.createRealMatrix(new double[][] {
+                { 0.40434286, -0.09376327, 0.30328980, 0.04909388 },
+                {-0.09376327,  0.10400408, 0.07137959, 0.04762857 },
+                { 0.30328980,  0.07137959, 0.30458776, 0.04882449 },
+                { 0.04909388,  0.04762857, 0.04882449, 0.07543265 }
+
+        }));
+    }
+
+    /** test A = LLT */
+    @Test
+    public void testAEqualLLT() throws MathException {
+        RealMatrix matrix = MatrixUtils.createRealMatrix(testData);
+        CholeskyDecomposition llt = new CholeskyDecompositionImpl(matrix);
+        RealMatrix l  = llt.getL();
+        RealMatrix lt = llt.getLT();
+        double norm = l.multiply(lt).subtract(matrix).getNorm();
+        assertEquals(0, norm, 1.0e-15);
+    }
+
+    /** test that L is lower triangular */
+    @Test
+    public void testLLowerTriangular() throws MathException {
+        RealMatrix matrix = MatrixUtils.createRealMatrix(testData);
+        RealMatrix l = new CholeskyDecompositionImpl(matrix).getL();
+        for (int i = 0; i < l.getRowDimension(); i++) {
+            for (int j = i + 1; j < l.getColumnDimension(); j++) {
+                assertEquals(0.0, l.getEntry(i, j), 0.0);
+            }
+        }
+    }
+
+    /** test that LT is transpose of L */
+    @Test
+    public void testLTTransposed() throws MathException {
+        RealMatrix matrix = MatrixUtils.createRealMatrix(testData);
+        CholeskyDecomposition llt = new CholeskyDecompositionImpl(matrix);
+        RealMatrix l  = llt.getL();
+        RealMatrix lt = llt.getLT();
+        double norm = l.subtract(lt.transpose()).getNorm();
+        assertEquals(0, norm, 1.0e-15);
+    }
+
+    /** test matrices values */
+    @Test
+    public void testMatricesValues() throws MathException {
+        RealMatrix lRef = MatrixUtils.createRealMatrix(new double[][] {
+                {  1,  0,  0,  0,  0 },
+                {  2,  3,  0,  0,  0 },
+                {  4,  5,  6,  0,  0 },
+                {  7,  8,  9, 10,  0 },
+                { 11, 12, 13, 14, 15 }
+        });
+       CholeskyDecomposition llt =
+            new CholeskyDecompositionImpl(MatrixUtils.createRealMatrix(testData));
+
+        // check values against known references
+        RealMatrix l = llt.getL();
+        assertEquals(0, l.subtract(lRef).getNorm(), 1.0e-13);
+        RealMatrix lt = llt.getLT();
+        assertEquals(0, lt.subtract(lRef.transpose()).getNorm(), 1.0e-13);
+
+        // check the same cached instance is returned the second time
+        assertTrue(l  == llt.getL());
+        assertTrue(lt == llt.getLT());
+
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/linear/CholeskySolverTest.java b/src/test/java/org/apache/commons/math/linear/CholeskySolverTest.java
new file mode 100644
index 0000000..3b7e994
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/linear/CholeskySolverTest.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.linear;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.math.MathException;
+
+public class CholeskySolverTest extends TestCase {
+
+    private double[][] testData = new double[][] {
+            {  1,  2,   4,   7,  11 },
+            {  2, 13,  23,  38,  58 },
+            {  4, 23,  77, 122, 182 },
+            {  7, 38, 122, 294, 430 },
+            { 11, 58, 182, 430, 855 }
+    };
+
+    public CholeskySolverTest(String name) {
+        super(name);
+    }
+
+    /** test solve dimension errors */
+    public void testSolveDimensionErrors() throws MathException {
+        DecompositionSolver solver =
+            new CholeskyDecompositionImpl(MatrixUtils.createRealMatrix(testData)).getSolver();
+        RealMatrix b = MatrixUtils.createRealMatrix(new double[2][2]);
+        try {
+            solver.solve(b);
+            fail("an exception should have been thrown");
+        } catch (IllegalArgumentException iae) {
+            // expected behavior
+        }
+        try {
+            solver.solve(b.getColumn(0));
+            fail("an exception should have been thrown");
+        } catch (IllegalArgumentException iae) {
+            // expected behavior
+        }
+        try {
+            solver.solve(new ArrayRealVectorTest.RealVectorTestImpl(b.getColumn(0)));
+            fail("an exception should have been thrown");
+        } catch (IllegalArgumentException iae) {
+            // expected behavior
+        }
+    }
+
+    /** test solve */
+    public void testSolve() throws MathException {
+        DecompositionSolver solver =
+            new CholeskyDecompositionImpl(MatrixUtils.createRealMatrix(testData)).getSolver();
+        RealMatrix b = MatrixUtils.createRealMatrix(new double[][] {
+                {   78,  -13,    1 },
+                {  414,  -62,   -1 },
+                { 1312, -202,  -37 },
+                { 2989, -542,  145 },
+                { 5510, -1465, 201 }
+        });
+        RealMatrix xRef = MatrixUtils.createRealMatrix(new double[][] {
+                { 1,  0,  1 },
+                { 0,  1,  1 },
+                { 2,  1, -4 },
+                { 2,  2,  2 },
+                { 5, -3,  0 }
+        });
+
+        // using RealMatrix
+        assertEquals(0, solver.solve(b).subtract(xRef).getNorm(), 1.0e-13);
+
+        // using double[]
+        for (int i = 0; i < b.getColumnDimension(); ++i) {
+            assertEquals(0,
+                         new ArrayRealVector(solver.solve(b.getColumn(i))).subtract(xRef.getColumnVector(i)).getNorm(),
+                         1.0e-13);
+        }
+
+        // using ArrayRealVector
+        for (int i = 0; i < b.getColumnDimension(); ++i) {
+            assertEquals(0,
+                         solver.solve(b.getColumnVector(i)).subtract(xRef.getColumnVector(i)).getNorm(),
+                         1.0e-13);
+        }
+
+        // using RealVector with an alternate implementation
+        for (int i = 0; i < b.getColumnDimension(); ++i) {
+            ArrayRealVectorTest.RealVectorTestImpl v =
+                new ArrayRealVectorTest.RealVectorTestImpl(b.getColumn(i));
+            assertEquals(0,
+                         solver.solve(v).subtract(xRef.getColumnVector(i)).getNorm(),
+                         1.0e-13);
+        }
+
+    }
+
+    /** test determinant */
+    public void testDeterminant() throws MathException {
+        assertEquals(7290000.0, getDeterminant(MatrixUtils.createRealMatrix(testData)), 1.0e-15);
+    }
+
+    private double getDeterminant(RealMatrix m) throws MathException {
+        return new CholeskyDecompositionImpl(m).getDeterminant();
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/linear/EigenDecompositionImplTest.java b/src/test/java/org/apache/commons/math/linear/EigenDecompositionImplTest.java
new file mode 100644
index 0000000..6a4bf75
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/linear/EigenDecompositionImplTest.java
@@ -0,0 +1,533 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 java.util.Random;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.math.util.FastMath;
+import org.apache.commons.math.util.MathUtils;
+
+public class EigenDecompositionImplTest extends TestCase {
+
+    private double[] refValues;
+    private RealMatrix matrix;
+
+    public EigenDecompositionImplTest(String name) {
+        super(name);
+    }
+
+    public void testDimension1() {
+        RealMatrix matrix =
+            MatrixUtils.createRealMatrix(new double[][] { { 1.5 } });
+        EigenDecomposition ed = new EigenDecompositionImpl(matrix, MathUtils.SAFE_MIN);
+        assertEquals(1.5, ed.getRealEigenvalue(0), 1.0e-15);
+    }
+
+    public void testDimension2() {
+        RealMatrix matrix =
+            MatrixUtils.createRealMatrix(new double[][] {
+                    { 59.0, 12.0 },
+                    { 12.0, 66.0 }
+            });
+        EigenDecomposition ed = new EigenDecompositionImpl(matrix, MathUtils.SAFE_MIN);
+        assertEquals(75.0, ed.getRealEigenvalue(0), 1.0e-15);
+        assertEquals(50.0, ed.getRealEigenvalue(1), 1.0e-15);
+    }
+
+    public void testDimension3() {
+        RealMatrix matrix =
+            MatrixUtils.createRealMatrix(new double[][] {
+                                   {  39632.0, -4824.0, -16560.0 },
+                                   {  -4824.0,  8693.0,   7920.0 },
+                                   { -16560.0,  7920.0,  17300.0 }
+                               });
+        EigenDecomposition ed = new EigenDecompositionImpl(matrix, MathUtils.SAFE_MIN);
+        assertEquals(50000.0, ed.getRealEigenvalue(0), 3.0e-11);
+        assertEquals(12500.0, ed.getRealEigenvalue(1), 3.0e-11);
+        assertEquals( 3125.0, ed.getRealEigenvalue(2), 3.0e-11);
+    }
+
+    public void testDimension3MultipleRoot() {
+        RealMatrix matrix =
+            MatrixUtils.createRealMatrix(new double[][] {
+                    {  5,   10,   15 },
+                    { 10,   20,   30 },
+                    { 15,   30,   45 }
+            });
+        EigenDecomposition ed = new EigenDecompositionImpl(matrix, MathUtils.SAFE_MIN);
+        assertEquals(70.0, ed.getRealEigenvalue(0), 3.0e-11);
+        assertEquals(0.0,  ed.getRealEigenvalue(1), 3.0e-11);
+        assertEquals(0.0,  ed.getRealEigenvalue(2), 3.0e-11);
+    }
+
+    public void testDimension4WithSplit() {
+        RealMatrix matrix =
+            MatrixUtils.createRealMatrix(new double[][] {
+                                   {  0.784, -0.288,  0.000,  0.000 },
+                                   { -0.288,  0.616,  0.000,  0.000 },
+                                   {  0.000,  0.000,  0.164, -0.048 },
+                                   {  0.000,  0.000, -0.048,  0.136 }
+                               });
+        EigenDecomposition ed = new EigenDecompositionImpl(matrix, MathUtils.SAFE_MIN);
+        assertEquals(1.0, ed.getRealEigenvalue(0), 1.0e-15);
+        assertEquals(0.4, ed.getRealEigenvalue(1), 1.0e-15);
+        assertEquals(0.2, ed.getRealEigenvalue(2), 1.0e-15);
+        assertEquals(0.1, ed.getRealEigenvalue(3), 1.0e-15);
+    }
+
+    public void testDimension4WithoutSplit() {
+        RealMatrix matrix =
+            MatrixUtils.createRealMatrix(new double[][] {
+                                   {  0.5608, -0.2016,  0.1152, -0.2976 },
+                                   { -0.2016,  0.4432, -0.2304,  0.1152 },
+                                   {  0.1152, -0.2304,  0.3088, -0.1344 },
+                                   { -0.2976,  0.1152, -0.1344,  0.3872 }
+                               });
+        EigenDecomposition ed = new EigenDecompositionImpl(matrix, MathUtils.SAFE_MIN);
+        assertEquals(1.0, ed.getRealEigenvalue(0), 1.0e-15);
+        assertEquals(0.4, ed.getRealEigenvalue(1), 1.0e-15);
+        assertEquals(0.2, ed.getRealEigenvalue(2), 1.0e-15);
+        assertEquals(0.1, ed.getRealEigenvalue(3), 1.0e-15);
+    }
+
+    // the following test triggered an ArrayIndexOutOfBoundsException in commons-math 2.0
+    public void testMath308() {
+
+        double[] mainTridiagonal = {
+            22.330154644539597, 46.65485522478641, 17.393672330044705, 54.46687435351116, 80.17800767709437
+        };
+        double[] secondaryTridiagonal = {
+            13.04450406501361, -5.977590941539671, 2.9040909856707517, 7.1570352792841225
+        };
+
+        // the reference values have been computed using routine DSTEMR
+        // from the fortran library LAPACK version 3.2.1
+        double[] refEigenValues = {
+            82.044413207204002, 53.456697699894512, 52.536278520113882, 18.847969733754262, 14.138204224043099
+        };
+        RealVector[] refEigenVectors = {
+            new ArrayRealVector(new double[] { -0.000462690386766, -0.002118073109055,  0.011530080757413,  0.252322434584915,  0.967572088232592 }),
+            new ArrayRealVector(new double[] {  0.314647769490148,  0.750806415553905, -0.167700312025760, -0.537092972407375,  0.143854968127780 }),
+            new ArrayRealVector(new double[] {  0.222368839324646,  0.514921891363332, -0.021377019336614,  0.801196801016305, -0.207446991247740 }),
+            new ArrayRealVector(new double[] { -0.713933751051495,  0.190582113553930, -0.671410443368332,  0.056056055955050, -0.006541576993581 }),
+            new ArrayRealVector(new double[] { -0.584677060845929,  0.367177264979103,  0.721453187784497, -0.052971054621812,  0.005740715188257 })
+        };
+
+        EigenDecomposition decomposition =
+            new EigenDecompositionImpl(mainTridiagonal, secondaryTridiagonal, MathUtils.SAFE_MIN);
+
+        double[] eigenValues = decomposition.getRealEigenvalues();
+        for (int i = 0; i < refEigenValues.length; ++i) {
+            assertEquals(refEigenValues[i], eigenValues[i], 1.0e-5);
+            assertEquals(0, refEigenVectors[i].subtract(decomposition.getEigenvector(i)).getNorm(), 2.0e-7);
+        }
+
+    }
+
+    public void testMathpbx02() {
+
+        double[] mainTridiagonal = {
+              7484.860960227216, 18405.28129035345, 13855.225609560746,
+             10016.708722343366, 559.8117399576674, 6750.190788301587,
+                71.21428769782159
+        };
+        double[] secondaryTridiagonal = {
+             -4175.088570476366,1975.7955858241994,5193.178422374075,
+              1995.286659169179,75.34535882933804,-234.0808002076056
+        };
+
+        // the reference values have been computed using routine DSTEMR
+        // from the fortran library LAPACK version 3.2.1
+        double[] refEigenValues = {
+                20654.744890306974412,16828.208208485466457,
+                6893.155912634994820,6757.083016675340332,
+                5887.799885688558788,64.309089923240379,
+                57.992628792736340
+        };
+        RealVector[] refEigenVectors = {
+                new ArrayRealVector(new double[] {-0.270356342026904, 0.852811091326997, 0.399639490702077, 0.198794657813990, 0.019739323307666, 0.000106983022327, -0.000001216636321}),
+                new ArrayRealVector(new double[] {0.179995273578326,-0.402807848153042,0.701870993525734,0.555058211014888,0.068079148898236,0.000509139115227,-0.000007112235617}),
+                new ArrayRealVector(new double[] {-0.399582721284727,-0.056629954519333,-0.514406488522827,0.711168164518580,0.225548081276367,0.125943999652923,-0.004321507456014}),
+                new ArrayRealVector(new double[] {0.058515721572821,0.010200130057739,0.063516274916536,-0.090696087449378,-0.017148420432597,0.991318870265707,-0.034707338554096}),
+                new ArrayRealVector(new double[] {0.855205995537564,0.327134656629775,-0.265382397060548,0.282690729026706,0.105736068025572,-0.009138126622039,0.000367751821196}),
+                new ArrayRealVector(new double[] {-0.002913069901144,-0.005177515777101,0.041906334478672,-0.109315918416258,0.436192305456741,0.026307315639535,0.891797507436344}),
+                new ArrayRealVector(new double[] {-0.005738311176435,-0.010207611670378,0.082662420517928,-0.215733886094368,0.861606487840411,-0.025478530652759,-0.451080697503958})
+        };
+
+        // the following line triggers the exception
+        EigenDecomposition decomposition =
+            new EigenDecompositionImpl(mainTridiagonal, secondaryTridiagonal, MathUtils.SAFE_MIN);
+
+        double[] eigenValues = decomposition.getRealEigenvalues();
+        for (int i = 0; i < refEigenValues.length; ++i) {
+            assertEquals(refEigenValues[i], eigenValues[i], 1.0e-3);
+            if (refEigenVectors[i].dotProduct(decomposition.getEigenvector(i)) < 0) {
+                assertEquals(0, refEigenVectors[i].add(decomposition.getEigenvector(i)).getNorm(), 1.0e-5);
+            } else {
+                assertEquals(0, refEigenVectors[i].subtract(decomposition.getEigenvector(i)).getNorm(), 1.0e-5);
+            }
+        }
+
+    }
+
+    public void testMathpbx03() {
+
+        double[] mainTridiagonal = {
+            1809.0978259647177,3395.4763425956166,1832.1894584712693,3804.364873592377,
+            806.0482458637571,2403.656427234185,28.48691431556015
+        };
+        double[] secondaryTridiagonal = {
+            -656.8932064545833,-469.30804108920734,-1021.7714889369421,
+            -1152.540497328983,-939.9765163817368,-12.885877015422391
+        };
+
+        // the reference values have been computed using routine DSTEMR
+        // from the fortran library LAPACK version 3.2.1
+        double[] refEigenValues = {
+            4603.121913685183245,3691.195818048970978,2743.442955402465032,1657.596442107321764,
+            1336.797819095331306,30.129865209677519,17.035352085224986
+        };
+
+        RealVector[] refEigenVectors = {
+            new ArrayRealVector(new double[] {-0.036249830202337,0.154184732411519,-0.346016328392363,0.867540105133093,-0.294483395433451,0.125854235969548,-0.000354507444044}),
+            new ArrayRealVector(new double[] {-0.318654191697157,0.912992309960507,-0.129270874079777,-0.184150038178035,0.096521712579439,-0.070468788536461,0.000247918177736}),
+            new ArrayRealVector(new double[] {-0.051394668681147,0.073102235876933,0.173502042943743,-0.188311980310942,-0.327158794289386,0.905206581432676,-0.004296342252659}),
+            new ArrayRealVector(new double[] {0.838150199198361,0.193305209055716,-0.457341242126146,-0.166933875895419,0.094512811358535,0.119062381338757,-0.000941755685226}),
+            new ArrayRealVector(new double[] {0.438071395458547,0.314969169786246,0.768480630802146,0.227919171600705,-0.193317045298647,-0.170305467485594,0.001677380536009}),
+            new ArrayRealVector(new double[] {-0.003726503878741,-0.010091946369146,-0.067152015137611,-0.113798146542187,-0.313123000097908,-0.118940107954918,0.932862311396062}),
+            new ArrayRealVector(new double[] {0.009373003194332,0.025570377559400,0.170955836081348,0.291954519805750,0.807824267665706,0.320108347088646,0.360202112392266}),
+        };
+
+        // the following line triggers the exception
+        EigenDecomposition decomposition =
+            new EigenDecompositionImpl(mainTridiagonal, secondaryTridiagonal, MathUtils.SAFE_MIN);
+
+        double[] eigenValues = decomposition.getRealEigenvalues();
+        for (int i = 0; i < refEigenValues.length; ++i) {
+            assertEquals(refEigenValues[i], eigenValues[i], 1.0e-4);
+            if (refEigenVectors[i].dotProduct(decomposition.getEigenvector(i)) < 0) {
+                assertEquals(0, refEigenVectors[i].add(decomposition.getEigenvector(i)).getNorm(), 1.0e-5);
+            } else {
+                assertEquals(0, refEigenVectors[i].subtract(decomposition.getEigenvector(i)).getNorm(), 1.0e-5);
+            }
+        }
+
+    }
+
+    /** test a matrix already in tridiagonal form. */
+    public void testTridiagonal() {
+        Random r = new Random(4366663527842l);
+        double[] ref = new double[30];
+        for (int i = 0; i < ref.length; ++i) {
+            if (i < 5) {
+                ref[i] = 2 * r.nextDouble() - 1;
+            } else {
+                ref[i] = 0.0001 * r.nextDouble() + 6;
+            }
+        }
+        Arrays.sort(ref);
+        TriDiagonalTransformer t =
+            new TriDiagonalTransformer(createTestMatrix(r, ref));
+        EigenDecomposition ed =
+            new EigenDecompositionImpl(t.getMainDiagonalRef(),
+                                       t.getSecondaryDiagonalRef(),
+                                       MathUtils.SAFE_MIN);
+        double[] eigenValues = ed.getRealEigenvalues();
+        assertEquals(ref.length, eigenValues.length);
+        for (int i = 0; i < ref.length; ++i) {
+            assertEquals(ref[ref.length - i - 1], eigenValues[i], 2.0e-14);
+        }
+
+    }
+
+    /** test dimensions */
+    public void testDimensions() {
+        final int m = matrix.getRowDimension();
+        EigenDecomposition ed = new EigenDecompositionImpl(matrix, MathUtils.SAFE_MIN);
+        assertEquals(m, ed.getV().getRowDimension());
+        assertEquals(m, ed.getV().getColumnDimension());
+        assertEquals(m, ed.getD().getColumnDimension());
+        assertEquals(m, ed.getD().getColumnDimension());
+        assertEquals(m, ed.getVT().getRowDimension());
+        assertEquals(m, ed.getVT().getColumnDimension());
+    }
+
+    /** test eigenvalues */
+    public void testEigenvalues() {
+        EigenDecomposition ed = new EigenDecompositionImpl(matrix, MathUtils.SAFE_MIN);
+        double[] eigenValues = ed.getRealEigenvalues();
+        assertEquals(refValues.length, eigenValues.length);
+        for (int i = 0; i < refValues.length; ++i) {
+            assertEquals(refValues[i], eigenValues[i], 3.0e-15);
+        }
+    }
+
+    /** test eigenvalues for a big matrix. */
+    public void testBigMatrix() {
+        Random r = new Random(17748333525117l);
+        double[] bigValues = new double[200];
+        for (int i = 0; i < bigValues.length; ++i) {
+            bigValues[i] = 2 * r.nextDouble() - 1;
+        }
+        Arrays.sort(bigValues);
+        EigenDecomposition ed =
+            new EigenDecompositionImpl(createTestMatrix(r, bigValues), MathUtils.SAFE_MIN);
+        double[] eigenValues = ed.getRealEigenvalues();
+        assertEquals(bigValues.length, eigenValues.length);
+        for (int i = 0; i < bigValues.length; ++i) {
+            assertEquals(bigValues[bigValues.length - i - 1], eigenValues[i], 2.0e-14);
+        }
+    }
+
+    /** test eigenvectors */
+    public void testEigenvectors() {
+        EigenDecomposition ed = new EigenDecompositionImpl(matrix, MathUtils.SAFE_MIN);
+        for (int i = 0; i < matrix.getRowDimension(); ++i) {
+            double lambda = ed.getRealEigenvalue(i);
+            RealVector v  = ed.getEigenvector(i);
+            RealVector mV = matrix.operate(v);
+            assertEquals(0, mV.subtract(v.mapMultiplyToSelf(lambda)).getNorm(), 1.0e-13);
+        }
+    }
+
+    /** test A = VDVt */
+    public void testAEqualVDVt() {
+        EigenDecomposition ed = new EigenDecompositionImpl(matrix, MathUtils.SAFE_MIN);
+        RealMatrix v  = ed.getV();
+        RealMatrix d  = ed.getD();
+        RealMatrix vT = ed.getVT();
+        double norm = v.multiply(d).multiply(vT).subtract(matrix).getNorm();
+        assertEquals(0, norm, 6.0e-13);
+    }
+
+    /** test that V is orthogonal */
+    public void testVOrthogonal() {
+        RealMatrix v = new EigenDecompositionImpl(matrix, MathUtils.SAFE_MIN).getV();
+        RealMatrix vTv = v.transpose().multiply(v);
+        RealMatrix id  = MatrixUtils.createRealIdentityMatrix(vTv.getRowDimension());
+        assertEquals(0, vTv.subtract(id).getNorm(), 2.0e-13);
+    }
+
+    /** test diagonal matrix */
+    public void testDiagonal() {
+        double[] diagonal = new double[] { -3.0, -2.0, 2.0, 5.0 };
+        RealMatrix m = createDiagonalMatrix(diagonal, diagonal.length, diagonal.length);
+        EigenDecomposition ed = new EigenDecompositionImpl(m, MathUtils.SAFE_MIN);
+        assertEquals(diagonal[0], ed.getRealEigenvalue(3), 2.0e-15);
+        assertEquals(diagonal[1], ed.getRealEigenvalue(2), 2.0e-15);
+        assertEquals(diagonal[2], ed.getRealEigenvalue(1), 2.0e-15);
+        assertEquals(diagonal[3], ed.getRealEigenvalue(0), 2.0e-15);
+    }
+
+    /**
+     * Matrix with eigenvalues {8, -1, -1}
+     */
+    public void testRepeatedEigenvalue() {
+        RealMatrix repeated = MatrixUtils.createRealMatrix(new double[][] {
+                {3,  2,  4},
+                {2,  0,  2},
+                {4,  2,  3}
+        });
+        EigenDecomposition ed = new EigenDecompositionImpl(repeated, MathUtils.SAFE_MIN);
+        checkEigenValues((new double[] {8, -1, -1}), ed, 1E-12);
+        checkEigenVector((new double[] {2, 1, 2}), ed, 1E-12);
+    }
+
+    /**
+     * Matrix with eigenvalues {2, 0, 12}
+     */
+    public void testDistinctEigenvalues() {
+        RealMatrix distinct = MatrixUtils.createRealMatrix(new double[][] {
+                {3, 1, -4},
+                {1, 3, -4},
+                {-4, -4, 8}
+        });
+        EigenDecomposition ed = new EigenDecompositionImpl(distinct, MathUtils.SAFE_MIN);
+        checkEigenValues((new double[] {2, 0, 12}), ed, 1E-12);
+        checkEigenVector((new double[] {1, -1, 0}), ed, 1E-12);
+        checkEigenVector((new double[] {1, 1, 1}), ed, 1E-12);
+        checkEigenVector((new double[] {-1, -1, 2}), ed, 1E-12);
+    }
+
+    /**
+     * Verifies operation on indefinite matrix
+     */
+    public void testZeroDivide() {
+        RealMatrix indefinite = MatrixUtils.createRealMatrix(new double [][] {
+                { 0.0, 1.0, -1.0 },
+                { 1.0, 1.0, 0.0 },
+                { -1.0,0.0, 1.0 }
+        });
+        EigenDecomposition ed = new EigenDecompositionImpl(indefinite, MathUtils.SAFE_MIN);
+        checkEigenValues((new double[] {2, 1, -1}), ed, 1E-12);
+        double isqrt3 = 1/FastMath.sqrt(3.0);
+        checkEigenVector((new double[] {isqrt3,isqrt3,-isqrt3}), ed, 1E-12);
+        double isqrt2 = 1/FastMath.sqrt(2.0);
+        checkEigenVector((new double[] {0.0,-isqrt2,-isqrt2}), ed, 1E-12);
+        double isqrt6 = 1/FastMath.sqrt(6.0);
+        checkEigenVector((new double[] {2*isqrt6,-isqrt6,isqrt6}), ed, 1E-12);
+    }
+    /**
+     * Verifies that the given EigenDecomposition has eigenvalues equivalent to
+     * the targetValues, ignoring the order of the values and allowing
+     * values to differ by tolerance.
+     */
+    protected void checkEigenValues(double[] targetValues,
+            EigenDecomposition ed, double tolerance) {
+        double[] observed = ed.getRealEigenvalues();
+        for (int i = 0; i < observed.length; i++) {
+            assertTrue(isIncludedValue(observed[i], targetValues, tolerance));
+            assertTrue(isIncludedValue(targetValues[i], observed, tolerance));
+        }
+    }
+
+
+    /**
+     * Returns true iff there is an entry within tolerance of value in
+     * searchArray.
+     */
+    private boolean isIncludedValue(double value, double[] searchArray,
+            double tolerance) {
+       boolean found = false;
+       int i = 0;
+       while (!found && i < searchArray.length) {
+           if (FastMath.abs(value - searchArray[i]) < tolerance) {
+               found = true;
+           }
+           i++;
+       }
+       return found;
+    }
+
+    /**
+     * Returns true iff eigenVector is a scalar multiple of one of the columns
+     * of ed.getV().  Does not try linear combinations - i.e., should only be
+     * used to find vectors in one-dimensional eigenspaces.
+     */
+    protected void checkEigenVector(double[] eigenVector,
+            EigenDecomposition ed, double tolerance) {
+        assertTrue(isIncludedColumn(eigenVector, ed.getV(), tolerance));
+    }
+
+    /**
+     * Returns true iff there is a column that is a scalar multiple of column
+     * in searchMatrix (modulo tolerance)
+     */
+    private boolean isIncludedColumn(double[] column, RealMatrix searchMatrix,
+            double tolerance) {
+        boolean found = false;
+        int i = 0;
+        while (!found && i < searchMatrix.getColumnDimension()) {
+            double multiplier = 1.0;
+            boolean matching = true;
+            int j = 0;
+            while (matching && j < searchMatrix.getRowDimension()) {
+                double colEntry = searchMatrix.getEntry(j, i);
+                // Use the first entry where both are non-zero as scalar
+                if (FastMath.abs(multiplier - 1.0) <= FastMath.ulp(1.0) && FastMath.abs(colEntry) > 1E-14
+                        && FastMath.abs(column[j]) > 1e-14) {
+                    multiplier = colEntry / column[j];
+                }
+                if (FastMath.abs(column[j] * multiplier - colEntry) > tolerance) {
+                    matching = false;
+                }
+                j++;
+            }
+            found = matching;
+            i++;
+        }
+        return found;
+    }
+
+    @Override
+    public void setUp() {
+        refValues = new double[] {
+                2.003, 2.002, 2.001, 1.001, 1.000, 0.001
+        };
+        matrix = createTestMatrix(new Random(35992629946426l), refValues);
+    }
+
+    @Override
+    public void tearDown() {
+        refValues = null;
+        matrix    = null;
+    }
+
+    static RealMatrix createTestMatrix(final Random r, final double[] eigenValues) {
+        final int n = eigenValues.length;
+        final RealMatrix v = createOrthogonalMatrix(r, n);
+        final RealMatrix d = createDiagonalMatrix(eigenValues, n, n);
+        return v.multiply(d).multiply(v.transpose());
+    }
+
+    public static RealMatrix createOrthogonalMatrix(final Random r, final int size) {
+
+        final double[][] data = new double[size][size];
+
+        for (int i = 0; i < size; ++i) {
+            final double[] dataI = data[i];
+            double norm2 = 0;
+            do {
+
+                // generate randomly row I
+                for (int j = 0; j < size; ++j) {
+                    dataI[j] = 2 * r.nextDouble() - 1;
+                }
+
+                // project the row in the subspace orthogonal to previous rows
+                for (int k = 0; k < i; ++k) {
+                    final double[] dataK = data[k];
+                    double dotProduct = 0;
+                    for (int j = 0; j < size; ++j) {
+                        dotProduct += dataI[j] * dataK[j];
+                    }
+                    for (int j = 0; j < size; ++j) {
+                        dataI[j] -= dotProduct * dataK[j];
+                    }
+                }
+
+                // normalize the row
+                norm2 = 0;
+                for (final double dataIJ : dataI) {
+                    norm2 += dataIJ * dataIJ;
+                }
+                final double inv = 1.0 / FastMath.sqrt(norm2);
+                for (int j = 0; j < size; ++j) {
+                    dataI[j] *= inv;
+                }
+
+            } while (norm2 * size < 0.01);
+        }
+
+        return MatrixUtils.createRealMatrix(data);
+
+    }
+
+    public static RealMatrix createDiagonalMatrix(final double[] diagonal,
+                                                  final int rows, final int columns) {
+        final double[][] dData = new double[rows][columns];
+        for (int i = 0; i < FastMath.min(rows, columns); ++i) {
+            dData[i][i] = diagonal[i];
+        }
+        return MatrixUtils.createRealMatrix(dData);
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/linear/EigenSolverTest.java b/src/test/java/org/apache/commons/math/linear/EigenSolverTest.java
new file mode 100644
index 0000000..18e7c53
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/linear/EigenSolverTest.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.linear;
+
+import java.util.Random;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.math.util.MathUtils;
+
+public class EigenSolverTest extends TestCase {
+
+    private double[] refValues;
+    private RealMatrix matrix;
+
+    public EigenSolverTest(String name) {
+        super(name);
+    }
+
+    /** test non invertible matrix */
+    public void testNonInvertible() {
+        Random r = new Random(9994100315209l);
+        RealMatrix m =
+            EigenDecompositionImplTest.createTestMatrix(r, new double[] { 1.0, 0.0, -1.0, -2.0, -3.0 });
+        DecompositionSolver es = new EigenDecompositionImpl(m, MathUtils.SAFE_MIN).getSolver();
+        assertFalse(es.isNonSingular());
+        try {
+            es.getInverse();
+            fail("an exception should have been thrown");
+        } catch (InvalidMatrixException ime) {
+            // expected behavior
+        }
+    }
+
+    /** test invertible matrix */
+    public void testInvertible() {
+        Random r = new Random(9994100315209l);
+        RealMatrix m =
+            EigenDecompositionImplTest.createTestMatrix(r, new double[] { 1.0, 0.5, -1.0, -2.0, -3.0 });
+        DecompositionSolver es = new EigenDecompositionImpl(m, MathUtils.SAFE_MIN).getSolver();
+        assertTrue(es.isNonSingular());
+        RealMatrix inverse = es.getInverse();
+        RealMatrix error =
+            m.multiply(inverse).subtract(MatrixUtils.createRealIdentityMatrix(m.getRowDimension()));
+        assertEquals(0, error.getNorm(), 4.0e-15);
+    }
+
+    /** test solve dimension errors */
+    public void testSolveDimensionErrors() {
+        DecompositionSolver es = new EigenDecompositionImpl(matrix, MathUtils.SAFE_MIN).getSolver();
+        RealMatrix b = MatrixUtils.createRealMatrix(new double[2][2]);
+        try {
+            es.solve(b);
+            fail("an exception should have been thrown");
+        } catch (IllegalArgumentException iae) {
+            // expected behavior
+        }
+        try {
+            es.solve(b.getColumn(0));
+            fail("an exception should have been thrown");
+        } catch (IllegalArgumentException iae) {
+            // expected behavior
+        }
+        try {
+            es.solve(new ArrayRealVectorTest.RealVectorTestImpl(b.getColumn(0)));
+            fail("an exception should have been thrown");
+        } catch (IllegalArgumentException iae) {
+            // expected behavior
+        }
+    }
+
+    /** test solve */
+    public void testSolve() {
+        RealMatrix m = MatrixUtils.createRealMatrix(new double[][] {
+                { 91,  5, 29, 32, 40, 14 },
+                {  5, 34, -1,  0,  2, -1 },
+                { 29, -1, 12,  9, 21,  8 },
+                { 32,  0,  9, 14,  9,  0 },
+                { 40,  2, 21,  9, 51, 19 },
+                { 14, -1,  8,  0, 19, 14 }
+        });
+        DecompositionSolver es = new EigenDecompositionImpl(m, MathUtils.SAFE_MIN).getSolver();
+        RealMatrix b = MatrixUtils.createRealMatrix(new double[][] {
+                { 1561, 269, 188 },
+                {   69, -21,  70 },
+                {  739, 108,  63 },
+                {  324,  86,  59 },
+                { 1624, 194, 107 },
+                {  796,  69,  36 }
+        });
+        RealMatrix xRef = MatrixUtils.createRealMatrix(new double[][] {
+                { 1,   2, 1 },
+                { 2,  -1, 2 },
+                { 4,   2, 3 },
+                { 8,  -1, 0 },
+                { 16,  2, 0 },
+                { 32, -1, 0 }
+        });
+
+        // using RealMatrix
+        RealMatrix solution=es.solve(b);
+        assertEquals(0, solution.subtract(xRef).getNorm(), 2.5e-12);
+
+        // using double[]
+        for (int i = 0; i < b.getColumnDimension(); ++i) {
+            assertEquals(0,
+                         new ArrayRealVector(es.solve(b.getColumn(i))).subtract(xRef.getColumnVector(i)).getNorm(),
+                         2.0e-11);
+        }
+
+        // using Array2DRowRealMatrix
+        for (int i = 0; i < b.getColumnDimension(); ++i) {
+            assertEquals(0,
+                         es.solve(b.getColumnVector(i)).subtract(xRef.getColumnVector(i)).getNorm(),
+                         2.0e-11);
+        }
+
+        // using RealMatrix with an alternate implementation
+        for (int i = 0; i < b.getColumnDimension(); ++i) {
+            ArrayRealVectorTest.RealVectorTestImpl v =
+                new ArrayRealVectorTest.RealVectorTestImpl(b.getColumn(i));
+            assertEquals(0,
+                         es.solve(v).subtract(xRef.getColumnVector(i)).getNorm(),
+                         2.0e-11);
+        }
+
+    }
+
+    @Override
+    public void setUp() {
+        refValues = new double[] {
+                2.003, 2.002, 2.001, 1.001, 1.000, 0.001
+        };
+        matrix = EigenDecompositionImplTest.createTestMatrix(new Random(35992629946426l), refValues);
+    }
+
+    @Override
+    public void tearDown() {
+        refValues = null;
+        matrix    = null;
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/linear/FieldLUDecompositionImplTest.java b/src/test/java/org/apache/commons/math/linear/FieldLUDecompositionImplTest.java
new file mode 100644
index 0000000..30460b6
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/linear/FieldLUDecompositionImplTest.java
@@ -0,0 +1,297 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.linear;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.fraction.Fraction;
+import org.apache.commons.math.fraction.FractionField;
+
+public class FieldLUDecompositionImplTest extends TestCase {
+    private Fraction[][] testData = {
+            { new Fraction(1), new Fraction(2), new Fraction(3)},
+            { new Fraction(2), new Fraction(5), new Fraction(3)},
+            { new Fraction(1), new Fraction(0), new Fraction(8)}
+    };
+    private Fraction[][] testDataMinus = {
+            { new Fraction(-1), new Fraction(-2), new Fraction(-3)},
+            { new Fraction(-2), new Fraction(-5), new Fraction(-3)},
+            { new Fraction(-1),  new Fraction(0), new Fraction(-8)}
+    };
+    private Fraction[][] luData = {
+            { new Fraction(2), new Fraction(3), new Fraction(3) },
+            { new Fraction(2), new Fraction(3), new Fraction(7) },
+            { new Fraction(6), new Fraction(6), new Fraction(8) }
+    };
+
+    // singular matrices
+    private Fraction[][] singular = {
+            { new Fraction(2), new Fraction(3) },
+            { new Fraction(2), new Fraction(3) }
+    };
+    private Fraction[][] bigSingular = {
+            { new Fraction(1), new Fraction(2),   new Fraction(3),    new Fraction(4) },
+            { new Fraction(2), new Fraction(5),   new Fraction(3),    new Fraction(4) },
+            { new Fraction(7), new Fraction(3), new Fraction(256), new Fraction(1930) },
+            { new Fraction(3), new Fraction(7),   new Fraction(6),    new Fraction(8) }
+    }; // 4th row = 1st + 2nd
+
+    public FieldLUDecompositionImplTest(String name) {
+        super(name);
+    }
+
+    /** test dimensions */
+    public void testDimensions() {
+        FieldMatrix<Fraction> matrix = new Array2DRowFieldMatrix<Fraction>(testData);
+        FieldLUDecomposition<Fraction> LU = new FieldLUDecompositionImpl<Fraction>(matrix);
+        assertEquals(testData.length, LU.getL().getRowDimension());
+        assertEquals(testData.length, LU.getL().getColumnDimension());
+        assertEquals(testData.length, LU.getU().getRowDimension());
+        assertEquals(testData.length, LU.getU().getColumnDimension());
+        assertEquals(testData.length, LU.getP().getRowDimension());
+        assertEquals(testData.length, LU.getP().getColumnDimension());
+
+    }
+
+    /** test non-square matrix */
+    public void testNonSquare() {
+        try {
+            new FieldLUDecompositionImpl<Fraction>(new Array2DRowFieldMatrix<Fraction>(new Fraction[][] {
+                    { Fraction.ZERO, Fraction.ZERO },
+                    { Fraction.ZERO, Fraction.ZERO },
+                    { Fraction.ZERO, Fraction.ZERO }
+            }));
+            fail("Expected InvalidMatrixException");
+        } catch (InvalidMatrixException ime) {
+            // expected behavior
+        }
+    }
+
+    /** test PA = LU */
+    public void testPAEqualLU() {
+        FieldMatrix<Fraction> matrix = new Array2DRowFieldMatrix<Fraction>(testData);
+        FieldLUDecomposition<Fraction> lu = new FieldLUDecompositionImpl<Fraction>(matrix);
+        FieldMatrix<Fraction> l = lu.getL();
+        FieldMatrix<Fraction> u = lu.getU();
+        FieldMatrix<Fraction> p = lu.getP();
+        TestUtils.assertEquals(p.multiply(matrix), l.multiply(u));
+
+        matrix = new Array2DRowFieldMatrix<Fraction>(testDataMinus);
+        lu = new FieldLUDecompositionImpl<Fraction>(matrix);
+        l = lu.getL();
+        u = lu.getU();
+        p = lu.getP();
+        TestUtils.assertEquals(p.multiply(matrix), l.multiply(u));
+
+        matrix = new Array2DRowFieldMatrix<Fraction>(FractionField.getInstance(), 17, 17);
+        for (int i = 0; i < matrix.getRowDimension(); ++i) {
+            matrix.setEntry(i, i, Fraction.ONE);
+        }
+        lu = new FieldLUDecompositionImpl<Fraction>(matrix);
+        l = lu.getL();
+        u = lu.getU();
+        p = lu.getP();
+        TestUtils.assertEquals(p.multiply(matrix), l.multiply(u));
+
+        matrix = new Array2DRowFieldMatrix<Fraction>(singular);
+        lu = new FieldLUDecompositionImpl<Fraction>(matrix);
+        assertFalse(lu.getSolver().isNonSingular());
+        assertNull(lu.getL());
+        assertNull(lu.getU());
+        assertNull(lu.getP());
+
+        matrix = new Array2DRowFieldMatrix<Fraction>(bigSingular);
+        lu = new FieldLUDecompositionImpl<Fraction>(matrix);
+        assertFalse(lu.getSolver().isNonSingular());
+        assertNull(lu.getL());
+        assertNull(lu.getU());
+        assertNull(lu.getP());
+
+    }
+
+    /** test that L is lower triangular with unit diagonal */
+    public void testLLowerTriangular() {
+        FieldMatrix<Fraction> matrix = new Array2DRowFieldMatrix<Fraction>(testData);
+        FieldMatrix<Fraction> l = new FieldLUDecompositionImpl<Fraction>(matrix).getL();
+        for (int i = 0; i < l.getRowDimension(); i++) {
+            assertEquals(Fraction.ONE, l.getEntry(i, i));
+            for (int j = i + 1; j < l.getColumnDimension(); j++) {
+                assertEquals(Fraction.ZERO, l.getEntry(i, j));
+            }
+        }
+    }
+
+    /** test that U is upper triangular */
+    public void testUUpperTriangular() {
+        FieldMatrix<Fraction> matrix = new Array2DRowFieldMatrix<Fraction>(testData);
+        FieldMatrix<Fraction> u = new FieldLUDecompositionImpl<Fraction>(matrix).getU();
+        for (int i = 0; i < u.getRowDimension(); i++) {
+            for (int j = 0; j < i; j++) {
+                assertEquals(Fraction.ZERO, u.getEntry(i, j));
+            }
+        }
+    }
+
+    /** test that P is a permutation matrix */
+    public void testPPermutation() {
+        FieldMatrix<Fraction> matrix = new Array2DRowFieldMatrix<Fraction>(testData);
+        FieldMatrix<Fraction> p   = new FieldLUDecompositionImpl<Fraction>(matrix).getP();
+
+        FieldMatrix<Fraction> ppT = p.multiply(p.transpose());
+        FieldMatrix<Fraction> id  =
+            new Array2DRowFieldMatrix<Fraction>(FractionField.getInstance(),
+                                          p.getRowDimension(), p.getRowDimension());
+        for (int i = 0; i < id.getRowDimension(); ++i) {
+            id.setEntry(i, i, Fraction.ONE);
+        }
+        TestUtils.assertEquals(id, ppT);
+
+        for (int i = 0; i < p.getRowDimension(); i++) {
+            int zeroCount  = 0;
+            int oneCount   = 0;
+            int otherCount = 0;
+            for (int j = 0; j < p.getColumnDimension(); j++) {
+                final Fraction e = p.getEntry(i, j);
+                if (e.equals(Fraction.ZERO)) {
+                    ++zeroCount;
+                } else if (e.equals(Fraction.ONE)) {
+                    ++oneCount;
+                } else {
+                    ++otherCount;
+                }
+            }
+            assertEquals(p.getColumnDimension() - 1, zeroCount);
+            assertEquals(1, oneCount);
+            assertEquals(0, otherCount);
+        }
+
+        for (int j = 0; j < p.getColumnDimension(); j++) {
+            int zeroCount  = 0;
+            int oneCount   = 0;
+            int otherCount = 0;
+            for (int i = 0; i < p.getRowDimension(); i++) {
+                final Fraction e = p.getEntry(i, j);
+                if (e.equals(Fraction.ZERO)) {
+                    ++zeroCount;
+                } else if (e.equals(Fraction.ONE)) {
+                    ++oneCount;
+                } else {
+                    ++otherCount;
+                }
+            }
+            assertEquals(p.getRowDimension() - 1, zeroCount);
+            assertEquals(1, oneCount);
+            assertEquals(0, otherCount);
+        }
+
+    }
+
+
+    /** test singular */
+    public void testSingular() {
+        FieldLUDecomposition<Fraction> lu =
+            new FieldLUDecompositionImpl<Fraction>(new Array2DRowFieldMatrix<Fraction>(testData));
+        assertTrue(lu.getSolver().isNonSingular());
+        lu = new FieldLUDecompositionImpl<Fraction>(new Array2DRowFieldMatrix<Fraction>(singular));
+        assertFalse(lu.getSolver().isNonSingular());
+        lu = new FieldLUDecompositionImpl<Fraction>(new Array2DRowFieldMatrix<Fraction>(bigSingular));
+        assertFalse(lu.getSolver().isNonSingular());
+    }
+
+    /** test matrices values */
+    public void testMatricesValues1() {
+       FieldLUDecomposition<Fraction> lu =
+            new FieldLUDecompositionImpl<Fraction>(new Array2DRowFieldMatrix<Fraction>(testData));
+        FieldMatrix<Fraction> lRef = new Array2DRowFieldMatrix<Fraction>(new Fraction[][] {
+                { new Fraction(1), new Fraction(0), new Fraction(0) },
+                { new Fraction(2), new Fraction(1), new Fraction(0) },
+                { new Fraction(1), new Fraction(-2), new Fraction(1) }
+        });
+        FieldMatrix<Fraction> uRef = new Array2DRowFieldMatrix<Fraction>(new Fraction[][] {
+                { new Fraction(1),  new Fraction(2), new Fraction(3) },
+                { new Fraction(0), new Fraction(1), new Fraction(-3) },
+                { new Fraction(0),  new Fraction(0), new Fraction(-1) }
+        });
+        FieldMatrix<Fraction> pRef = new Array2DRowFieldMatrix<Fraction>(new Fraction[][] {
+                { new Fraction(1), new Fraction(0), new Fraction(0) },
+                { new Fraction(0), new Fraction(1), new Fraction(0) },
+                { new Fraction(0), new Fraction(0), new Fraction(1) }
+        });
+        int[] pivotRef = { 0, 1, 2 };
+
+        // check values against known references
+        FieldMatrix<Fraction> l = lu.getL();
+        TestUtils.assertEquals(lRef, l);
+        FieldMatrix<Fraction> u = lu.getU();
+        TestUtils.assertEquals(uRef, u);
+        FieldMatrix<Fraction> p = lu.getP();
+        TestUtils.assertEquals(pRef, p);
+        int[] pivot = lu.getPivot();
+        for (int i = 0; i < pivotRef.length; ++i) {
+            assertEquals(pivotRef[i], pivot[i]);
+        }
+
+        // check the same cached instance is returned the second time
+        assertTrue(l == lu.getL());
+        assertTrue(u == lu.getU());
+        assertTrue(p == lu.getP());
+
+    }
+
+    /** test matrices values */
+    public void testMatricesValues2() {
+       FieldLUDecomposition<Fraction> lu =
+            new FieldLUDecompositionImpl<Fraction>(new Array2DRowFieldMatrix<Fraction>(luData));
+        FieldMatrix<Fraction> lRef = new Array2DRowFieldMatrix<Fraction>(new Fraction[][] {
+                { new Fraction(1), new Fraction(0), new Fraction(0) },
+                { new Fraction(3), new Fraction(1), new Fraction(0) },
+                { new Fraction(1), new Fraction(0), new Fraction(1) }
+        });
+        FieldMatrix<Fraction> uRef = new Array2DRowFieldMatrix<Fraction>(new Fraction[][] {
+                { new Fraction(2), new Fraction(3), new Fraction(3)    },
+                { new Fraction(0), new Fraction(-3), new Fraction(-1)  },
+                { new Fraction(0), new Fraction(0), new Fraction(4) }
+        });
+        FieldMatrix<Fraction> pRef = new Array2DRowFieldMatrix<Fraction>(new Fraction[][] {
+                { new Fraction(1), new Fraction(0), new Fraction(0) },
+                { new Fraction(0), new Fraction(0), new Fraction(1) },
+                { new Fraction(0), new Fraction(1), new Fraction(0) }
+        });
+        int[] pivotRef = { 0, 2, 1 };
+
+        // check values against known references
+        FieldMatrix<Fraction> l = lu.getL();
+        TestUtils.assertEquals(lRef, l);
+        FieldMatrix<Fraction> u = lu.getU();
+        TestUtils.assertEquals(uRef, u);
+        FieldMatrix<Fraction> p = lu.getP();
+        TestUtils.assertEquals(pRef, p);
+        int[] pivot = lu.getPivot();
+        for (int i = 0; i < pivotRef.length; ++i) {
+            assertEquals(pivotRef[i], pivot[i]);
+        }
+
+        // check the same cached instance is returned the second time
+        assertTrue(l == lu.getL());
+        assertTrue(u == lu.getU());
+        assertTrue(p == lu.getP());
+
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/linear/FieldMatrixImplTest.java b/src/test/java/org/apache/commons/math/linear/FieldMatrixImplTest.java
new file mode 100644
index 0000000..f761c10
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/linear/FieldMatrixImplTest.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 junit.framework.TestCase;
+
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.linear.MatrixVisitorException;
+import org.apache.commons.math.fraction.Fraction;
+import org.apache.commons.math.fraction.FractionField;
+
+/**
+ * Test cases for the {@link Array2DRowFieldMatrix} class.
+ *
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ */
+
+public final class FieldMatrixImplTest extends TestCase {
+
+    // 3 x 3 identity matrix
+    protected Fraction[][] id = { {new Fraction(1),new Fraction(0),new Fraction(0)}, {new Fraction(0),new Fraction(1),new Fraction(0)}, {new Fraction(0),new Fraction(0),new Fraction(1)} };
+
+    // Test data for group operations
+    protected Fraction[][] testData = { {new Fraction(1),new Fraction(2),new Fraction(3)}, {new Fraction(2),new Fraction(5),new Fraction(3)}, {new Fraction(1),new Fraction(0),new Fraction(8)} };
+    protected Fraction[][] testDataLU = {{new Fraction(2), new Fraction(5), new Fraction(3)}, {new Fraction(1, 2), new Fraction(-5, 2), new Fraction(13, 2)}, {new Fraction(1, 2), new Fraction(1, 5), new Fraction(1, 5)}};
+    protected Fraction[][] testDataPlus2 = { {new Fraction(3),new Fraction(4),new Fraction(5)}, {new Fraction(4),new Fraction(7),new Fraction(5)}, {new Fraction(3),new Fraction(2),new Fraction(10)} };
+    protected Fraction[][] testDataMinus = { {new Fraction(-1),new Fraction(-2),new Fraction(-3)}, {new Fraction(-2),new Fraction(-5),new Fraction(-3)},
+       {new Fraction(-1),new Fraction(0),new Fraction(-8)} };
+    protected Fraction[] testDataRow1 = {new Fraction(1),new Fraction(2),new Fraction(3)};
+    protected Fraction[] testDataCol3 = {new Fraction(3),new Fraction(3),new Fraction(8)};
+    protected Fraction[][] testDataInv =
+        { {new Fraction(-40),new Fraction(16),new Fraction(9)}, {new Fraction(13),new Fraction(-5),new Fraction(-3)}, {new Fraction(5),new Fraction(-2),new Fraction(-1)} };
+    protected Fraction[] preMultTest = {new Fraction(8),new Fraction(12),new Fraction(33)};
+    protected Fraction[][] testData2 ={ {new Fraction(1),new Fraction(2),new Fraction(3)}, {new Fraction(2),new Fraction(5),new Fraction(3)}};
+    protected Fraction[][] testData2T = { {new Fraction(1),new Fraction(2)}, {new Fraction(2),new Fraction(5)}, {new Fraction(3),new Fraction(3)}};
+    protected Fraction[][] testDataPlusInv =
+        { {new Fraction(-39),new Fraction(18),new Fraction(12)}, {new Fraction(15),new Fraction(0),new Fraction(0)}, {new Fraction(6),new Fraction(-2),new Fraction(7)} };
+
+    // lu decomposition tests
+    protected Fraction[][] luData = { {new Fraction(2),new Fraction(3),new Fraction(3)}, {new Fraction(0),new Fraction(5),new Fraction(7)}, {new Fraction(6),new Fraction(9),new Fraction(8)} };
+    protected Fraction[][] luDataLUDecomposition = { {new Fraction(6),new Fraction(9),new Fraction(8)}, {new Fraction(0),new Fraction(5),new Fraction(7)},
+            {new Fraction(1, 3),new Fraction(0),new Fraction(1, 3)} };
+
+    // singular matrices
+    protected Fraction[][] singular = { {new Fraction(2),new Fraction(3)}, {new Fraction(2),new Fraction(3)} };
+    protected Fraction[][] bigSingular = {{new Fraction(1),new Fraction(2),new Fraction(3),new Fraction(4)}, {new Fraction(2),new Fraction(5),new Fraction(3),new Fraction(4)},
+        {new Fraction(7),new Fraction(3),new Fraction(256),new Fraction(1930)}, {new Fraction(3),new Fraction(7),new Fraction(6),new Fraction(8)}}; // 4th row = 1st + 2nd
+    protected Fraction[][] detData = { {new Fraction(1),new Fraction(2),new Fraction(3)}, {new Fraction(4),new Fraction(5),new Fraction(6)}, {new Fraction(7),new Fraction(8),new Fraction(10)} };
+    protected Fraction[][] detData2 = { {new Fraction(1), new Fraction(3)}, {new Fraction(2), new Fraction(4)}};
+
+    // vectors
+    protected Fraction[] testVector = {new Fraction(1),new Fraction(2),new Fraction(3)};
+    protected Fraction[] testVector2 = {new Fraction(1),new Fraction(2),new Fraction(3),new Fraction(4)};
+
+    // submatrix accessor tests
+    protected Fraction[][] subTestData = {{new Fraction(1), new Fraction(2), new Fraction(3), new Fraction(4)}, {new Fraction(3, 2), new Fraction(5, 2), new Fraction(7, 2), new Fraction(9, 2)},
+            {new Fraction(2), new Fraction(4), new Fraction(6), new Fraction(8)}, {new Fraction(4), new Fraction(5), new Fraction(6), new Fraction(7)}};
+    // array selections
+    protected Fraction[][] subRows02Cols13 = { {new Fraction(2), new Fraction(4)}, {new Fraction(4), new Fraction(8)}};
+    protected Fraction[][] subRows03Cols12 = { {new Fraction(2), new Fraction(3)}, {new Fraction(5), new Fraction(6)}};
+    protected Fraction[][] subRows03Cols123 = { {new Fraction(2), new Fraction(3), new Fraction(4)} , {new Fraction(5), new Fraction(6), new Fraction(7)}};
+    // effective permutations
+    protected Fraction[][] subRows20Cols123 = { {new Fraction(4), new Fraction(6), new Fraction(8)} , {new Fraction(2), new Fraction(3), new Fraction(4)}};
+    protected Fraction[][] subRows31Cols31 = {{new Fraction(7), new Fraction(5)}, {new Fraction(9, 2), new Fraction(5, 2)}};
+    // contiguous ranges
+    protected Fraction[][] subRows01Cols23 = {{new Fraction(3),new Fraction(4)} , {new Fraction(7, 2), new Fraction(9, 2)}};
+    protected Fraction[][] subRows23Cols00 = {{new Fraction(2)} , {new Fraction(4)}};
+    protected Fraction[][] subRows00Cols33 = {{new Fraction(4)}};
+    // row matrices
+    protected Fraction[][] subRow0 = {{new Fraction(1),new Fraction(2),new Fraction(3),new Fraction(4)}};
+    protected Fraction[][] subRow3 = {{new Fraction(4),new Fraction(5),new Fraction(6),new Fraction(7)}};
+    // column matrices
+    protected Fraction[][] subColumn1 = {{new Fraction(2)}, {new Fraction(5, 2)}, {new Fraction(4)}, {new Fraction(5)}};
+    protected Fraction[][] subColumn3 = {{new Fraction(4)}, {new Fraction(9, 2)}, {new Fraction(8)}, {new Fraction(7)}};
+
+    // tolerances
+    protected double entryTolerance = 10E-16;
+    protected double normTolerance = 10E-14;
+
+    public FieldMatrixImplTest(String name) {
+        super(name);
+    }
+
+    /** test dimensions */
+    public void testDimensions() {
+        Array2DRowFieldMatrix<Fraction> m = new Array2DRowFieldMatrix<Fraction>(testData);
+        Array2DRowFieldMatrix<Fraction> m2 = new Array2DRowFieldMatrix<Fraction>(testData2);
+        assertEquals("testData row dimension",3,m.getRowDimension());
+        assertEquals("testData column dimension",3,m.getColumnDimension());
+        assertTrue("testData is square",m.isSquare());
+        assertEquals("testData2 row dimension",m2.getRowDimension(),2);
+        assertEquals("testData2 column dimension",m2.getColumnDimension(),3);
+        assertTrue("testData2 is not square",!m2.isSquare());
+    }
+
+    /** test copy functions */
+    public void testCopyFunctions() {
+        Array2DRowFieldMatrix<Fraction> m1 = new Array2DRowFieldMatrix<Fraction>(testData);
+        Array2DRowFieldMatrix<Fraction> m2 = new Array2DRowFieldMatrix<Fraction>(m1.getData());
+        assertEquals(m2,m1);
+        Array2DRowFieldMatrix<Fraction> m3 = new Array2DRowFieldMatrix<Fraction>(testData);
+        Array2DRowFieldMatrix<Fraction> m4 = new Array2DRowFieldMatrix<Fraction>(m3.getData(), false);
+        assertEquals(m4,m3);
+    }
+
+    /** test add */
+    public void testAdd() {
+        Array2DRowFieldMatrix<Fraction> m = new Array2DRowFieldMatrix<Fraction>(testData);
+        Array2DRowFieldMatrix<Fraction> mInv = new Array2DRowFieldMatrix<Fraction>(testDataInv);
+        FieldMatrix<Fraction> mPlusMInv = m.add(mInv);
+        Fraction[][] sumEntries = mPlusMInv.getData();
+        for (int row = 0; row < m.getRowDimension(); row++) {
+            for (int col = 0; col < m.getColumnDimension(); col++) {
+                assertEquals(testDataPlusInv[row][col],sumEntries[row][col]);
+            }
+        }
+    }
+
+    /** test add failure */
+    public void testAddFail() {
+        Array2DRowFieldMatrix<Fraction> m = new Array2DRowFieldMatrix<Fraction>(testData);
+        Array2DRowFieldMatrix<Fraction> m2 = new Array2DRowFieldMatrix<Fraction>(testData2);
+        try {
+            m.add(m2);
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+     /** test m-n = m + -n */
+    public void testPlusMinus() {
+        Array2DRowFieldMatrix<Fraction> m = new Array2DRowFieldMatrix<Fraction>(testData);
+        Array2DRowFieldMatrix<Fraction> m2 = new Array2DRowFieldMatrix<Fraction>(testDataInv);
+        TestUtils.assertEquals(m.subtract(m2),m2.scalarMultiply(new Fraction(-1)).add(m));
+        try {
+            m.subtract(new Array2DRowFieldMatrix<Fraction>(testData2));
+            fail("Expecting illegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    /** test multiply */
+     public void testMultiply() {
+        Array2DRowFieldMatrix<Fraction> m = new Array2DRowFieldMatrix<Fraction>(testData);
+        Array2DRowFieldMatrix<Fraction> mInv = new Array2DRowFieldMatrix<Fraction>(testDataInv);
+        Array2DRowFieldMatrix<Fraction> identity = new Array2DRowFieldMatrix<Fraction>(id);
+        Array2DRowFieldMatrix<Fraction> m2 = new Array2DRowFieldMatrix<Fraction>(testData2);
+        TestUtils.assertEquals(m.multiply(mInv), identity);
+        TestUtils.assertEquals(mInv.multiply(m), identity);
+        TestUtils.assertEquals(m.multiply(identity), m);
+        TestUtils.assertEquals(identity.multiply(mInv), mInv);
+        TestUtils.assertEquals(m2.multiply(identity), m2);
+        try {
+            m.multiply(new Array2DRowFieldMatrix<Fraction>(bigSingular));
+            fail("Expecting illegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    //Additional Test for Array2DRowFieldMatrix<Fraction>Test.testMultiply
+
+    private Fraction[][] d3 = new Fraction[][] {{new Fraction(1),new Fraction(2),new Fraction(3),new Fraction(4)},{new Fraction(5),new Fraction(6),new Fraction(7),new Fraction(8)}};
+    private Fraction[][] d4 = new Fraction[][] {{new Fraction(1)},{new Fraction(2)},{new Fraction(3)},{new Fraction(4)}};
+    private Fraction[][] d5 = new Fraction[][] {{new Fraction(30)},{new Fraction(70)}};
+
+    public void testMultiply2() {
+       FieldMatrix<Fraction> m3 = new Array2DRowFieldMatrix<Fraction>(d3);
+       FieldMatrix<Fraction> m4 = new Array2DRowFieldMatrix<Fraction>(d4);
+       FieldMatrix<Fraction> m5 = new Array2DRowFieldMatrix<Fraction>(d5);
+       TestUtils.assertEquals(m3.multiply(m4), m5);
+   }
+
+    /** test trace */
+    public void testTrace() {
+        FieldMatrix<Fraction> m = new Array2DRowFieldMatrix<Fraction>(id);
+        assertEquals("identity trace",new Fraction(3),m.getTrace());
+        m = new Array2DRowFieldMatrix<Fraction>(testData2);
+        try {
+            m.getTrace();
+            fail("Expecting NonSquareMatrixException");
+        } catch (NonSquareMatrixException ex) {
+            // ignored
+        }
+    }
+
+    /** test sclarAdd */
+    public void testScalarAdd() {
+        FieldMatrix<Fraction> m = new Array2DRowFieldMatrix<Fraction>(testData);
+        TestUtils.assertEquals(new Array2DRowFieldMatrix<Fraction>(testDataPlus2), m.scalarAdd(new Fraction(2)));
+    }
+
+    /** test operate */
+    public void testOperate() {
+        FieldMatrix<Fraction> m = new Array2DRowFieldMatrix<Fraction>(id);
+        TestUtils.assertEquals(testVector, m.operate(testVector));
+        TestUtils.assertEquals(testVector, m.operate(new ArrayFieldVector<Fraction>(testVector)).getData());
+        m = new Array2DRowFieldMatrix<Fraction>(bigSingular);
+        try {
+            m.operate(testVector);
+            fail("Expecting illegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    /** test issue MATH-209 */
+    public void testMath209() {
+        FieldMatrix<Fraction> a = new Array2DRowFieldMatrix<Fraction>(new Fraction[][] {
+                { new Fraction(1), new Fraction(2) }, { new Fraction(3), new Fraction(4) }, { new Fraction(5), new Fraction(6) }
+        }, false);
+        Fraction[] b = a.operate(new Fraction[] { new Fraction(1), new Fraction(1) });
+        assertEquals(a.getRowDimension(), b.length);
+        assertEquals( new Fraction(3), b[0]);
+        assertEquals( new Fraction(7), b[1]);
+        assertEquals(new Fraction(11), b[2]);
+    }
+
+    /** test transpose */
+    public void testTranspose() {
+        FieldMatrix<Fraction> m = new Array2DRowFieldMatrix<Fraction>(testData);
+        FieldMatrix<Fraction> mIT = new FieldLUDecompositionImpl<Fraction>(m).getSolver().getInverse().transpose();
+        FieldMatrix<Fraction> mTI = new FieldLUDecompositionImpl<Fraction>(m.transpose()).getSolver().getInverse();
+        TestUtils.assertEquals(mIT, mTI);
+        m = new Array2DRowFieldMatrix<Fraction>(testData2);
+        FieldMatrix<Fraction> mt = new Array2DRowFieldMatrix<Fraction>(testData2T);
+        TestUtils.assertEquals(mt, m.transpose());
+    }
+
+    /** test preMultiply by vector */
+    public void testPremultiplyVector() {
+        FieldMatrix<Fraction> m = new Array2DRowFieldMatrix<Fraction>(testData);
+        TestUtils.assertEquals(m.preMultiply(testVector), preMultTest);
+        TestUtils.assertEquals(m.preMultiply(new ArrayFieldVector<Fraction>(testVector).getData()),
+                               preMultTest);
+        m = new Array2DRowFieldMatrix<Fraction>(bigSingular);
+        try {
+            m.preMultiply(testVector);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    public void testPremultiply() {
+        FieldMatrix<Fraction> m3 = new Array2DRowFieldMatrix<Fraction>(d3);
+        FieldMatrix<Fraction> m4 = new Array2DRowFieldMatrix<Fraction>(d4);
+        FieldMatrix<Fraction> m5 = new Array2DRowFieldMatrix<Fraction>(d5);
+        TestUtils.assertEquals(m4.preMultiply(m3), m5);
+
+        Array2DRowFieldMatrix<Fraction> m = new Array2DRowFieldMatrix<Fraction>(testData);
+        Array2DRowFieldMatrix<Fraction> mInv = new Array2DRowFieldMatrix<Fraction>(testDataInv);
+        Array2DRowFieldMatrix<Fraction> identity = new Array2DRowFieldMatrix<Fraction>(id);
+        TestUtils.assertEquals(m.preMultiply(mInv), identity);
+        TestUtils.assertEquals(mInv.preMultiply(m), identity);
+        TestUtils.assertEquals(m.preMultiply(identity), m);
+        TestUtils.assertEquals(identity.preMultiply(mInv), mInv);
+        try {
+            m.preMultiply(new Array2DRowFieldMatrix<Fraction>(bigSingular));
+            fail("Expecting illegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    public void testGetVectors() {
+        FieldMatrix<Fraction> m = new Array2DRowFieldMatrix<Fraction>(testData);
+        TestUtils.assertEquals(m.getRow(0), testDataRow1);
+        TestUtils.assertEquals(m.getColumn(2), testDataCol3);
+        try {
+            m.getRow(10);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // ignored
+        }
+        try {
+            m.getColumn(-1);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // ignored
+        }
+    }
+
+    public void testGetEntry() {
+        FieldMatrix<Fraction> m = new Array2DRowFieldMatrix<Fraction>(testData);
+        assertEquals("get entry",m.getEntry(0,1),new Fraction(2));
+        try {
+            m.getEntry(10, 4);
+            fail ("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    /** test examples in user guide */
+    public void testExamples() {
+        // Create a real matrix with two rows and three columns
+        Fraction[][] matrixData = {
+                {new Fraction(1),new Fraction(2),new Fraction(3)},
+                {new Fraction(2),new Fraction(5),new Fraction(3)}
+        };
+        FieldMatrix<Fraction> m = new Array2DRowFieldMatrix<Fraction>(matrixData);
+        // One more with three rows, two columns
+        Fraction[][] matrixData2 = {
+                {new Fraction(1),new Fraction(2)},
+                {new Fraction(2),new Fraction(5)},
+                {new Fraction(1), new Fraction(7)}
+        };
+        FieldMatrix<Fraction> n = new Array2DRowFieldMatrix<Fraction>(matrixData2);
+        // Now multiply m by n
+        FieldMatrix<Fraction> p = m.multiply(n);
+        assertEquals(2, p.getRowDimension());
+        assertEquals(2, p.getColumnDimension());
+        // Invert p
+        FieldMatrix<Fraction> pInverse = new FieldLUDecompositionImpl<Fraction>(p).getSolver().getInverse();
+        assertEquals(2, pInverse.getRowDimension());
+        assertEquals(2, pInverse.getColumnDimension());
+
+        // Solve example
+        Fraction[][] coefficientsData = {
+                {new Fraction(2), new Fraction(3), new Fraction(-2)},
+                {new Fraction(-1), new Fraction(7), new Fraction(6)},
+                {new Fraction(4), new Fraction(-3), new Fraction(-5)}
+        };
+        FieldMatrix<Fraction> coefficients = new Array2DRowFieldMatrix<Fraction>(coefficientsData);
+        Fraction[] constants = {new Fraction(1), new Fraction(-2), new Fraction(1)};
+        Fraction[] solution = new FieldLUDecompositionImpl<Fraction>(coefficients).getSolver().solve(constants);
+        assertEquals(new Fraction(2).multiply(solution[0]).
+                     add(new Fraction(3).multiply(solution[1])).
+                     subtract(new Fraction(2).multiply(solution[2])), constants[0]);
+        assertEquals(new Fraction(-1).multiply(solution[0]).
+                     add(new Fraction(7).multiply(solution[1])).
+                     add(new Fraction(6).multiply(solution[2])), constants[1]);
+        assertEquals(new Fraction(4).multiply(solution[0]).
+                     subtract(new Fraction(3).multiply(solution[1])).
+                     subtract(new Fraction(5).multiply(solution[2])), constants[2]);
+
+    }
+
+    // test submatrix accessors
+    public void testGetSubMatrix() {
+        FieldMatrix<Fraction> m = new Array2DRowFieldMatrix<Fraction>(subTestData);
+        checkGetSubMatrix(m, subRows23Cols00,  2 , 3 , 0, 0);
+        checkGetSubMatrix(m, subRows00Cols33,  0 , 0 , 3, 3);
+        checkGetSubMatrix(m, subRows01Cols23,  0 , 1 , 2, 3);
+        checkGetSubMatrix(m, subRows02Cols13,  new int[] { 0, 2 }, new int[] { 1, 3 });
+        checkGetSubMatrix(m, subRows03Cols12,  new int[] { 0, 3 }, new int[] { 1, 2 });
+        checkGetSubMatrix(m, subRows03Cols123, new int[] { 0, 3 }, new int[] { 1, 2, 3 });
+        checkGetSubMatrix(m, subRows20Cols123, new int[] { 2, 0 }, new int[] { 1, 2, 3 });
+        checkGetSubMatrix(m, subRows31Cols31,  new int[] { 3, 1 }, new int[] { 3, 1 });
+        checkGetSubMatrix(m, subRows31Cols31,  new int[] { 3, 1 }, new int[] { 3, 1 });
+        checkGetSubMatrix(m, null,  1, 0, 2, 4);
+        checkGetSubMatrix(m, null, -1, 1, 2, 2);
+        checkGetSubMatrix(m, null,  1, 0, 2, 2);
+        checkGetSubMatrix(m, null,  1, 0, 2, 4);
+        checkGetSubMatrix(m, null, new int[] {},    new int[] { 0 });
+        checkGetSubMatrix(m, null, new int[] { 0 }, new int[] { 4 });
+    }
+
+    private void checkGetSubMatrix(FieldMatrix<Fraction> m, Fraction[][] reference,
+                                   int startRow, int endRow, int startColumn, int endColumn) {
+        try {
+            FieldMatrix<Fraction> sub = m.getSubMatrix(startRow, endRow, startColumn, endColumn);
+            if (reference != null) {
+                assertEquals(new Array2DRowFieldMatrix<Fraction>(reference), sub);
+            } else {
+                fail("Expecting MatrixIndexException");
+            }
+        } catch (MatrixIndexException e) {
+            if (reference != null) {
+                throw e;
+            }
+        }
+    }
+
+    private void checkGetSubMatrix(FieldMatrix<Fraction> m, Fraction[][] reference,
+                                   int[] selectedRows, int[] selectedColumns) {
+        try {
+            FieldMatrix<Fraction> sub = m.getSubMatrix(selectedRows, selectedColumns);
+            if (reference != null) {
+                assertEquals(new Array2DRowFieldMatrix<Fraction>(reference), sub);
+            } else {
+                fail("Expecting MatrixIndexException");
+            }
+        } catch (MatrixIndexException e) {
+            if (reference != null) {
+                throw e;
+            }
+        }
+    }
+
+    public void testCopySubMatrix() {
+        FieldMatrix<Fraction> m = new Array2DRowFieldMatrix<Fraction>(subTestData);
+        checkCopy(m, subRows23Cols00,  2 , 3 , 0, 0);
+        checkCopy(m, subRows00Cols33,  0 , 0 , 3, 3);
+        checkCopy(m, subRows01Cols23,  0 , 1 , 2, 3);
+        checkCopy(m, subRows02Cols13,  new int[] { 0, 2 }, new int[] { 1, 3 });
+        checkCopy(m, subRows03Cols12,  new int[] { 0, 3 }, new int[] { 1, 2 });
+        checkCopy(m, subRows03Cols123, new int[] { 0, 3 }, new int[] { 1, 2, 3 });
+        checkCopy(m, subRows20Cols123, new int[] { 2, 0 }, new int[] { 1, 2, 3 });
+        checkCopy(m, subRows31Cols31,  new int[] { 3, 1 }, new int[] { 3, 1 });
+        checkCopy(m, subRows31Cols31,  new int[] { 3, 1 }, new int[] { 3, 1 });
+
+        checkCopy(m, null,  1, 0, 2, 4);
+        checkCopy(m, null, -1, 1, 2, 2);
+        checkCopy(m, null,  1, 0, 2, 2);
+        checkCopy(m, null,  1, 0, 2, 4);
+        checkCopy(m, null, new int[] {},    new int[] { 0 });
+        checkCopy(m, null, new int[] { 0 }, new int[] { 4 });
+    }
+
+    private void checkCopy(FieldMatrix<Fraction> m, Fraction[][] reference,
+                           int startRow, int endRow, int startColumn, int endColumn) {
+        try {
+            Fraction[][] sub = (reference == null) ?
+                             new Fraction[1][1] :
+                             new Fraction[reference.length][reference[0].length];
+            m.copySubMatrix(startRow, endRow, startColumn, endColumn, sub);
+            if (reference != null) {
+                assertEquals(new Array2DRowFieldMatrix<Fraction>(reference), new Array2DRowFieldMatrix<Fraction>(sub));
+            } else {
+                fail("Expecting MatrixIndexException");
+            }
+        } catch (MatrixIndexException e) {
+            if (reference != null) {
+                throw e;
+            }
+        }
+    }
+
+    private void checkCopy(FieldMatrix<Fraction> m, Fraction[][] reference,
+                           int[] selectedRows, int[] selectedColumns) {
+        try {
+            Fraction[][] sub = (reference == null) ?
+                    new Fraction[1][1] :
+                    new Fraction[reference.length][reference[0].length];
+            m.copySubMatrix(selectedRows, selectedColumns, sub);
+            if (reference != null) {
+                assertEquals(new Array2DRowFieldMatrix<Fraction>(reference), new Array2DRowFieldMatrix<Fraction>(sub));
+            } else {
+                fail("Expecting MatrixIndexException");
+            }
+        } catch (MatrixIndexException e) {
+            if (reference != null) {
+                throw e;
+            }
+        }
+    }
+
+    public void testGetRowMatrix() {
+        FieldMatrix<Fraction> m = new Array2DRowFieldMatrix<Fraction>(subTestData);
+        FieldMatrix<Fraction> mRow0 = new Array2DRowFieldMatrix<Fraction>(subRow0);
+        FieldMatrix<Fraction> mRow3 = new Array2DRowFieldMatrix<Fraction>(subRow3);
+        assertEquals("Row0", mRow0,
+                m.getRowMatrix(0));
+        assertEquals("Row3", mRow3,
+                m.getRowMatrix(3));
+        try {
+            m.getRowMatrix(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getRowMatrix(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testSetRowMatrix() {
+        FieldMatrix<Fraction> m = new Array2DRowFieldMatrix<Fraction>(subTestData);
+        FieldMatrix<Fraction> mRow3 = new Array2DRowFieldMatrix<Fraction>(subRow3);
+        assertNotSame(mRow3, m.getRowMatrix(0));
+        m.setRowMatrix(0, mRow3);
+        assertEquals(mRow3, m.getRowMatrix(0));
+        try {
+            m.setRowMatrix(-1, mRow3);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.setRowMatrix(0, m);
+            fail("Expecting InvalidMatrixException");
+        } catch (InvalidMatrixException ex) {
+            // expected
+        }
+    }
+
+    public void testGetColumnMatrix() {
+        FieldMatrix<Fraction> m = new Array2DRowFieldMatrix<Fraction>(subTestData);
+        FieldMatrix<Fraction> mColumn1 = new Array2DRowFieldMatrix<Fraction>(subColumn1);
+        FieldMatrix<Fraction> mColumn3 = new Array2DRowFieldMatrix<Fraction>(subColumn3);
+        assertEquals("Column1", mColumn1,
+                m.getColumnMatrix(1));
+        assertEquals("Column3", mColumn3,
+                m.getColumnMatrix(3));
+        try {
+            m.getColumnMatrix(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getColumnMatrix(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testSetColumnMatrix() {
+        FieldMatrix<Fraction> m = new Array2DRowFieldMatrix<Fraction>(subTestData);
+        FieldMatrix<Fraction> mColumn3 = new Array2DRowFieldMatrix<Fraction>(subColumn3);
+        assertNotSame(mColumn3, m.getColumnMatrix(1));
+        m.setColumnMatrix(1, mColumn3);
+        assertEquals(mColumn3, m.getColumnMatrix(1));
+        try {
+            m.setColumnMatrix(-1, mColumn3);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.setColumnMatrix(0, m);
+            fail("Expecting InvalidMatrixException");
+        } catch (InvalidMatrixException ex) {
+            // expected
+        }
+    }
+
+    public void testGetRowVector() {
+        FieldMatrix<Fraction> m = new Array2DRowFieldMatrix<Fraction>(subTestData);
+        FieldVector<Fraction> mRow0 = new ArrayFieldVector<Fraction>(subRow0[0]);
+        FieldVector<Fraction> mRow3 = new ArrayFieldVector<Fraction>(subRow3[0]);
+        assertEquals("Row0", mRow0, m.getRowVector(0));
+        assertEquals("Row3", mRow3, m.getRowVector(3));
+        try {
+            m.getRowVector(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getRowVector(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testSetRowVector() {
+        FieldMatrix<Fraction> m = new Array2DRowFieldMatrix<Fraction>(subTestData);
+        FieldVector<Fraction> mRow3 = new ArrayFieldVector<Fraction>(subRow3[0]);
+        assertNotSame(mRow3, m.getRowMatrix(0));
+        m.setRowVector(0, mRow3);
+        assertEquals(mRow3, m.getRowVector(0));
+        try {
+            m.setRowVector(-1, mRow3);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.setRowVector(0, new ArrayFieldVector<Fraction>(FractionField.getInstance(), 5));
+            fail("Expecting InvalidMatrixException");
+        } catch (InvalidMatrixException ex) {
+            // expected
+        }
+    }
+
+    public void testGetColumnVector() {
+        FieldMatrix<Fraction> m = new Array2DRowFieldMatrix<Fraction>(subTestData);
+        FieldVector<Fraction> mColumn1 = columnToVector(subColumn1);
+        FieldVector<Fraction> mColumn3 = columnToVector(subColumn3);
+        assertEquals("Column1", mColumn1, m.getColumnVector(1));
+        assertEquals("Column3", mColumn3, m.getColumnVector(3));
+        try {
+            m.getColumnVector(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getColumnVector(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testSetColumnVector() {
+        FieldMatrix<Fraction> m = new Array2DRowFieldMatrix<Fraction>(subTestData);
+        FieldVector<Fraction> mColumn3 = columnToVector(subColumn3);
+        assertNotSame(mColumn3, m.getColumnVector(1));
+        m.setColumnVector(1, mColumn3);
+        assertEquals(mColumn3, m.getColumnVector(1));
+        try {
+            m.setColumnVector(-1, mColumn3);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.setColumnVector(0, new ArrayFieldVector<Fraction>(FractionField.getInstance(), 5));
+            fail("Expecting InvalidMatrixException");
+        } catch (InvalidMatrixException ex) {
+            // expected
+        }
+    }
+
+    private FieldVector<Fraction> columnToVector(Fraction[][] column) {
+        Fraction[] data = new Fraction[column.length];
+        for (int i = 0; i < data.length; ++i) {
+            data[i] = column[i][0];
+        }
+        return new ArrayFieldVector<Fraction>(data, false);
+    }
+
+    public void testGetRow() {
+        FieldMatrix<Fraction> m = new Array2DRowFieldMatrix<Fraction>(subTestData);
+        checkArrays(subRow0[0], m.getRow(0));
+        checkArrays(subRow3[0], m.getRow(3));
+        try {
+            m.getRow(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getRow(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testSetRow() {
+        FieldMatrix<Fraction> m = new Array2DRowFieldMatrix<Fraction>(subTestData);
+        assertTrue(subRow3[0][0] != m.getRow(0)[0]);
+        m.setRow(0, subRow3[0]);
+        checkArrays(subRow3[0], m.getRow(0));
+        try {
+            m.setRow(-1, subRow3[0]);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.setRow(0, new Fraction[5]);
+            fail("Expecting InvalidMatrixException");
+        } catch (InvalidMatrixException ex) {
+            // expected
+        }
+    }
+
+    public void testGetColumn() {
+        FieldMatrix<Fraction> m = new Array2DRowFieldMatrix<Fraction>(subTestData);
+        Fraction[] mColumn1 = columnToArray(subColumn1);
+        Fraction[] mColumn3 = columnToArray(subColumn3);
+        checkArrays(mColumn1, m.getColumn(1));
+        checkArrays(mColumn3, m.getColumn(3));
+        try {
+            m.getColumn(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getColumn(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testSetColumn() {
+        FieldMatrix<Fraction> m = new Array2DRowFieldMatrix<Fraction>(subTestData);
+        Fraction[] mColumn3 = columnToArray(subColumn3);
+        assertTrue(mColumn3[0] != m.getColumn(1)[0]);
+        m.setColumn(1, mColumn3);
+        checkArrays(mColumn3, m.getColumn(1));
+        try {
+            m.setColumn(-1, mColumn3);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.setColumn(0, new Fraction[5]);
+            fail("Expecting InvalidMatrixException");
+        } catch (InvalidMatrixException ex) {
+            // expected
+        }
+    }
+
+    private Fraction[] columnToArray(Fraction[][] column) {
+        Fraction[] data = new Fraction[column.length];
+        for (int i = 0; i < data.length; ++i) {
+            data[i] = column[i][0];
+        }
+        return data;
+    }
+
+    private void checkArrays(Fraction[] expected, Fraction[] actual) {
+        assertEquals(expected.length, actual.length);
+        for (int i = 0; i < expected.length; ++i) {
+            assertEquals(expected[i], actual[i]);
+        }
+    }
+
+    public void testEqualsAndHashCode() {
+        Array2DRowFieldMatrix<Fraction> m = new Array2DRowFieldMatrix<Fraction>(testData);
+        Array2DRowFieldMatrix<Fraction> m1 = (Array2DRowFieldMatrix<Fraction>) m.copy();
+        Array2DRowFieldMatrix<Fraction> mt = (Array2DRowFieldMatrix<Fraction>) m.transpose();
+        assertTrue(m.hashCode() != mt.hashCode());
+        assertEquals(m.hashCode(), m1.hashCode());
+        assertEquals(m, m);
+        assertEquals(m, m1);
+        assertFalse(m.equals(null));
+        assertFalse(m.equals(mt));
+        assertFalse(m.equals(new Array2DRowFieldMatrix<Fraction>(bigSingular)));
+    }
+
+    public void testToString() {
+        Array2DRowFieldMatrix<Fraction> m = new Array2DRowFieldMatrix<Fraction>(testData);
+        assertEquals("Array2DRowFieldMatrix{{1,2,3},{2,5,3},{1,0,8}}", m.toString());
+        m = new Array2DRowFieldMatrix<Fraction>(FractionField.getInstance());
+        assertEquals("Array2DRowFieldMatrix{}", m.toString());
+    }
+
+    public void testSetSubMatrix() throws Exception {
+        Array2DRowFieldMatrix<Fraction> m = new Array2DRowFieldMatrix<Fraction>(testData);
+        m.setSubMatrix(detData2,1,1);
+        FieldMatrix<Fraction> expected = new Array2DRowFieldMatrix<Fraction>
+            (new Fraction[][] {
+                    {new Fraction(1),new Fraction(2),new Fraction(3)},
+                    {new Fraction(2),new Fraction(1),new Fraction(3)},
+                    {new Fraction(1),new Fraction(2),new Fraction(4)}
+             });
+        assertEquals(expected, m);
+
+        m.setSubMatrix(detData2,0,0);
+        expected = new Array2DRowFieldMatrix<Fraction>
+            (new Fraction[][] {
+                    {new Fraction(1),new Fraction(3),new Fraction(3)},
+                    {new Fraction(2),new Fraction(4),new Fraction(3)},
+                    {new Fraction(1),new Fraction(2),new Fraction(4)}
+             });
+        assertEquals(expected, m);
+
+        m.setSubMatrix(testDataPlus2,0,0);
+        expected = new Array2DRowFieldMatrix<Fraction>
+            (new Fraction[][] {
+                    {new Fraction(3),new Fraction(4),new Fraction(5)},
+                    {new Fraction(4),new Fraction(7),new Fraction(5)},
+                    {new Fraction(3),new Fraction(2),new Fraction(10)}
+             });
+        assertEquals(expected, m);
+
+        // dimension overflow
+        try {
+            m.setSubMatrix(testData,1,1);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException e) {
+            // expected
+        }
+        // dimension underflow
+        try {
+            m.setSubMatrix(testData,-1,1);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException e) {
+            // expected
+        }
+        try {
+            m.setSubMatrix(testData,1,-1);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException e) {
+            // expected
+        }
+
+        // null
+        try {
+            m.setSubMatrix(null,1,1);
+            fail("expecting NullPointerException");
+        } catch (NullPointerException e) {
+            // expected
+        }
+        Array2DRowFieldMatrix<Fraction> m2 = new Array2DRowFieldMatrix<Fraction>(FractionField.getInstance());
+        try {
+            m2.setSubMatrix(testData,0,1);
+            fail("expecting IllegalStateException");
+        } catch (IllegalStateException e) {
+            // expected
+        }
+        try {
+            m2.setSubMatrix(testData,1,0);
+            fail("expecting IllegalStateException");
+        } catch (IllegalStateException e) {
+            // expected
+        }
+
+        // ragged
+        try {
+            m.setSubMatrix(new Fraction[][] {{new Fraction(1)}, {new Fraction(2), new Fraction(3)}}, 0, 0);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        // empty
+        try {
+            m.setSubMatrix(new Fraction[][] {{}}, 0, 0);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+    }
+
+    public void testWalk() throws MatrixVisitorException {
+        int rows    = 150;
+        int columns = 75;
+
+        FieldMatrix<Fraction> m =
+            new Array2DRowFieldMatrix<Fraction>(FractionField.getInstance(), rows, columns);
+        m.walkInRowOrder(new SetVisitor());
+        GetVisitor getVisitor = new GetVisitor();
+        m.walkInOptimizedOrder(getVisitor);
+        assertEquals(rows * columns, getVisitor.getCount());
+
+        m = new Array2DRowFieldMatrix<Fraction>(FractionField.getInstance(), rows, columns);
+        m.walkInRowOrder(new SetVisitor(), 1, rows - 2, 1, columns - 2);
+        getVisitor = new GetVisitor();
+        m.walkInOptimizedOrder(getVisitor, 1, rows - 2, 1, columns - 2);
+        assertEquals((rows - 2) * (columns - 2), getVisitor.getCount());
+        for (int i = 0; i < rows; ++i) {
+            assertEquals(new Fraction(0), m.getEntry(i, 0));
+            assertEquals(new Fraction(0), m.getEntry(i, columns - 1));
+        }
+        for (int j = 0; j < columns; ++j) {
+            assertEquals(new Fraction(0), m.getEntry(0, j));
+            assertEquals(new Fraction(0), m.getEntry(rows - 1, j));
+        }
+
+        m = new Array2DRowFieldMatrix<Fraction>(FractionField.getInstance(), rows, columns);
+        m.walkInColumnOrder(new SetVisitor());
+        getVisitor = new GetVisitor();
+        m.walkInOptimizedOrder(getVisitor);
+        assertEquals(rows * columns, getVisitor.getCount());
+
+        m = new Array2DRowFieldMatrix<Fraction>(FractionField.getInstance(), rows, columns);
+        m.walkInColumnOrder(new SetVisitor(), 1, rows - 2, 1, columns - 2);
+        getVisitor = new GetVisitor();
+        m.walkInOptimizedOrder(getVisitor, 1, rows - 2, 1, columns - 2);
+        assertEquals((rows - 2) * (columns - 2), getVisitor.getCount());
+        for (int i = 0; i < rows; ++i) {
+            assertEquals(new Fraction(0), m.getEntry(i, 0));
+            assertEquals(new Fraction(0), m.getEntry(i, columns - 1));
+        }
+        for (int j = 0; j < columns; ++j) {
+            assertEquals(new Fraction(0), m.getEntry(0, j));
+            assertEquals(new Fraction(0), m.getEntry(rows - 1, j));
+        }
+
+        m = new Array2DRowFieldMatrix<Fraction>(FractionField.getInstance(), rows, columns);
+        m.walkInOptimizedOrder(new SetVisitor());
+        getVisitor = new GetVisitor();
+        m.walkInRowOrder(getVisitor);
+        assertEquals(rows * columns, getVisitor.getCount());
+
+        m = new Array2DRowFieldMatrix<Fraction>(FractionField.getInstance(), rows, columns);
+        m.walkInOptimizedOrder(new SetVisitor(), 1, rows - 2, 1, columns - 2);
+        getVisitor = new GetVisitor();
+        m.walkInRowOrder(getVisitor, 1, rows - 2, 1, columns - 2);
+        assertEquals((rows - 2) * (columns - 2), getVisitor.getCount());
+        for (int i = 0; i < rows; ++i) {
+            assertEquals(new Fraction(0), m.getEntry(i, 0));
+            assertEquals(new Fraction(0), m.getEntry(i, columns - 1));
+        }
+        for (int j = 0; j < columns; ++j) {
+            assertEquals(new Fraction(0), m.getEntry(0, j));
+            assertEquals(new Fraction(0), m.getEntry(rows - 1, j));
+        }
+
+        m = new Array2DRowFieldMatrix<Fraction>(FractionField.getInstance(), rows, columns);
+        m.walkInOptimizedOrder(new SetVisitor());
+        getVisitor = new GetVisitor();
+        m.walkInColumnOrder(getVisitor);
+        assertEquals(rows * columns, getVisitor.getCount());
+
+        m = new Array2DRowFieldMatrix<Fraction>(FractionField.getInstance(), rows, columns);
+        m.walkInOptimizedOrder(new SetVisitor(), 1, rows - 2, 1, columns - 2);
+        getVisitor = new GetVisitor();
+        m.walkInColumnOrder(getVisitor, 1, rows - 2, 1, columns - 2);
+        assertEquals((rows - 2) * (columns - 2), getVisitor.getCount());
+        for (int i = 0; i < rows; ++i) {
+            assertEquals(new Fraction(0), m.getEntry(i, 0));
+            assertEquals(new Fraction(0), m.getEntry(i, columns - 1));
+        }
+        for (int j = 0; j < columns; ++j) {
+            assertEquals(new Fraction(0), m.getEntry(0, j));
+            assertEquals(new Fraction(0), m.getEntry(rows - 1, j));
+        }
+
+    }
+
+    public void testSerial()  {
+        Array2DRowFieldMatrix<Fraction> m = new Array2DRowFieldMatrix<Fraction>(testData);
+        assertEquals(m,TestUtils.serializeAndRecover(m));
+    }
+
+    private static class SetVisitor extends DefaultFieldMatrixChangingVisitor<Fraction> {
+        public SetVisitor() {
+            super(Fraction.ZERO);
+        }
+        @Override
+        public Fraction visit(int i, int j, Fraction value) {
+            return new Fraction(i * 1024 + j, 1024);
+        }
+    }
+
+    private static class GetVisitor extends DefaultFieldMatrixPreservingVisitor<Fraction> {
+        private int count;
+        public GetVisitor() {
+            super(Fraction.ZERO);
+            count = 0;
+        }
+        @Override
+        public void visit(int i, int j, Fraction value) {
+            ++count;
+            assertEquals(new Fraction(i * 1024 + j, 1024), value);
+        }
+        public int getCount() {
+            return count;
+        }
+    }
+
+    //--------------- -----------------Protected methods
+
+    /** extracts the l  and u matrices from compact lu representation */
+    protected void splitLU(FieldMatrix<Fraction> lu,
+                           Fraction[][] lowerData,
+                           Fraction[][] upperData)
+        throws InvalidMatrixException {
+        if (!lu.isSquare() ||
+            lowerData.length != lowerData[0].length ||
+            upperData.length != upperData[0].length ||
+            lowerData.length != upperData.length ||
+            lowerData.length != lu.getRowDimension()) {
+            throw new InvalidMatrixException("incorrect dimensions");
+        }
+        int n = lu.getRowDimension();
+        for (int i = 0; i < n; i++) {
+            for (int j = 0; j < n; j++) {
+                if (j < i) {
+                    lowerData[i][j] = lu.getEntry(i, j);
+                    upperData[i][j] = Fraction.ZERO;
+                } else if (i == j) {
+                    lowerData[i][j] = Fraction.ONE;
+                    upperData[i][j] = lu.getEntry(i, j);
+                } else {
+                    lowerData[i][j] = Fraction.ZERO;
+                    upperData[i][j] = lu.getEntry(i, j);
+                }
+            }
+        }
+    }
+
+    /** Returns the result of applying the given row permutation to the matrix */
+    protected FieldMatrix<Fraction> permuteRows(FieldMatrix<Fraction> matrix, int[] permutation) {
+        if (!matrix.isSquare() || matrix.getRowDimension() != permutation.length) {
+            throw new IllegalArgumentException("dimension mismatch");
+        }
+        int n = matrix.getRowDimension();
+        int m = matrix.getColumnDimension();
+        Fraction out[][] = new Fraction[m][n];
+        for (int i = 0; i < n; i++) {
+            for (int j = 0; j < m; j++) {
+                out[i][j] = matrix.getEntry(permutation[i], j);
+            }
+        }
+        return new Array2DRowFieldMatrix<Fraction>(out);
+    }
+
+}
+
diff --git a/src/test/java/org/apache/commons/math/linear/FrenchRealVectorFormatTest.java b/src/test/java/org/apache/commons/math/linear/FrenchRealVectorFormatTest.java
new file mode 100644
index 0000000..59d9880
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/linear/FrenchRealVectorFormatTest.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.linear;
+
+import java.util.Locale;
+
+
+public class FrenchRealVectorFormatTest extends RealVectorFormatAbstractTest {
+
+    @Override
+    protected char getDecimalCharacter() {
+        return ',';
+    }
+
+    @Override
+    protected Locale getLocale() {
+        return Locale.FRENCH;
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/linear/InvalidMatrixExceptionTest.java b/src/test/java/org/apache/commons/math/linear/InvalidMatrixExceptionTest.java
new file mode 100644
index 0000000..c79c5d4
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/linear/InvalidMatrixExceptionTest.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.linear;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+public class InvalidMatrixExceptionTest extends TestCase {
+
+    public void testConstructorMessage(){
+        String msg = "message";
+        InvalidMatrixException ex = new InvalidMatrixException(msg);
+        assertEquals(msg, ex.getMessage());
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/linear/LUDecompositionImplTest.java b/src/test/java/org/apache/commons/math/linear/LUDecompositionImplTest.java
new file mode 100644
index 0000000..ecea0a2
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/linear/LUDecompositionImplTest.java
@@ -0,0 +1,288 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.linear;
+
+import junit.framework.TestCase;
+
+public class LUDecompositionImplTest extends TestCase {
+    private double[][] testData = {
+            { 1.0, 2.0, 3.0},
+            { 2.0, 5.0, 3.0},
+            { 1.0, 0.0, 8.0}
+    };
+    private double[][] testDataMinus = {
+            { -1.0, -2.0, -3.0},
+            { -2.0, -5.0, -3.0},
+            { -1.0,  0.0, -8.0}
+    };
+    private double[][] luData = {
+            { 2.0, 3.0, 3.0 },
+            { 0.0, 5.0, 7.0 },
+            { 6.0, 9.0, 8.0 }
+    };
+
+    // singular matrices
+    private double[][] singular = {
+            { 2.0, 3.0 },
+            { 2.0, 3.0 }
+    };
+    private double[][] bigSingular = {
+            { 1.0, 2.0,   3.0,    4.0 },
+            { 2.0, 5.0,   3.0,    4.0 },
+            { 7.0, 3.0, 256.0, 1930.0 },
+            { 3.0, 7.0,   6.0,    8.0 }
+    }; // 4th row = 1st + 2nd
+
+    private static final double entryTolerance = 10e-16;
+
+    private static final double normTolerance = 10e-14;
+
+    public LUDecompositionImplTest(String name) {
+        super(name);
+    }
+
+    /** test dimensions */
+    public void testDimensions() {
+        RealMatrix matrix = MatrixUtils.createRealMatrix(testData);
+        LUDecomposition LU = new LUDecompositionImpl(matrix);
+        assertEquals(testData.length, LU.getL().getRowDimension());
+        assertEquals(testData.length, LU.getL().getColumnDimension());
+        assertEquals(testData.length, LU.getU().getRowDimension());
+        assertEquals(testData.length, LU.getU().getColumnDimension());
+        assertEquals(testData.length, LU.getP().getRowDimension());
+        assertEquals(testData.length, LU.getP().getColumnDimension());
+
+    }
+
+    /** test non-square matrix */
+    public void testNonSquare() {
+        try {
+            new LUDecompositionImpl(MatrixUtils.createRealMatrix(new double[3][2]));
+            fail("Expecting InvalidMatrixException");
+        } catch (InvalidMatrixException ime) {
+            // expected behavior
+        }
+    }
+
+    /** test PA = LU */
+    public void testPAEqualLU() {
+        RealMatrix matrix = MatrixUtils.createRealMatrix(testData);
+        LUDecomposition lu = new LUDecompositionImpl(matrix);
+        RealMatrix l = lu.getL();
+        RealMatrix u = lu.getU();
+        RealMatrix p = lu.getP();
+        double norm = l.multiply(u).subtract(p.multiply(matrix)).getNorm();
+        assertEquals(0, norm, normTolerance);
+
+        matrix = MatrixUtils.createRealMatrix(testDataMinus);
+        lu = new LUDecompositionImpl(matrix);
+        l = lu.getL();
+        u = lu.getU();
+        p = lu.getP();
+        norm = l.multiply(u).subtract(p.multiply(matrix)).getNorm();
+        assertEquals(0, norm, normTolerance);
+
+        matrix = MatrixUtils.createRealIdentityMatrix(17);
+        lu = new LUDecompositionImpl(matrix);
+        l = lu.getL();
+        u = lu.getU();
+        p = lu.getP();
+        norm = l.multiply(u).subtract(p.multiply(matrix)).getNorm();
+        assertEquals(0, norm, normTolerance);
+
+        matrix = MatrixUtils.createRealMatrix(singular);
+        lu = new LUDecompositionImpl(matrix);
+        assertFalse(lu.getSolver().isNonSingular());
+        assertNull(lu.getL());
+        assertNull(lu.getU());
+        assertNull(lu.getP());
+
+        matrix = MatrixUtils.createRealMatrix(bigSingular);
+        lu = new LUDecompositionImpl(matrix);
+        assertFalse(lu.getSolver().isNonSingular());
+        assertNull(lu.getL());
+        assertNull(lu.getU());
+        assertNull(lu.getP());
+
+    }
+
+    /** test that L is lower triangular with unit diagonal */
+    public void testLLowerTriangular() {
+        RealMatrix matrix = MatrixUtils.createRealMatrix(testData);
+        RealMatrix l = new LUDecompositionImpl(matrix).getL();
+        for (int i = 0; i < l.getRowDimension(); i++) {
+            assertEquals(l.getEntry(i, i), 1, entryTolerance);
+            for (int j = i + 1; j < l.getColumnDimension(); j++) {
+                assertEquals(l.getEntry(i, j), 0, entryTolerance);
+            }
+        }
+    }
+
+    /** test that U is upper triangular */
+    public void testUUpperTriangular() {
+        RealMatrix matrix = MatrixUtils.createRealMatrix(testData);
+        RealMatrix u = new LUDecompositionImpl(matrix).getU();
+        for (int i = 0; i < u.getRowDimension(); i++) {
+            for (int j = 0; j < i; j++) {
+                assertEquals(u.getEntry(i, j), 0, entryTolerance);
+            }
+        }
+    }
+
+    /** test that P is a permutation matrix */
+    public void testPPermutation() {
+        RealMatrix matrix = MatrixUtils.createRealMatrix(testData);
+        RealMatrix p   = new LUDecompositionImpl(matrix).getP();
+
+        RealMatrix ppT = p.multiply(p.transpose());
+        RealMatrix id  = MatrixUtils.createRealIdentityMatrix(p.getRowDimension());
+        assertEquals(0, ppT.subtract(id).getNorm(), normTolerance);
+
+        for (int i = 0; i < p.getRowDimension(); i++) {
+            int zeroCount  = 0;
+            int oneCount   = 0;
+            int otherCount = 0;
+            for (int j = 0; j < p.getColumnDimension(); j++) {
+                final double e = p.getEntry(i, j);
+                if (e == 0) {
+                    ++zeroCount;
+                } else if (e == 1) {
+                    ++oneCount;
+                } else {
+                    ++otherCount;
+                }
+            }
+            assertEquals(p.getColumnDimension() - 1, zeroCount);
+            assertEquals(1, oneCount);
+            assertEquals(0, otherCount);
+        }
+
+        for (int j = 0; j < p.getColumnDimension(); j++) {
+            int zeroCount  = 0;
+            int oneCount   = 0;
+            int otherCount = 0;
+            for (int i = 0; i < p.getRowDimension(); i++) {
+                final double e = p.getEntry(i, j);
+                if (e == 0) {
+                    ++zeroCount;
+                } else if (e == 1) {
+                    ++oneCount;
+                } else {
+                    ++otherCount;
+                }
+            }
+            assertEquals(p.getRowDimension() - 1, zeroCount);
+            assertEquals(1, oneCount);
+            assertEquals(0, otherCount);
+        }
+
+    }
+
+
+    /** test singular */
+    public void testSingular() {
+        LUDecomposition lu =
+            new LUDecompositionImpl(MatrixUtils.createRealMatrix(testData));
+        assertTrue(lu.getSolver().isNonSingular());
+        lu = new LUDecompositionImpl(MatrixUtils.createRealMatrix(singular));
+        assertFalse(lu.getSolver().isNonSingular());
+        lu = new LUDecompositionImpl(MatrixUtils.createRealMatrix(bigSingular));
+        assertFalse(lu.getSolver().isNonSingular());
+    }
+
+    /** test matrices values */
+    public void testMatricesValues1() {
+       LUDecomposition lu =
+            new LUDecompositionImpl(MatrixUtils.createRealMatrix(testData));
+        RealMatrix lRef = MatrixUtils.createRealMatrix(new double[][] {
+                { 1.0, 0.0, 0.0 },
+                { 0.5, 1.0, 0.0 },
+                { 0.5, 0.2, 1.0 }
+        });
+        RealMatrix uRef = MatrixUtils.createRealMatrix(new double[][] {
+                { 2.0,  5.0, 3.0 },
+                { 0.0, -2.5, 6.5 },
+                { 0.0,  0.0, 0.2 }
+        });
+        RealMatrix pRef = MatrixUtils.createRealMatrix(new double[][] {
+                { 0.0, 1.0, 0.0 },
+                { 0.0, 0.0, 1.0 },
+                { 1.0, 0.0, 0.0 }
+        });
+        int[] pivotRef = { 1, 2, 0 };
+
+        // check values against known references
+        RealMatrix l = lu.getL();
+        assertEquals(0, l.subtract(lRef).getNorm(), 1.0e-13);
+        RealMatrix u = lu.getU();
+        assertEquals(0, u.subtract(uRef).getNorm(), 1.0e-13);
+        RealMatrix p = lu.getP();
+        assertEquals(0, p.subtract(pRef).getNorm(), 1.0e-13);
+        int[] pivot = lu.getPivot();
+        for (int i = 0; i < pivotRef.length; ++i) {
+            assertEquals(pivotRef[i], pivot[i]);
+        }
+
+        // check the same cached instance is returned the second time
+        assertTrue(l == lu.getL());
+        assertTrue(u == lu.getU());
+        assertTrue(p == lu.getP());
+
+    }
+
+    /** test matrices values */
+    public void testMatricesValues2() {
+       LUDecomposition lu =
+            new LUDecompositionImpl(MatrixUtils.createRealMatrix(luData));
+        RealMatrix lRef = MatrixUtils.createRealMatrix(new double[][] {
+                {    1.0,    0.0, 0.0 },
+                {    0.0,    1.0, 0.0 },
+                { 1.0 / 3.0, 0.0, 1.0 }
+        });
+        RealMatrix uRef = MatrixUtils.createRealMatrix(new double[][] {
+                { 6.0, 9.0,    8.0    },
+                { 0.0, 5.0,    7.0    },
+                { 0.0, 0.0, 1.0 / 3.0 }
+        });
+        RealMatrix pRef = MatrixUtils.createRealMatrix(new double[][] {
+                { 0.0, 0.0, 1.0 },
+                { 0.0, 1.0, 0.0 },
+                { 1.0, 0.0, 0.0 }
+        });
+        int[] pivotRef = { 2, 1, 0 };
+
+        // check values against known references
+        RealMatrix l = lu.getL();
+        assertEquals(0, l.subtract(lRef).getNorm(), 1.0e-13);
+        RealMatrix u = lu.getU();
+        assertEquals(0, u.subtract(uRef).getNorm(), 1.0e-13);
+        RealMatrix p = lu.getP();
+        assertEquals(0, p.subtract(pRef).getNorm(), 1.0e-13);
+        int[] pivot = lu.getPivot();
+        for (int i = 0; i < pivotRef.length; ++i) {
+            assertEquals(pivotRef[i], pivot[i]);
+        }
+
+        // check the same cached instance is returned the second time
+        assertTrue(l == lu.getL());
+        assertTrue(u == lu.getU());
+        assertTrue(p == lu.getP());
+
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/linear/LUSolverTest.java b/src/test/java/org/apache/commons/math/linear/LUSolverTest.java
new file mode 100644
index 0000000..54da984
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/linear/LUSolverTest.java
@@ -0,0 +1,179 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 junit.framework.TestCase;
+
+public class LUSolverTest extends TestCase {
+    private double[][] testData = {
+            { 1.0, 2.0, 3.0},
+            { 2.0, 5.0, 3.0},
+            { 1.0, 0.0, 8.0}
+    };
+    private double[][] luData = {
+            { 2.0, 3.0, 3.0 },
+            { 0.0, 5.0, 7.0 },
+            { 6.0, 9.0, 8.0 }
+    };
+
+    // singular matrices
+    private double[][] singular = {
+            { 2.0, 3.0 },
+            { 2.0, 3.0 }
+    };
+    private double[][] bigSingular = {
+            { 1.0, 2.0,   3.0,    4.0 },
+            { 2.0, 5.0,   3.0,    4.0 },
+            { 7.0, 3.0, 256.0, 1930.0 },
+            { 3.0, 7.0,   6.0,    8.0 }
+    }; // 4th row = 1st + 2nd
+
+    public LUSolverTest(String name) {
+        super(name);
+    }
+
+    /** test threshold impact */
+    public void testThreshold() {
+        final RealMatrix matrix = MatrixUtils.createRealMatrix(new double[][] {
+                                                       { 1.0, 2.0, 3.0},
+                                                       { 2.0, 5.0, 3.0},
+                                                       { 4.000001, 9.0, 9.0}
+                                                     });
+        assertFalse(new LUDecompositionImpl(matrix, 1.0e-5).getSolver().isNonSingular());
+        assertTrue(new LUDecompositionImpl(matrix, 1.0e-10).getSolver().isNonSingular());
+    }
+
+    /** test singular */
+    public void testSingular() {
+        DecompositionSolver solver =
+            new LUDecompositionImpl(MatrixUtils.createRealMatrix(testData)).getSolver();
+        assertTrue(solver.isNonSingular());
+        solver = new LUDecompositionImpl(MatrixUtils.createRealMatrix(singular)).getSolver();
+        assertFalse(solver.isNonSingular());
+        solver = new LUDecompositionImpl(MatrixUtils.createRealMatrix(bigSingular)).getSolver();
+        assertFalse(solver.isNonSingular());
+    }
+
+    /** test solve dimension errors */
+    public void testSolveDimensionErrors() {
+        DecompositionSolver solver =
+            new LUDecompositionImpl(MatrixUtils.createRealMatrix(testData)).getSolver();
+        RealMatrix b = MatrixUtils.createRealMatrix(new double[2][2]);
+        try {
+            solver.solve(b);
+            fail("an exception should have been thrown");
+        } catch (IllegalArgumentException iae) {
+            // expected behavior
+        }
+        try {
+            solver.solve(b.getColumn(0));
+            fail("an exception should have been thrown");
+        } catch (IllegalArgumentException iae) {
+            // expected behavior
+        }
+        try {
+            solver.solve(new ArrayRealVectorTest.RealVectorTestImpl(b.getColumn(0)));
+            fail("an exception should have been thrown");
+        } catch (IllegalArgumentException iae) {
+            // expected behavior
+        }
+    }
+
+    /** test solve singularity errors */
+    public void testSolveSingularityErrors() {
+        DecompositionSolver solver =
+            new LUDecompositionImpl(MatrixUtils.createRealMatrix(singular)).getSolver();
+        RealMatrix b = MatrixUtils.createRealMatrix(new double[2][2]);
+        try {
+            solver.solve(b);
+            fail("an exception should have been thrown");
+        } catch (InvalidMatrixException ime) {
+            // expected behavior
+        }
+        try {
+            solver.solve(b.getColumn(0));
+            fail("an exception should have been thrown");
+        } catch (InvalidMatrixException ime) {
+            // expected behavior
+        }
+        try {
+            solver.solve(b.getColumnVector(0));
+            fail("an exception should have been thrown");
+        } catch (InvalidMatrixException ime) {
+            // expected behavior
+        }
+        try {
+            solver.solve(new ArrayRealVectorTest.RealVectorTestImpl(b.getColumn(0)));
+            fail("an exception should have been thrown");
+        } catch (InvalidMatrixException ime) {
+            // expected behavior
+        }
+    }
+
+    /** test solve */
+    public void testSolve() {
+        DecompositionSolver solver =
+            new LUDecompositionImpl(MatrixUtils.createRealMatrix(testData)).getSolver();
+        RealMatrix b = MatrixUtils.createRealMatrix(new double[][] {
+                { 1, 0 }, { 2, -5 }, { 3, 1 }
+        });
+        RealMatrix xRef = MatrixUtils.createRealMatrix(new double[][] {
+                { 19, -71 }, { -6, 22 }, { -2, 9 }
+        });
+
+        // using RealMatrix
+        assertEquals(0, solver.solve(b).subtract(xRef).getNorm(), 1.0e-13);
+
+        // using double[]
+        for (int i = 0; i < b.getColumnDimension(); ++i) {
+            assertEquals(0,
+                         new ArrayRealVector(solver.solve(b.getColumn(i))).subtract(xRef.getColumnVector(i)).getNorm(),
+                         1.0e-13);
+        }
+
+        // using ArrayRealVector
+        for (int i = 0; i < b.getColumnDimension(); ++i) {
+            assertEquals(0,
+                         solver.solve(b.getColumnVector(i)).subtract(xRef.getColumnVector(i)).getNorm(),
+                         1.0e-13);
+        }
+
+        // using RealVector with an alternate implementation
+        for (int i = 0; i < b.getColumnDimension(); ++i) {
+            ArrayRealVectorTest.RealVectorTestImpl v =
+                new ArrayRealVectorTest.RealVectorTestImpl(b.getColumn(i));
+            assertEquals(0,
+                         solver.solve(v).subtract(xRef.getColumnVector(i)).getNorm(),
+                         1.0e-13);
+        }
+
+    }
+
+    /** test determinant */
+    public void testDeterminant() {
+        assertEquals( -1, getDeterminant(MatrixUtils.createRealMatrix(testData)), 1.0e-15);
+        assertEquals(-10, getDeterminant(MatrixUtils.createRealMatrix(luData)), 1.0e-14);
+        assertEquals(  0, getDeterminant(MatrixUtils.createRealMatrix(singular)), 1.0e-17);
+        assertEquals(  0, getDeterminant(MatrixUtils.createRealMatrix(bigSingular)), 1.0e-10);
+    }
+
+    private double getDeterminant(RealMatrix m) {
+        return new LUDecompositionImpl(m).getDeterminant();
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/linear/MatrixIndexExceptionTest.java b/src/test/java/org/apache/commons/math/linear/MatrixIndexExceptionTest.java
new file mode 100644
index 0000000..f56d838
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/linear/MatrixIndexExceptionTest.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.linear;
+
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ */
+public class MatrixIndexExceptionTest extends TestCase {
+
+    /**
+     *
+     */
+    public void testParameter(){
+        MatrixIndexException ex = new MatrixIndexException(LocalizedFormats.INDEX_OUT_OF_RANGE, 12, 0, 5);
+        assertEquals(12, ex.getArguments()[0]);
+        assertEquals(0,  ex.getArguments()[1]);
+        assertEquals(5,  ex.getArguments()[2]);
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/linear/MatrixUtilsTest.java b/src/test/java/org/apache/commons/math/linear/MatrixUtilsTest.java
new file mode 100644
index 0000000..095ef31
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/linear/MatrixUtilsTest.java
@@ -0,0 +1,404 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.math.fraction.BigFraction;
+import org.apache.commons.math.fraction.Fraction;
+import org.apache.commons.math.fraction.FractionConversionException;
+import org.apache.commons.math.fraction.FractionField;
+
+/**
+ * Test cases for the {@link MatrixUtils} class.
+ *
+ * @version $Revision: 1003899 $ $Date: 2010-10-03 00:06:55 +0200 (dim. 03 oct. 2010) $
+ */
+
+public final class MatrixUtilsTest extends TestCase {
+
+    protected double[][] testData = { {1d,2d,3d}, {2d,5d,3d}, {1d,0d,8d} };
+    protected double[][] nullMatrix = null;
+    protected double[] row = {1,2,3};
+    protected BigDecimal[] bigRow =
+        {new BigDecimal(1),new BigDecimal(2),new BigDecimal(3)};
+    protected String[] stringRow = {"1", "2", "3"};
+    protected Fraction[] fractionRow =
+        {new Fraction(1),new Fraction(2),new Fraction(3)};
+    protected double[][] rowMatrix = {{1,2,3}};
+    protected BigDecimal[][] bigRowMatrix =
+        {{new BigDecimal(1), new BigDecimal(2), new BigDecimal(3)}};
+    protected String[][] stringRowMatrix = {{"1", "2", "3"}};
+    protected Fraction[][] fractionRowMatrix =
+        {{new Fraction(1), new Fraction(2), new Fraction(3)}};
+    protected double[] col = {0,4,6};
+    protected BigDecimal[] bigCol =
+        {new BigDecimal(0),new BigDecimal(4),new BigDecimal(6)};
+    protected String[] stringCol = {"0","4","6"};
+    protected Fraction[] fractionCol =
+        {new Fraction(0),new Fraction(4),new Fraction(6)};
+    protected double[] nullDoubleArray = null;
+    protected double[][] colMatrix = {{0},{4},{6}};
+    protected BigDecimal[][] bigColMatrix =
+        {{new BigDecimal(0)},{new BigDecimal(4)},{new BigDecimal(6)}};
+    protected String[][] stringColMatrix = {{"0"}, {"4"}, {"6"}};
+    protected Fraction[][] fractionColMatrix =
+        {{new Fraction(0)},{new Fraction(4)},{new Fraction(6)}};
+
+    public MatrixUtilsTest(String name) {
+        super(name);
+    }
+
+
+    public void testCreateRealMatrix() {
+        assertEquals(new BlockRealMatrix(testData),
+                MatrixUtils.createRealMatrix(testData));
+        try {
+            MatrixUtils.createRealMatrix(new double[][] {{1}, {1,2}});  // ragged
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            MatrixUtils.createRealMatrix(new double[][] {{}, {}});  // no columns
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            MatrixUtils.createRealMatrix(null);  // null
+            fail("Expecting NullPointerException");
+        } catch (NullPointerException ex) {
+            // expected
+        }
+    }
+
+    public void testcreateFieldMatrix() {
+        assertEquals(new Array2DRowFieldMatrix<Fraction>(asFraction(testData)),
+                     MatrixUtils.createFieldMatrix(asFraction(testData)));
+        assertEquals(new Array2DRowFieldMatrix<Fraction>(fractionColMatrix),
+                     MatrixUtils.createFieldMatrix(fractionColMatrix));
+        try {
+            MatrixUtils.createFieldMatrix(asFraction(new double[][] {{1}, {1,2}}));  // ragged
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            MatrixUtils.createFieldMatrix(asFraction(new double[][] {{}, {}}));  // no columns
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            MatrixUtils.createFieldMatrix((Fraction[][])null);  // null
+            fail("Expecting NullPointerException");
+        } catch (NullPointerException ex) {
+            // expected
+        }
+    }
+
+    @Deprecated
+    public void testCreateBigMatrix() {
+        assertEquals(new BigMatrixImpl(testData),
+                MatrixUtils.createBigMatrix(testData));
+        assertEquals(new BigMatrixImpl(BigMatrixImplTest.asBigDecimal(testData), true),
+                MatrixUtils.createBigMatrix(BigMatrixImplTest.asBigDecimal(testData), false));
+        assertEquals(new BigMatrixImpl(BigMatrixImplTest.asBigDecimal(testData), false),
+                MatrixUtils.createBigMatrix(BigMatrixImplTest.asBigDecimal(testData), true));
+        assertEquals(new BigMatrixImpl(bigColMatrix),
+                MatrixUtils.createBigMatrix(bigColMatrix));
+        assertEquals(new BigMatrixImpl(stringColMatrix),
+                MatrixUtils.createBigMatrix(stringColMatrix));
+        try {
+            MatrixUtils.createBigMatrix(new double[][] {{1}, {1,2}});  // ragged
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            MatrixUtils.createBigMatrix(new double[][] {{}, {}});  // no columns
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            MatrixUtils.createBigMatrix(nullMatrix);  // null
+            fail("Expecting NullPointerException");
+        } catch (NullPointerException ex) {
+            // expected
+        }
+    }
+
+    public void testCreateRowRealMatrix() {
+        assertEquals(MatrixUtils.createRowRealMatrix(row),
+                     new BlockRealMatrix(rowMatrix));
+        try {
+            MatrixUtils.createRowRealMatrix(new double[] {});  // empty
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            MatrixUtils.createRowRealMatrix(null);  // null
+            fail("Expecting NullPointerException");
+        } catch (NullPointerException ex) {
+            // expected
+        }
+    }
+
+    public void testCreateRowFieldMatrix() {
+        assertEquals(MatrixUtils.createRowFieldMatrix(asFraction(row)),
+                     new Array2DRowFieldMatrix<Fraction>(asFraction(rowMatrix)));
+        assertEquals(MatrixUtils.createRowFieldMatrix(fractionRow),
+                     new Array2DRowFieldMatrix<Fraction>(fractionRowMatrix));
+        try {
+            MatrixUtils.createRowFieldMatrix(new Fraction[] {});  // empty
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            MatrixUtils.createRowFieldMatrix((Fraction[]) null);  // null
+            fail("Expecting NullPointerException");
+        } catch (NullPointerException ex) {
+            // expected
+        }
+    }
+
+    @Deprecated
+    public void testCreateRowBigMatrix() {
+        assertEquals(MatrixUtils.createRowBigMatrix(row),
+                new BigMatrixImpl(rowMatrix));
+        assertEquals(MatrixUtils.createRowBigMatrix(bigRow),
+                new BigMatrixImpl(bigRowMatrix));
+        assertEquals(MatrixUtils.createRowBigMatrix(stringRow),
+                new BigMatrixImpl(stringRowMatrix));
+        try {
+            MatrixUtils.createRowBigMatrix(new double[] {});  // empty
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            MatrixUtils.createRowBigMatrix(nullDoubleArray);  // null
+            fail("Expecting NullPointerException");
+        } catch (NullPointerException ex) {
+            // expected
+        }
+    }
+
+    public void testCreateColumnRealMatrix() {
+        assertEquals(MatrixUtils.createColumnRealMatrix(col),
+                     new BlockRealMatrix(colMatrix));
+        try {
+            MatrixUtils.createColumnRealMatrix(new double[] {});  // empty
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            MatrixUtils.createColumnRealMatrix(null);  // null
+            fail("Expecting NullPointerException");
+        } catch (NullPointerException ex) {
+            // expected
+        }
+    }
+
+    public void testCreateColumnFieldMatrix() {
+        assertEquals(MatrixUtils.createColumnFieldMatrix(asFraction(col)),
+                     new Array2DRowFieldMatrix<Fraction>(asFraction(colMatrix)));
+        assertEquals(MatrixUtils.createColumnFieldMatrix(fractionCol),
+                     new Array2DRowFieldMatrix<Fraction>(fractionColMatrix));
+
+        try {
+            MatrixUtils.createColumnFieldMatrix(new Fraction[] {});  // empty
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            MatrixUtils.createColumnFieldMatrix((Fraction[]) null);  // null
+            fail("Expecting NullPointerException");
+        } catch (NullPointerException ex) {
+            // expected
+        }
+    }
+
+    @Deprecated
+    public void testCreateColumnBigMatrix() {
+        assertEquals(MatrixUtils.createColumnBigMatrix(col),
+                new BigMatrixImpl(colMatrix));
+        assertEquals(MatrixUtils.createColumnBigMatrix(bigCol),
+                new BigMatrixImpl(bigColMatrix));
+        assertEquals(MatrixUtils.createColumnBigMatrix(stringCol),
+                new BigMatrixImpl(stringColMatrix));
+
+        try {
+            MatrixUtils.createColumnBigMatrix(new double[] {});  // empty
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            MatrixUtils.createColumnBigMatrix(nullDoubleArray);  // null
+            fail("Expecting NullPointerException");
+        } catch (NullPointerException ex) {
+            // expected
+        }
+    }
+
+    /**
+     * Verifies that the matrix is an identity matrix
+     */
+    protected void checkIdentityMatrix(RealMatrix m) {
+        for (int i = 0; i < m.getRowDimension(); i++) {
+            for (int j =0; j < m.getColumnDimension(); j++) {
+                if (i == j) {
+                    assertEquals(m.getEntry(i, j), 1d, 0);
+                } else {
+                    assertEquals(m.getEntry(i, j), 0d, 0);
+                }
+            }
+        }
+    }
+
+    public void testCreateIdentityMatrix() {
+        checkIdentityMatrix(MatrixUtils.createRealIdentityMatrix(3));
+        checkIdentityMatrix(MatrixUtils.createRealIdentityMatrix(2));
+        checkIdentityMatrix(MatrixUtils.createRealIdentityMatrix(1));
+        try {
+            MatrixUtils.createRealIdentityMatrix(0);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+
+    /**
+     * Verifies that the matrix is an identity matrix
+     */
+    protected void checkIdentityFieldMatrix(FieldMatrix<Fraction> m) {
+        for (int i = 0; i < m.getRowDimension(); i++) {
+            for (int j =0; j < m.getColumnDimension(); j++) {
+                if (i == j) {
+                    assertEquals(m.getEntry(i, j), Fraction.ONE);
+                } else {
+                    assertEquals(m.getEntry(i, j), Fraction.ZERO);
+                }
+            }
+        }
+    }
+
+    public void testcreateFieldIdentityMatrix() {
+        checkIdentityFieldMatrix(MatrixUtils.createFieldIdentityMatrix(FractionField.getInstance(), 3));
+        checkIdentityFieldMatrix(MatrixUtils.createFieldIdentityMatrix(FractionField.getInstance(), 2));
+        checkIdentityFieldMatrix(MatrixUtils.createFieldIdentityMatrix(FractionField.getInstance(), 1));
+        try {
+            MatrixUtils.createRealIdentityMatrix(0);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+
+    public void testBigFractionConverter() {
+        BigFraction[][] bfData = {
+                { new BigFraction(1), new BigFraction(2), new BigFraction(3) },
+                { new BigFraction(2), new BigFraction(5), new BigFraction(3) },
+                { new BigFraction(1), new BigFraction(0), new BigFraction(8) }
+        };
+        FieldMatrix<BigFraction> m = new Array2DRowFieldMatrix<BigFraction>(bfData, false);
+        RealMatrix converted = MatrixUtils.bigFractionMatrixToRealMatrix(m);
+        RealMatrix reference = new Array2DRowRealMatrix(testData, false);
+        assertEquals(0.0, converted.subtract(reference).getNorm(), 0.0);
+    }
+
+    public void testFractionConverter() {
+        Fraction[][] fData = {
+                { new Fraction(1), new Fraction(2), new Fraction(3) },
+                { new Fraction(2), new Fraction(5), new Fraction(3) },
+                { new Fraction(1), new Fraction(0), new Fraction(8) }
+        };
+        FieldMatrix<Fraction> m = new Array2DRowFieldMatrix<Fraction>(fData, false);
+        RealMatrix converted = MatrixUtils.fractionMatrixToRealMatrix(m);
+        RealMatrix reference = new Array2DRowRealMatrix(testData, false);
+        assertEquals(0.0, converted.subtract(reference).getNorm(), 0.0);
+    }
+
+    public static final Fraction[][] asFraction(double[][] data) {
+        Fraction d[][] = new Fraction[data.length][];
+        try {
+            for (int i = 0; i < data.length; ++i) {
+                double[] dataI = data[i];
+                Fraction[] dI  = new Fraction[dataI.length];
+                for (int j = 0; j < dataI.length; ++j) {
+                    dI[j] = new Fraction(dataI[j]);
+                }
+                d[i] = dI;
+            }
+        } catch (FractionConversionException fce) {
+            fail(fce.getMessage());
+        }
+        return d;
+    }
+
+    public static final Fraction[] asFraction(double[] data) {
+        Fraction d[] = new Fraction[data.length];
+        try {
+            for (int i = 0; i < data.length; ++i) {
+                d[i] = new Fraction(data[i]);
+            }
+        } catch (FractionConversionException fce) {
+            fail(fce.getMessage());
+        }
+        return d;
+    }
+
+    /**
+     * Verifies that the matrix is an identity matrix
+     */
+    @Deprecated
+    protected void checkIdentityBigMatrix(BigMatrix m) {
+        for (int i = 0; i < m.getRowDimension(); i++) {
+            for (int j =0; j < m.getColumnDimension(); j++) {
+                if (i == j) {
+                    assertEquals(m.getEntry(i, j), BigMatrixImpl.ONE);
+                } else {
+                    assertEquals(m.getEntry(i, j), BigMatrixImpl.ZERO);
+                }
+            }
+        }
+    }
+
+    @Deprecated
+    public void testCreateBigIdentityMatrix() {
+        checkIdentityBigMatrix(MatrixUtils.createBigIdentityMatrix(3));
+        checkIdentityBigMatrix(MatrixUtils.createBigIdentityMatrix(2));
+        checkIdentityBigMatrix(MatrixUtils.createBigIdentityMatrix(1));
+        try {
+            MatrixUtils.createRealIdentityMatrix(0);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+
+}
+
diff --git a/src/test/java/org/apache/commons/math/linear/QRDecompositionImplTest.java b/src/test/java/org/apache/commons/math/linear/QRDecompositionImplTest.java
new file mode 100644
index 0000000..b3a203f
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/linear/QRDecompositionImplTest.java
@@ -0,0 +1,256 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.Random;
+
+import org.apache.commons.math.linear.MatrixVisitorException;
+
+import junit.framework.TestCase;
+
+public class QRDecompositionImplTest extends TestCase {
+    double[][] testData3x3NonSingular = {
+            { 12, -51, 4 },
+            { 6, 167, -68 },
+            { -4, 24, -41 }, };
+
+    double[][] testData3x3Singular = {
+            { 1, 4, 7, },
+            { 2, 5, 8, },
+            { 3, 6, 9, }, };
+
+    double[][] testData3x4 = {
+            { 12, -51, 4, 1 },
+            { 6, 167, -68, 2 },
+            { -4, 24, -41, 3 }, };
+
+    double[][] testData4x3 = {
+            { 12, -51, 4, },
+            { 6, 167, -68, },
+            { -4, 24, -41, },
+            { -5, 34, 7, }, };
+
+    private static final double entryTolerance = 10e-16;
+
+    private static final double normTolerance = 10e-14;
+
+    public QRDecompositionImplTest(String name) {
+        super(name);
+    }
+
+    /** test dimensions 
+     * @throws MatrixVisitorException */
+    public void testDimensions() throws MatrixVisitorException {
+        checkDimension(MatrixUtils.createRealMatrix(testData3x3NonSingular));
+
+        checkDimension(MatrixUtils.createRealMatrix(testData4x3));
+
+        checkDimension(MatrixUtils.createRealMatrix(testData3x4));
+
+        Random r = new Random(643895747384642l);
+        int    p = (5 * BlockRealMatrix.BLOCK_SIZE) / 4;
+        int    q = (7 * BlockRealMatrix.BLOCK_SIZE) / 4;
+        checkDimension(createTestMatrix(r, p, q));
+        checkDimension(createTestMatrix(r, q, p));
+
+    }
+
+    private void checkDimension(RealMatrix m) {
+        int rows = m.getRowDimension();
+        int columns = m.getColumnDimension();
+        QRDecomposition qr = new QRDecompositionImpl(m);
+        assertEquals(rows,    qr.getQ().getRowDimension());
+        assertEquals(rows,    qr.getQ().getColumnDimension());
+        assertEquals(rows,    qr.getR().getRowDimension());
+        assertEquals(columns, qr.getR().getColumnDimension());
+    }
+
+    /** test A = QR 
+     * @throws MatrixVisitorException */
+    public void testAEqualQR() throws MatrixVisitorException {
+        checkAEqualQR(MatrixUtils.createRealMatrix(testData3x3NonSingular));
+
+        checkAEqualQR(MatrixUtils.createRealMatrix(testData3x3Singular));
+
+        checkAEqualQR(MatrixUtils.createRealMatrix(testData3x4));
+
+        checkAEqualQR(MatrixUtils.createRealMatrix(testData4x3));
+
+        Random r = new Random(643895747384642l);
+        int    p = (5 * BlockRealMatrix.BLOCK_SIZE) / 4;
+        int    q = (7 * BlockRealMatrix.BLOCK_SIZE) / 4;
+        checkAEqualQR(createTestMatrix(r, p, q));
+
+        checkAEqualQR(createTestMatrix(r, q, p));
+
+    }
+
+    private void checkAEqualQR(RealMatrix m) {
+        QRDecomposition qr = new QRDecompositionImpl(m);
+        double norm = qr.getQ().multiply(qr.getR()).subtract(m).getNorm();
+        assertEquals(0, norm, normTolerance);
+    }
+
+    /** test the orthogonality of Q 
+     * @throws MatrixVisitorException */
+    public void testQOrthogonal() throws MatrixVisitorException {
+        checkQOrthogonal(MatrixUtils.createRealMatrix(testData3x3NonSingular));
+
+        checkQOrthogonal(MatrixUtils.createRealMatrix(testData3x3Singular));
+
+        checkQOrthogonal(MatrixUtils.createRealMatrix(testData3x4));
+
+        checkQOrthogonal(MatrixUtils.createRealMatrix(testData4x3));
+
+        Random r = new Random(643895747384642l);
+        int    p = (5 * BlockRealMatrix.BLOCK_SIZE) / 4;
+        int    q = (7 * BlockRealMatrix.BLOCK_SIZE) / 4;
+        checkQOrthogonal(createTestMatrix(r, p, q));
+
+        checkQOrthogonal(createTestMatrix(r, q, p));
+
+    }
+
+    private void checkQOrthogonal(RealMatrix m) {
+        QRDecomposition qr = new QRDecompositionImpl(m);
+        RealMatrix eye = MatrixUtils.createRealIdentityMatrix(m.getRowDimension());
+        double norm = qr.getQT().multiply(qr.getQ()).subtract(eye).getNorm();
+        assertEquals(0, norm, normTolerance);
+    }
+
+    /** test that R is upper triangular */
+    public void testRUpperTriangular() throws MatrixVisitorException {
+        RealMatrix matrix = MatrixUtils.createRealMatrix(testData3x3NonSingular);
+        checkUpperTriangular(new QRDecompositionImpl(matrix).getR());
+
+        matrix = MatrixUtils.createRealMatrix(testData3x3Singular);
+        checkUpperTriangular(new QRDecompositionImpl(matrix).getR());
+
+        matrix = MatrixUtils.createRealMatrix(testData3x4);
+        checkUpperTriangular(new QRDecompositionImpl(matrix).getR());
+
+        matrix = MatrixUtils.createRealMatrix(testData4x3);
+        checkUpperTriangular(new QRDecompositionImpl(matrix).getR());
+
+        Random r = new Random(643895747384642l);
+        int    p = (5 * BlockRealMatrix.BLOCK_SIZE) / 4;
+        int    q = (7 * BlockRealMatrix.BLOCK_SIZE) / 4;
+        matrix = createTestMatrix(r, p, q);
+        checkUpperTriangular(new QRDecompositionImpl(matrix).getR());
+
+        matrix = createTestMatrix(r, p, q);
+        checkUpperTriangular(new QRDecompositionImpl(matrix).getR());
+
+    }
+
+    private void checkUpperTriangular(RealMatrix m) throws MatrixVisitorException {
+        m.walkInOptimizedOrder(new DefaultRealMatrixPreservingVisitor() {
+            @Override
+            public void visit(int row, int column, double value) {
+                if (column < row) {
+                    assertEquals(0.0, value, entryTolerance);
+                }
+            }
+        });
+    }
+
+    /** test that H is trapezoidal 
+     * @throws MatrixVisitorException */
+    public void testHTrapezoidal() throws MatrixVisitorException {
+        RealMatrix matrix = MatrixUtils.createRealMatrix(testData3x3NonSingular);
+        checkTrapezoidal(new QRDecompositionImpl(matrix).getH());
+
+        matrix = MatrixUtils.createRealMatrix(testData3x3Singular);
+        checkTrapezoidal(new QRDecompositionImpl(matrix).getH());
+
+        matrix = MatrixUtils.createRealMatrix(testData3x4);
+        checkTrapezoidal(new QRDecompositionImpl(matrix).getH());
+
+        matrix = MatrixUtils.createRealMatrix(testData4x3);
+        checkTrapezoidal(new QRDecompositionImpl(matrix).getH());
+
+        Random r = new Random(643895747384642l);
+        int    p = (5 * BlockRealMatrix.BLOCK_SIZE) / 4;
+        int    q = (7 * BlockRealMatrix.BLOCK_SIZE) / 4;
+        matrix = createTestMatrix(r, p, q);
+        checkTrapezoidal(new QRDecompositionImpl(matrix).getH());
+
+        matrix = createTestMatrix(r, p, q);
+        checkTrapezoidal(new QRDecompositionImpl(matrix).getH());
+
+    }
+
+    private void checkTrapezoidal(RealMatrix m) throws MatrixVisitorException {
+        m.walkInOptimizedOrder(new DefaultRealMatrixPreservingVisitor() {
+            @Override
+            public void visit(int row, int column, double value) {
+                if (column > row) {
+                    assertEquals(0.0, value, entryTolerance);
+                }
+            }
+        });
+    }
+    /** test matrices values */
+    public void testMatricesValues() {
+        QRDecomposition qr =
+            new QRDecompositionImpl(MatrixUtils.createRealMatrix(testData3x3NonSingular));
+        RealMatrix qRef = MatrixUtils.createRealMatrix(new double[][] {
+                { -12.0 / 14.0,   69.0 / 175.0,  -58.0 / 175.0 },
+                {  -6.0 / 14.0, -158.0 / 175.0,    6.0 / 175.0 },
+                {   4.0 / 14.0,  -30.0 / 175.0, -165.0 / 175.0 }
+        });
+        RealMatrix rRef = MatrixUtils.createRealMatrix(new double[][] {
+                { -14.0,  -21.0, 14.0 },
+                {   0.0, -175.0, 70.0 },
+                {   0.0,    0.0, 35.0 }
+        });
+        RealMatrix hRef = MatrixUtils.createRealMatrix(new double[][] {
+                { 26.0 / 14.0, 0.0, 0.0 },
+                {  6.0 / 14.0, 648.0 / 325.0, 0.0 },
+                { -4.0 / 14.0,  36.0 / 325.0, 2.0 }
+        });
+
+        // check values against known references
+        RealMatrix q = qr.getQ();
+        assertEquals(0, q.subtract(qRef).getNorm(), 1.0e-13);
+        RealMatrix qT = qr.getQT();
+        assertEquals(0, qT.subtract(qRef.transpose()).getNorm(), 1.0e-13);
+        RealMatrix r = qr.getR();
+        assertEquals(0, r.subtract(rRef).getNorm(), 1.0e-13);
+        RealMatrix h = qr.getH();
+        assertEquals(0, h.subtract(hRef).getNorm(), 1.0e-13);
+
+        // check the same cached instance is returned the second time
+        assertTrue(q == qr.getQ());
+        assertTrue(r == qr.getR());
+        assertTrue(h == qr.getH());
+
+    }
+
+    private RealMatrix createTestMatrix(final Random r, final int rows, final int columns) throws MatrixVisitorException {
+        RealMatrix m = MatrixUtils.createRealMatrix(rows, columns);
+        m.walkInOptimizedOrder(new DefaultRealMatrixChangingVisitor(){
+            @Override
+            public double visit(int row, int column, double value) {
+                return 2.0 * r.nextDouble() - 1.0;
+            }
+        });
+        return m;
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/linear/QRSolverTest.java b/src/test/java/org/apache/commons/math/linear/QRSolverTest.java
new file mode 100644
index 0000000..ccedc7a
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/linear/QRSolverTest.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.linear;
+
+import java.util.Random;
+
+import org.apache.commons.math.linear.MatrixVisitorException;
+
+import junit.framework.TestCase;
+
+public class QRSolverTest extends TestCase {
+    double[][] testData3x3NonSingular = {
+            { 12, -51,   4 },
+            {  6, 167, -68 },
+            { -4,  24, -41 }
+    };
+
+    double[][] testData3x3Singular = {
+            { 1, 2,  2 },
+            { 2, 4,  6 },
+            { 4, 8, 12 }
+    };
+
+    double[][] testData3x4 = {
+            { 12, -51,   4, 1 },
+            {  6, 167, -68, 2 },
+            { -4,  24, -41, 3 }
+    };
+
+    double[][] testData4x3 = {
+            { 12, -51,   4 },
+            {  6, 167, -68 },
+            { -4,  24, -41 },
+            { -5,  34,   7 }
+    };
+
+    public QRSolverTest(String name) {
+        super(name);
+    }
+
+    /** test rank */
+    public void testRank() {
+        DecompositionSolver solver =
+            new QRDecompositionImpl(MatrixUtils.createRealMatrix(testData3x3NonSingular)).getSolver();
+        assertTrue(solver.isNonSingular());
+
+        solver = new QRDecompositionImpl(MatrixUtils.createRealMatrix(testData3x3Singular)).getSolver();
+        assertFalse(solver.isNonSingular());
+
+        solver = new QRDecompositionImpl(MatrixUtils.createRealMatrix(testData3x4)).getSolver();
+        assertTrue(solver.isNonSingular());
+
+        solver = new QRDecompositionImpl(MatrixUtils.createRealMatrix(testData4x3)).getSolver();
+        assertTrue(solver.isNonSingular());
+
+    }
+
+    /** test solve dimension errors */
+    public void testSolveDimensionErrors() {
+        DecompositionSolver solver =
+            new QRDecompositionImpl(MatrixUtils.createRealMatrix(testData3x3NonSingular)).getSolver();
+        RealMatrix b = MatrixUtils.createRealMatrix(new double[2][2]);
+        try {
+            solver.solve(b);
+            fail("an exception should have been thrown");
+        } catch (IllegalArgumentException iae) {
+            // expected behavior
+        }
+        try {
+            solver.solve(b.getColumn(0));
+            fail("an exception should have been thrown");
+        } catch (IllegalArgumentException iae) {
+            // expected behavior
+        }
+        try {
+            solver.solve(b.getColumnVector(0));
+            fail("an exception should have been thrown");
+        } catch (IllegalArgumentException iae) {
+            // expected behavior
+        }
+    }
+
+    /** test solve rank errors */
+    public void testSolveRankErrors() {
+        DecompositionSolver solver =
+            new QRDecompositionImpl(MatrixUtils.createRealMatrix(testData3x3Singular)).getSolver();
+        RealMatrix b = MatrixUtils.createRealMatrix(new double[3][2]);
+        try {
+            solver.solve(b);
+            fail("an exception should have been thrown");
+        } catch (InvalidMatrixException iae) {
+            // expected behavior
+        }
+        try {
+            solver.solve(b.getColumn(0));
+            fail("an exception should have been thrown");
+        } catch (InvalidMatrixException iae) {
+            // expected behavior
+        }
+        try {
+            solver.solve(b.getColumnVector(0));
+            fail("an exception should have been thrown");
+        } catch (InvalidMatrixException iae) {
+            // expected behavior
+        }
+    }
+
+    /** test solve */
+    public void testSolve() {
+        QRDecomposition decomposition =
+            new QRDecompositionImpl(MatrixUtils.createRealMatrix(testData3x3NonSingular));
+        DecompositionSolver solver = decomposition.getSolver();
+        RealMatrix b = MatrixUtils.createRealMatrix(new double[][] {
+                { -102, 12250 }, { 544, 24500 }, { 167, -36750 }
+        });
+        RealMatrix xRef = MatrixUtils.createRealMatrix(new double[][] {
+                { 1, 2515 }, { 2, 422 }, { -3, 898 }
+        });
+
+        // using RealMatrix
+        assertEquals(0, solver.solve(b).subtract(xRef).getNorm(), 2.0e-16 * xRef.getNorm());
+
+        // using double[]
+        for (int i = 0; i < b.getColumnDimension(); ++i) {
+            final double[] x = solver.solve(b.getColumn(i));
+            final double error = new ArrayRealVector(x).subtract(xRef.getColumnVector(i)).getNorm();
+            assertEquals(0, error, 3.0e-16 * xRef.getColumnVector(i).getNorm());
+        }
+
+        // using ArrayRealVector
+        for (int i = 0; i < b.getColumnDimension(); ++i) {
+            final RealVector x = solver.solve(b.getColumnVector(i));
+            final double error = x.subtract(xRef.getColumnVector(i)).getNorm();
+            assertEquals(0, error, 3.0e-16 * xRef.getColumnVector(i).getNorm());
+        }
+
+        // using RealVector with an alternate implementation
+        for (int i = 0; i < b.getColumnDimension(); ++i) {
+            ArrayRealVectorTest.RealVectorTestImpl v =
+                new ArrayRealVectorTest.RealVectorTestImpl(b.getColumn(i));
+            final RealVector x = solver.solve(v);
+            final double error = x.subtract(xRef.getColumnVector(i)).getNorm();
+            assertEquals(0, error, 3.0e-16 * xRef.getColumnVector(i).getNorm());
+        }
+
+    }
+
+    public void testOverdetermined() throws MatrixVisitorException {
+        final Random r    = new Random(5559252868205245l);
+        int          p    = (7 * BlockRealMatrix.BLOCK_SIZE) / 4;
+        int          q    = (5 * BlockRealMatrix.BLOCK_SIZE) / 4;
+        RealMatrix   a    = createTestMatrix(r, p, q);
+        RealMatrix   xRef = createTestMatrix(r, q, BlockRealMatrix.BLOCK_SIZE + 3);
+
+        // build a perturbed system: A.X + noise = B
+        RealMatrix b = a.multiply(xRef);
+        final double noise = 0.001;
+        b.walkInOptimizedOrder(new DefaultRealMatrixChangingVisitor() {
+            @Override
+            public double visit(int row, int column, double value) {
+                return value * (1.0 + noise * (2 * r.nextDouble() - 1));
+            }
+        });
+
+        // despite perturbation, the least square solution should be pretty good
+        RealMatrix x = new QRDecompositionImpl(a).getSolver().solve(b);
+        assertEquals(0, x.subtract(xRef).getNorm(), 0.01 * noise * p * q);
+
+    }
+
+    public void testUnderdetermined() throws MatrixVisitorException {
+        final Random r    = new Random(42185006424567123l);
+        int          p    = (5 * BlockRealMatrix.BLOCK_SIZE) / 4;
+        int          q    = (7 * BlockRealMatrix.BLOCK_SIZE) / 4;
+        RealMatrix   a    = createTestMatrix(r, p, q);
+        RealMatrix   xRef = createTestMatrix(r, q, BlockRealMatrix.BLOCK_SIZE + 3);
+        RealMatrix   b    = a.multiply(xRef);
+        RealMatrix   x = new QRDecompositionImpl(a).getSolver().solve(b);
+
+        // too many equations, the system cannot be solved at all
+        assertTrue(x.subtract(xRef).getNorm() / (p * q) > 0.01);
+
+        // the last unknown should have been set to 0
+        assertEquals(0.0, x.getSubMatrix(p, q - 1, 0, x.getColumnDimension() - 1).getNorm());
+
+    }
+
+    private RealMatrix createTestMatrix(final Random r, final int rows, final int columns) throws MatrixVisitorException {
+        RealMatrix m = MatrixUtils.createRealMatrix(rows, columns);
+        m.walkInOptimizedOrder(new DefaultRealMatrixChangingVisitor(){
+            @Override
+            public double visit(int row, int column, double value) {
+                return 2.0 * r.nextDouble() - 1.0;
+            }
+        });
+        return m;
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/linear/RealMatrixImplTest.java b/src/test/java/org/apache/commons/math/linear/RealMatrixImplTest.java
new file mode 100644
index 0000000..2007ce1
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/linear/RealMatrixImplTest.java
@@ -0,0 +1,1008 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 junit.framework.TestCase;
+
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.linear.MatrixVisitorException;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Test cases for the {@link RealMatrixImpl} class.
+ *
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ */
+ at Deprecated
+public final class RealMatrixImplTest extends TestCase {
+
+    // 3 x 3 identity matrix
+    protected double[][] id = { {1d,0d,0d}, {0d,1d,0d}, {0d,0d,1d} };
+
+    // Test data for group operations
+    protected double[][] testData = { {1d,2d,3d}, {2d,5d,3d}, {1d,0d,8d} };
+    protected double[][] testDataLU = {{2d, 5d, 3d}, {.5d, -2.5d, 6.5d}, {0.5d, 0.2d, .2d}};
+    protected double[][] testDataPlus2 = { {3d,4d,5d}, {4d,7d,5d}, {3d,2d,10d} };
+    protected double[][] testDataMinus = { {-1d,-2d,-3d}, {-2d,-5d,-3d},
+       {-1d,0d,-8d} };
+    protected double[] testDataRow1 = {1d,2d,3d};
+    protected double[] testDataCol3 = {3d,3d,8d};
+    protected double[][] testDataInv =
+        { {-40d,16d,9d}, {13d,-5d,-3d}, {5d,-2d,-1d} };
+    protected double[] preMultTest = {8,12,33};
+    protected double[][] testData2 ={ {1d,2d,3d}, {2d,5d,3d}};
+    protected double[][] testData2T = { {1d,2d}, {2d,5d}, {3d,3d}};
+    protected double[][] testDataPlusInv =
+        { {-39d,18d,12d}, {15d,0d,0d}, {6d,-2d,7d} };
+
+    // lu decomposition tests
+    protected double[][] luData = { {2d,3d,3d}, {0d,5d,7d}, {6d,9d,8d} };
+    protected double[][] luDataLUDecomposition = { {6d,9d,8d}, {0d,5d,7d},
+            {0.33333333333333,0d,0.33333333333333} };
+
+    // singular matrices
+    protected double[][] singular = { {2d,3d}, {2d,3d} };
+    protected double[][] bigSingular = {{1d,2d,3d,4d}, {2d,5d,3d,4d},
+        {7d,3d,256d,1930d}, {3d,7d,6d,8d}}; // 4th row = 1st + 2nd
+    protected double[][] detData = { {1d,2d,3d}, {4d,5d,6d}, {7d,8d,10d} };
+    protected double[][] detData2 = { {1d, 3d}, {2d, 4d}};
+
+    // vectors
+    protected double[] testVector = {1,2,3};
+    protected double[] testVector2 = {1,2,3,4};
+
+    // submatrix accessor tests
+    protected double[][] subTestData = {{1, 2, 3, 4}, {1.5, 2.5, 3.5, 4.5},
+            {2, 4, 6, 8}, {4, 5, 6, 7}};
+    // array selections
+    protected double[][] subRows02Cols13 = { {2, 4}, {4, 8}};
+    protected double[][] subRows03Cols12 = { {2, 3}, {5, 6}};
+    protected double[][] subRows03Cols123 = { {2, 3, 4} , {5, 6, 7}};
+    // effective permutations
+    protected double[][] subRows20Cols123 = { {4, 6, 8} , {2, 3, 4}};
+    protected double[][] subRows31Cols31 = {{7, 5}, {4.5, 2.5}};
+    // contiguous ranges
+    protected double[][] subRows01Cols23 = {{3,4} , {3.5, 4.5}};
+    protected double[][] subRows23Cols00 = {{2} , {4}};
+    protected double[][] subRows00Cols33 = {{4}};
+    // row matrices
+    protected double[][] subRow0 = {{1,2,3,4}};
+    protected double[][] subRow3 = {{4,5,6,7}};
+    // column matrices
+    protected double[][] subColumn1 = {{2}, {2.5}, {4}, {5}};
+    protected double[][] subColumn3 = {{4}, {4.5}, {8}, {7}};
+
+    // tolerances
+    protected double entryTolerance = 10E-16;
+    protected double normTolerance = 10E-14;
+
+    public RealMatrixImplTest(String name) {
+        super(name);
+    }
+
+    /** test dimensions */
+    public void testDimensions() {
+        RealMatrixImpl m = new RealMatrixImpl(testData);
+        RealMatrixImpl m2 = new RealMatrixImpl(testData2);
+        assertEquals("testData row dimension",3,m.getRowDimension());
+        assertEquals("testData column dimension",3,m.getColumnDimension());
+        assertTrue("testData is square",m.isSquare());
+        assertEquals("testData2 row dimension",m2.getRowDimension(),2);
+        assertEquals("testData2 column dimension",m2.getColumnDimension(),3);
+        assertTrue("testData2 is not square",!m2.isSquare());
+    }
+
+    /** test copy functions */
+    public void testCopyFunctions() {
+        RealMatrixImpl m1 = new RealMatrixImpl(testData);
+        RealMatrixImpl m2 = new RealMatrixImpl(m1.getData());
+        assertEquals(m2,m1);
+        RealMatrixImpl m3 = new RealMatrixImpl(testData);
+        RealMatrixImpl m4 = new RealMatrixImpl(m3.getData(), false);
+        assertEquals(m4,m3);
+    }
+
+    /** test add */
+    public void testAdd() {
+        RealMatrixImpl m = new RealMatrixImpl(testData);
+        RealMatrixImpl mInv = new RealMatrixImpl(testDataInv);
+        RealMatrix mPlusMInv = m.add(mInv);
+        double[][] sumEntries = mPlusMInv.getData();
+        for (int row = 0; row < m.getRowDimension(); row++) {
+            for (int col = 0; col < m.getColumnDimension(); col++) {
+                assertEquals("sum entry entry",
+                    testDataPlusInv[row][col],sumEntries[row][col],
+                        entryTolerance);
+            }
+        }
+    }
+
+    /** test add failure */
+    public void testAddFail() {
+        RealMatrixImpl m = new RealMatrixImpl(testData);
+        RealMatrixImpl m2 = new RealMatrixImpl(testData2);
+        try {
+            m.add(m2);
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    /** test norm */
+    public void testNorm() {
+        RealMatrixImpl m = new RealMatrixImpl(testData);
+        RealMatrixImpl m2 = new RealMatrixImpl(testData2);
+        assertEquals("testData norm",14d,m.getNorm(),entryTolerance);
+        assertEquals("testData2 norm",7d,m2.getNorm(),entryTolerance);
+    }
+
+    /** test Frobenius norm */
+    public void testFrobeniusNorm() {
+        RealMatrixImpl m = new RealMatrixImpl(testData);
+        RealMatrixImpl m2 = new RealMatrixImpl(testData2);
+        assertEquals("testData Frobenius norm", FastMath.sqrt(117.0), m.getFrobeniusNorm(), entryTolerance);
+        assertEquals("testData2 Frobenius norm", FastMath.sqrt(52.0), m2.getFrobeniusNorm(), entryTolerance);
+    }
+
+     /** test m-n = m + -n */
+    public void testPlusMinus() {
+        RealMatrixImpl m = new RealMatrixImpl(testData);
+        RealMatrixImpl m2 = new RealMatrixImpl(testDataInv);
+        TestUtils.assertEquals("m-n = m + -n",m.subtract(m2),
+            m2.scalarMultiply(-1d).add(m),entryTolerance);
+        try {
+            m.subtract(new RealMatrixImpl(testData2));
+            fail("Expecting illegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    /** test multiply */
+     public void testMultiply() {
+        RealMatrixImpl m = new RealMatrixImpl(testData);
+        RealMatrixImpl mInv = new RealMatrixImpl(testDataInv);
+        RealMatrixImpl identity = new RealMatrixImpl(id);
+        RealMatrixImpl m2 = new RealMatrixImpl(testData2);
+        TestUtils.assertEquals("inverse multiply",m.multiply(mInv),
+            identity,entryTolerance);
+        TestUtils.assertEquals("inverse multiply",mInv.multiply(m),
+            identity,entryTolerance);
+        TestUtils.assertEquals("identity multiply",m.multiply(identity),
+            m,entryTolerance);
+        TestUtils.assertEquals("identity multiply",identity.multiply(mInv),
+            mInv,entryTolerance);
+        TestUtils.assertEquals("identity multiply",m2.multiply(identity),
+            m2,entryTolerance);
+        try {
+            m.multiply(new RealMatrixImpl(bigSingular));
+            fail("Expecting illegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    //Additional Test for RealMatrixImplTest.testMultiply
+
+    private double[][] d3 = new double[][] {{1,2,3,4},{5,6,7,8}};
+    private double[][] d4 = new double[][] {{1},{2},{3},{4}};
+    private double[][] d5 = new double[][] {{30},{70}};
+
+    public void testMultiply2() {
+       RealMatrix m3 = new RealMatrixImpl(d3);
+       RealMatrix m4 = new RealMatrixImpl(d4);
+       RealMatrix m5 = new RealMatrixImpl(d5);
+       TestUtils.assertEquals("m3*m4=m5", m3.multiply(m4), m5, entryTolerance);
+   }
+
+    /** test trace */
+    public void testTrace() {
+        RealMatrix m = new RealMatrixImpl(id);
+        assertEquals("identity trace",3d,m.getTrace(),entryTolerance);
+        m = new RealMatrixImpl(testData2);
+        try {
+            m.getTrace();
+            fail("Expecting NonSquareMatrixException");
+        } catch (NonSquareMatrixException ex) {
+            // ignored
+        }
+    }
+
+    /** test sclarAdd */
+    public void testScalarAdd() {
+        RealMatrix m = new RealMatrixImpl(testData);
+        TestUtils.assertEquals("scalar add",new RealMatrixImpl(testDataPlus2),
+            m.scalarAdd(2d),entryTolerance);
+    }
+
+    /** test operate */
+    public void testOperate() {
+        RealMatrix m = new RealMatrixImpl(id);
+        TestUtils.assertEquals("identity operate", testVector,
+                    m.operate(testVector), entryTolerance);
+        TestUtils.assertEquals("identity operate", testVector,
+                    m.operate(new ArrayRealVector(testVector)).getData(), entryTolerance);
+        m = new RealMatrixImpl(bigSingular);
+        try {
+            m.operate(testVector);
+            fail("Expecting illegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    /** test issue MATH-209 */
+    public void testMath209() {
+        RealMatrix a = new RealMatrixImpl(new double[][] {
+                { 1, 2 }, { 3, 4 }, { 5, 6 }
+        }, false);
+        double[] b = a.operate(new double[] { 1, 1 });
+        assertEquals(a.getRowDimension(), b.length);
+        assertEquals( 3.0, b[0], 1.0e-12);
+        assertEquals( 7.0, b[1], 1.0e-12);
+        assertEquals(11.0, b[2], 1.0e-12);
+    }
+
+    /** test transpose */
+    public void testTranspose() {
+        RealMatrix m = new RealMatrixImpl(testData);
+        RealMatrix mIT = new LUDecompositionImpl(m).getSolver().getInverse().transpose();
+        RealMatrix mTI = new LUDecompositionImpl(m.transpose()).getSolver().getInverse();
+        TestUtils.assertEquals("inverse-transpose", mIT, mTI, normTolerance);
+        m = new RealMatrixImpl(testData2);
+        RealMatrix mt = new RealMatrixImpl(testData2T);
+        TestUtils.assertEquals("transpose",mt,m.transpose(),normTolerance);
+    }
+
+    /** test preMultiply by vector */
+    public void testPremultiplyVector() {
+        RealMatrix m = new RealMatrixImpl(testData);
+        TestUtils.assertEquals("premultiply", m.preMultiply(testVector),
+                    preMultTest, normTolerance);
+        TestUtils.assertEquals("premultiply", m.preMultiply(new ArrayRealVector(testVector).getData()),
+                    preMultTest, normTolerance);
+        m = new RealMatrixImpl(bigSingular);
+        try {
+            m.preMultiply(testVector);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    public void testPremultiply() {
+        RealMatrix m3 = new RealMatrixImpl(d3);
+        RealMatrix m4 = new RealMatrixImpl(d4);
+        RealMatrix m5 = new RealMatrixImpl(d5);
+        TestUtils.assertEquals("m3*m4=m5", m4.preMultiply(m3), m5, entryTolerance);
+
+        RealMatrixImpl m = new RealMatrixImpl(testData);
+        RealMatrixImpl mInv = new RealMatrixImpl(testDataInv);
+        RealMatrixImpl identity = new RealMatrixImpl(id);
+        TestUtils.assertEquals("inverse multiply",m.preMultiply(mInv),
+                identity,entryTolerance);
+        TestUtils.assertEquals("inverse multiply",mInv.preMultiply(m),
+                identity,entryTolerance);
+        TestUtils.assertEquals("identity multiply",m.preMultiply(identity),
+                m,entryTolerance);
+        TestUtils.assertEquals("identity multiply",identity.preMultiply(mInv),
+                mInv,entryTolerance);
+        try {
+            m.preMultiply(new RealMatrixImpl(bigSingular));
+            fail("Expecting illegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    public void testGetVectors() {
+        RealMatrix m = new RealMatrixImpl(testData);
+        TestUtils.assertEquals("get row",m.getRow(0),testDataRow1,entryTolerance);
+        TestUtils.assertEquals("get col",m.getColumn(2),testDataCol3,entryTolerance);
+        try {
+            m.getRow(10);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // ignored
+        }
+        try {
+            m.getColumn(-1);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // ignored
+        }
+    }
+
+    public void testGetEntry() {
+        RealMatrix m = new RealMatrixImpl(testData);
+        assertEquals("get entry",m.getEntry(0,1),2d,entryTolerance);
+        try {
+            m.getEntry(10, 4);
+            fail ("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    /** test examples in user guide */
+    public void testExamples() {
+        // Create a real matrix with two rows and three columns
+        double[][] matrixData = { {1d,2d,3d}, {2d,5d,3d}};
+        RealMatrix m = new RealMatrixImpl(matrixData);
+        // One more with three rows, two columns
+        double[][] matrixData2 = { {1d,2d}, {2d,5d}, {1d, 7d}};
+        RealMatrix n = new RealMatrixImpl(matrixData2);
+        // Now multiply m by n
+        RealMatrix p = m.multiply(n);
+        assertEquals(2, p.getRowDimension());
+        assertEquals(2, p.getColumnDimension());
+        // Invert p
+        RealMatrix pInverse = new LUDecompositionImpl(p).getSolver().getInverse();
+        assertEquals(2, pInverse.getRowDimension());
+        assertEquals(2, pInverse.getColumnDimension());
+
+        // Solve example
+        double[][] coefficientsData = {{2, 3, -2}, {-1, 7, 6}, {4, -3, -5}};
+        RealMatrix coefficients = new RealMatrixImpl(coefficientsData);
+        double[] constants = {1, -2, 1};
+        double[] solution = new LUDecompositionImpl(coefficients).getSolver().solve(constants);
+        assertEquals(2 * solution[0] + 3 * solution[1] -2 * solution[2], constants[0], 1E-12);
+        assertEquals(-1 * solution[0] + 7 * solution[1] + 6 * solution[2], constants[1], 1E-12);
+        assertEquals(4 * solution[0] - 3 * solution[1] -5 * solution[2], constants[2], 1E-12);
+
+    }
+
+    // test submatrix accessors
+    public void testGetSubMatrix() {
+        RealMatrix m = new RealMatrixImpl(subTestData);
+        checkGetSubMatrix(m, subRows23Cols00,  2 , 3 , 0, 0, false);
+        checkGetSubMatrix(m, subRows00Cols33,  0 , 0 , 3, 3, false);
+        checkGetSubMatrix(m, subRows01Cols23,  0 , 1 , 2, 3, false);
+        checkGetSubMatrix(m, subRows02Cols13,  new int[] { 0, 2 }, new int[] { 1, 3 },    false);
+        checkGetSubMatrix(m, subRows03Cols12,  new int[] { 0, 3 }, new int[] { 1, 2 },    false);
+        checkGetSubMatrix(m, subRows03Cols123, new int[] { 0, 3 }, new int[] { 1, 2, 3 }, false);
+        checkGetSubMatrix(m, subRows20Cols123, new int[] { 2, 0 }, new int[] { 1, 2, 3 }, false);
+        checkGetSubMatrix(m, subRows31Cols31,  new int[] { 3, 1 }, new int[] { 3, 1 },    false);
+        checkGetSubMatrix(m, subRows31Cols31,  new int[] { 3, 1 }, new int[] { 3, 1 },    false);
+        checkGetSubMatrix(m, null,  1, 0, 2, 4, true);
+        checkGetSubMatrix(m, null, -1, 1, 2, 2, true);
+        checkGetSubMatrix(m, null,  1, 0, 2, 2, true);
+        checkGetSubMatrix(m, null,  1, 0, 2, 4, true);
+        checkGetSubMatrix(m, null, new int[] {},    new int[] { 0 }, true);
+        checkGetSubMatrix(m, null, new int[] { 0 }, new int[] { 4 }, true);
+    }
+
+    private void checkGetSubMatrix(RealMatrix m, double[][] reference,
+                                   int startRow, int endRow, int startColumn, int endColumn,
+                                   boolean mustFail) {
+        try {
+            RealMatrix sub = m.getSubMatrix(startRow, endRow, startColumn, endColumn);
+            assertEquals(new RealMatrixImpl(reference), sub);
+            if (mustFail) {
+                fail("Expecting MatrixIndexException");
+            }
+        } catch (MatrixIndexException e) {
+            if (!mustFail) {
+                throw e;
+            }
+        }
+    }
+
+    private void checkGetSubMatrix(RealMatrix m, double[][] reference,
+                                   int[] selectedRows, int[] selectedColumns,
+                                   boolean mustFail) {
+        try {
+            RealMatrix sub = m.getSubMatrix(selectedRows, selectedColumns);
+            assertEquals(new RealMatrixImpl(reference), sub);
+            if (mustFail) {
+                fail("Expecting MatrixIndexException");
+            }
+        } catch (MatrixIndexException e) {
+            if (!mustFail) {
+                throw e;
+            }
+        }
+    }
+
+    public void testCopySubMatrix() {
+        RealMatrix m = new RealMatrixImpl(subTestData);
+        checkCopy(m, subRows23Cols00,  2 , 3 , 0, 0, false);
+        checkCopy(m, subRows00Cols33,  0 , 0 , 3, 3, false);
+        checkCopy(m, subRows01Cols23,  0 , 1 , 2, 3, false);
+        checkCopy(m, subRows02Cols13,  new int[] { 0, 2 }, new int[] { 1, 3 },    false);
+        checkCopy(m, subRows03Cols12,  new int[] { 0, 3 }, new int[] { 1, 2 },    false);
+        checkCopy(m, subRows03Cols123, new int[] { 0, 3 }, new int[] { 1, 2, 3 }, false);
+        checkCopy(m, subRows20Cols123, new int[] { 2, 0 }, new int[] { 1, 2, 3 }, false);
+        checkCopy(m, subRows31Cols31,  new int[] { 3, 1 }, new int[] { 3, 1 },    false);
+        checkCopy(m, subRows31Cols31,  new int[] { 3, 1 }, new int[] { 3, 1 },    false);
+
+        checkCopy(m, null,  1, 0, 2, 4, true);
+        checkCopy(m, null, -1, 1, 2, 2, true);
+        checkCopy(m, null,  1, 0, 2, 2, true);
+        checkCopy(m, null,  1, 0, 2, 4, true);
+        checkCopy(m, null, new int[] {},    new int[] { 0 }, true);
+        checkCopy(m, null, new int[] { 0 }, new int[] { 4 }, true);
+    }
+
+    private void checkCopy(RealMatrix m, double[][] reference,
+                           int startRow, int endRow, int startColumn, int endColumn,
+                           boolean mustFail) {
+        try {
+            double[][] sub = (reference == null) ?
+                             new double[1][1] :
+                             new double[reference.length][reference[0].length];
+            m.copySubMatrix(startRow, endRow, startColumn, endColumn, sub);
+            assertEquals(new RealMatrixImpl(reference), new RealMatrixImpl(sub));
+            if (mustFail) {
+                fail("Expecting MatrixIndexException");
+            }
+        } catch (MatrixIndexException e) {
+            if (!mustFail) {
+                throw e;
+            }
+        }
+    }
+
+    private void checkCopy(RealMatrix m, double[][] reference,
+                           int[] selectedRows, int[] selectedColumns,
+                           boolean mustFail) {
+        try {
+            double[][] sub = (reference == null) ?
+                    new double[1][1] :
+                    new double[reference.length][reference[0].length];
+            m.copySubMatrix(selectedRows, selectedColumns, sub);
+            assertEquals(new RealMatrixImpl(reference), new RealMatrixImpl(sub));
+            if (mustFail) {
+                fail("Expecting MatrixIndexException");
+            }
+        } catch (MatrixIndexException e) {
+            if (!mustFail) {
+                throw e;
+            }
+        }
+    }
+
+    public void testGetRowMatrix() {
+        RealMatrix m = new RealMatrixImpl(subTestData);
+        RealMatrix mRow0 = new RealMatrixImpl(subRow0);
+        RealMatrix mRow3 = new RealMatrixImpl(subRow3);
+        assertEquals("Row0", mRow0,
+                m.getRowMatrix(0));
+        assertEquals("Row3", mRow3,
+                m.getRowMatrix(3));
+        try {
+            m.getRowMatrix(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getRowMatrix(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testSetRowMatrix() {
+        RealMatrix m = new RealMatrixImpl(subTestData);
+        RealMatrix mRow3 = new RealMatrixImpl(subRow3);
+        assertNotSame(mRow3, m.getRowMatrix(0));
+        m.setRowMatrix(0, mRow3);
+        assertEquals(mRow3, m.getRowMatrix(0));
+        try {
+            m.setRowMatrix(-1, mRow3);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.setRowMatrix(0, m);
+            fail("Expecting InvalidMatrixException");
+        } catch (InvalidMatrixException ex) {
+            // expected
+        }
+    }
+
+    public void testGetColumnMatrix() {
+        RealMatrix m = new RealMatrixImpl(subTestData);
+        RealMatrix mColumn1 = new RealMatrixImpl(subColumn1);
+        RealMatrix mColumn3 = new RealMatrixImpl(subColumn3);
+        assertEquals("Column1", mColumn1,
+                m.getColumnMatrix(1));
+        assertEquals("Column3", mColumn3,
+                m.getColumnMatrix(3));
+        try {
+            m.getColumnMatrix(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getColumnMatrix(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testSetColumnMatrix() {
+        RealMatrix m = new RealMatrixImpl(subTestData);
+        RealMatrix mColumn3 = new RealMatrixImpl(subColumn3);
+        assertNotSame(mColumn3, m.getColumnMatrix(1));
+        m.setColumnMatrix(1, mColumn3);
+        assertEquals(mColumn3, m.getColumnMatrix(1));
+        try {
+            m.setColumnMatrix(-1, mColumn3);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.setColumnMatrix(0, m);
+            fail("Expecting InvalidMatrixException");
+        } catch (InvalidMatrixException ex) {
+            // expected
+        }
+    }
+
+    public void testGetRowVector() {
+        RealMatrix m = new RealMatrixImpl(subTestData);
+        RealVector mRow0 = new ArrayRealVector(subRow0[0]);
+        RealVector mRow3 = new ArrayRealVector(subRow3[0]);
+        assertEquals("Row0", mRow0, m.getRowVector(0));
+        assertEquals("Row3", mRow3, m.getRowVector(3));
+        try {
+            m.getRowVector(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getRowVector(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testSetRowVector() {
+        RealMatrix m = new RealMatrixImpl(subTestData);
+        RealVector mRow3 = new ArrayRealVector(subRow3[0]);
+        assertNotSame(mRow3, m.getRowMatrix(0));
+        m.setRowVector(0, mRow3);
+        assertEquals(mRow3, m.getRowVector(0));
+        try {
+            m.setRowVector(-1, mRow3);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.setRowVector(0, new ArrayRealVector(5));
+            fail("Expecting InvalidMatrixException");
+        } catch (InvalidMatrixException ex) {
+            // expected
+        }
+    }
+
+    public void testGetColumnVector() {
+        RealMatrix m = new RealMatrixImpl(subTestData);
+        RealVector mColumn1 = columnToVector(subColumn1);
+        RealVector mColumn3 = columnToVector(subColumn3);
+        assertEquals("Column1", mColumn1, m.getColumnVector(1));
+        assertEquals("Column3", mColumn3, m.getColumnVector(3));
+        try {
+            m.getColumnVector(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getColumnVector(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testSetColumnVector() {
+        RealMatrix m = new RealMatrixImpl(subTestData);
+        RealVector mColumn3 = columnToVector(subColumn3);
+        assertNotSame(mColumn3, m.getColumnVector(1));
+        m.setColumnVector(1, mColumn3);
+        assertEquals(mColumn3, m.getColumnVector(1));
+        try {
+            m.setColumnVector(-1, mColumn3);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.setColumnVector(0, new ArrayRealVector(5));
+            fail("Expecting InvalidMatrixException");
+        } catch (InvalidMatrixException ex) {
+            // expected
+        }
+    }
+
+    private RealVector columnToVector(double[][] column) {
+        double[] data = new double[column.length];
+        for (int i = 0; i < data.length; ++i) {
+            data[i] = column[i][0];
+        }
+        return new ArrayRealVector(data, false);
+    }
+
+    public void testGetRow() {
+        RealMatrix m = new RealMatrixImpl(subTestData);
+        checkArrays(subRow0[0], m.getRow(0));
+        checkArrays(subRow3[0], m.getRow(3));
+        try {
+            m.getRow(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getRow(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testSetRow() {
+        RealMatrix m = new RealMatrixImpl(subTestData);
+        assertTrue(subRow3[0][0] != m.getRow(0)[0]);
+        m.setRow(0, subRow3[0]);
+        checkArrays(subRow3[0], m.getRow(0));
+        try {
+            m.setRow(-1, subRow3[0]);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.setRow(0, new double[5]);
+            fail("Expecting InvalidMatrixException");
+        } catch (InvalidMatrixException ex) {
+            // expected
+        }
+    }
+
+    public void testGetColumn() {
+        RealMatrix m = new RealMatrixImpl(subTestData);
+        double[] mColumn1 = columnToArray(subColumn1);
+        double[] mColumn3 = columnToArray(subColumn3);
+        checkArrays(mColumn1, m.getColumn(1));
+        checkArrays(mColumn3, m.getColumn(3));
+        try {
+            m.getColumn(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getColumn(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testSetColumn() {
+        RealMatrix m = new RealMatrixImpl(subTestData);
+        double[] mColumn3 = columnToArray(subColumn3);
+        assertTrue(mColumn3[0] != m.getColumn(1)[0]);
+        m.setColumn(1, mColumn3);
+        checkArrays(mColumn3, m.getColumn(1));
+        try {
+            m.setColumn(-1, mColumn3);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.setColumn(0, new double[5]);
+            fail("Expecting InvalidMatrixException");
+        } catch (InvalidMatrixException ex) {
+            // expected
+        }
+    }
+
+    private double[] columnToArray(double[][] column) {
+        double[] data = new double[column.length];
+        for (int i = 0; i < data.length; ++i) {
+            data[i] = column[i][0];
+        }
+        return data;
+    }
+
+    private void checkArrays(double[] expected, double[] actual) {
+        assertEquals(expected.length, actual.length);
+        for (int i = 0; i < expected.length; ++i) {
+            assertEquals(expected[i], actual[i]);
+        }
+    }
+
+    public void testEqualsAndHashCode() {
+        RealMatrixImpl m = new RealMatrixImpl(testData);
+        RealMatrixImpl m1 = (RealMatrixImpl) m.copy();
+        RealMatrixImpl mt = (RealMatrixImpl) m.transpose();
+        assertTrue(m.hashCode() != mt.hashCode());
+        assertEquals(m.hashCode(), m1.hashCode());
+        assertEquals(m, m);
+        assertEquals(m, m1);
+        assertFalse(m.equals(null));
+        assertFalse(m.equals(mt));
+        assertFalse(m.equals(new RealMatrixImpl(bigSingular)));
+    }
+
+    public void testToString() {
+        RealMatrixImpl m = new RealMatrixImpl(testData);
+        assertEquals("RealMatrixImpl{{1.0,2.0,3.0},{2.0,5.0,3.0},{1.0,0.0,8.0}}",
+                m.toString());
+        m = new RealMatrixImpl();
+        assertEquals("RealMatrixImpl{}",
+                m.toString());
+    }
+
+    public void testSetSubMatrix() throws Exception {
+        RealMatrixImpl m = new RealMatrixImpl(testData);
+        m.setSubMatrix(detData2,1,1);
+        RealMatrix expected = MatrixUtils.createRealMatrix
+            (new double[][] {{1.0,2.0,3.0},{2.0,1.0,3.0},{1.0,2.0,4.0}});
+        assertEquals(expected, m);
+
+        m.setSubMatrix(detData2,0,0);
+        expected = MatrixUtils.createRealMatrix
+            (new double[][] {{1.0,3.0,3.0},{2.0,4.0,3.0},{1.0,2.0,4.0}});
+        assertEquals(expected, m);
+
+        m.setSubMatrix(testDataPlus2,0,0);
+        expected = MatrixUtils.createRealMatrix
+            (new double[][] {{3.0,4.0,5.0},{4.0,7.0,5.0},{3.0,2.0,10.0}});
+        assertEquals(expected, m);
+
+        // dimension overflow
+        try {
+            m.setSubMatrix(testData,1,1);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException e) {
+            // expected
+        }
+        // dimension underflow
+        try {
+            m.setSubMatrix(testData,-1,1);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException e) {
+            // expected
+        }
+        try {
+            m.setSubMatrix(testData,1,-1);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException e) {
+            // expected
+        }
+
+        // null
+        try {
+            m.setSubMatrix(null,1,1);
+            fail("expecting NullPointerException");
+        } catch (NullPointerException e) {
+            // expected
+        }
+        RealMatrixImpl m2 = new RealMatrixImpl();
+        try {
+            m2.setSubMatrix(testData,0,1);
+            fail("expecting IllegalStateException");
+        } catch (IllegalStateException e) {
+            // expected
+        }
+        try {
+            m2.setSubMatrix(testData,1,0);
+            fail("expecting IllegalStateException");
+        } catch (IllegalStateException e) {
+            // expected
+        }
+
+        // ragged
+        try {
+            m.setSubMatrix(new double[][] {{1}, {2, 3}}, 0, 0);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        // empty
+        try {
+            m.setSubMatrix(new double[][] {{}}, 0, 0);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+    }
+
+    public void testWalk() throws MatrixIndexException, MatrixVisitorException {
+        int rows    = 150;
+        int columns = 75;
+
+        RealMatrix m = new RealMatrixImpl(rows, columns);
+        m.walkInRowOrder(new SetVisitor());
+        GetVisitor getVisitor = new GetVisitor();
+        m.walkInOptimizedOrder(getVisitor);
+        assertEquals(rows * columns, getVisitor.getCount());
+
+        m = new RealMatrixImpl(rows, columns);
+        m.walkInRowOrder(new SetVisitor(), 1, rows - 2, 1, columns - 2);
+        getVisitor = new GetVisitor();
+        m.walkInOptimizedOrder(getVisitor, 1, rows - 2, 1, columns - 2);
+        assertEquals((rows - 2) * (columns - 2), getVisitor.getCount());
+        for (int i = 0; i < rows; ++i) {
+            assertEquals(0.0, m.getEntry(i, 0), 0);
+            assertEquals(0.0, m.getEntry(i, columns - 1), 0);
+        }
+        for (int j = 0; j < columns; ++j) {
+            assertEquals(0.0, m.getEntry(0, j), 0);
+            assertEquals(0.0, m.getEntry(rows - 1, j), 0);
+        }
+
+        m = new RealMatrixImpl(rows, columns);
+        m.walkInColumnOrder(new SetVisitor());
+        getVisitor = new GetVisitor();
+        m.walkInOptimizedOrder(getVisitor);
+        assertEquals(rows * columns, getVisitor.getCount());
+
+        m = new RealMatrixImpl(rows, columns);
+        m.walkInColumnOrder(new SetVisitor(), 1, rows - 2, 1, columns - 2);
+        getVisitor = new GetVisitor();
+        m.walkInOptimizedOrder(getVisitor, 1, rows - 2, 1, columns - 2);
+        assertEquals((rows - 2) * (columns - 2), getVisitor.getCount());
+        for (int i = 0; i < rows; ++i) {
+            assertEquals(0.0, m.getEntry(i, 0), 0);
+            assertEquals(0.0, m.getEntry(i, columns - 1), 0);
+        }
+        for (int j = 0; j < columns; ++j) {
+            assertEquals(0.0, m.getEntry(0, j), 0);
+            assertEquals(0.0, m.getEntry(rows - 1, j), 0);
+        }
+
+        m = new RealMatrixImpl(rows, columns);
+        m.walkInOptimizedOrder(new SetVisitor());
+        getVisitor = new GetVisitor();
+        m.walkInRowOrder(getVisitor);
+        assertEquals(rows * columns, getVisitor.getCount());
+
+        m = new RealMatrixImpl(rows, columns);
+        m.walkInOptimizedOrder(new SetVisitor(), 1, rows - 2, 1, columns - 2);
+        getVisitor = new GetVisitor();
+        m.walkInRowOrder(getVisitor, 1, rows - 2, 1, columns - 2);
+        assertEquals((rows - 2) * (columns - 2), getVisitor.getCount());
+        for (int i = 0; i < rows; ++i) {
+            assertEquals(0.0, m.getEntry(i, 0), 0);
+            assertEquals(0.0, m.getEntry(i, columns - 1), 0);
+        }
+        for (int j = 0; j < columns; ++j) {
+            assertEquals(0.0, m.getEntry(0, j), 0);
+            assertEquals(0.0, m.getEntry(rows - 1, j), 0);
+        }
+
+        m = new RealMatrixImpl(rows, columns);
+        m.walkInOptimizedOrder(new SetVisitor());
+        getVisitor = new GetVisitor();
+        m.walkInColumnOrder(getVisitor);
+        assertEquals(rows * columns, getVisitor.getCount());
+
+        m = new RealMatrixImpl(rows, columns);
+        m.walkInOptimizedOrder(new SetVisitor(), 1, rows - 2, 1, columns - 2);
+        getVisitor = new GetVisitor();
+        m.walkInColumnOrder(getVisitor, 1, rows - 2, 1, columns - 2);
+        assertEquals((rows - 2) * (columns - 2), getVisitor.getCount());
+        for (int i = 0; i < rows; ++i) {
+            assertEquals(0.0, m.getEntry(i, 0), 0);
+            assertEquals(0.0, m.getEntry(i, columns - 1), 0);
+        }
+        for (int j = 0; j < columns; ++j) {
+            assertEquals(0.0, m.getEntry(0, j), 0);
+            assertEquals(0.0, m.getEntry(rows - 1, j), 0);
+        }
+
+    }
+
+    public void testSerial()  {
+        RealMatrixImpl m = new RealMatrixImpl(testData);
+        assertEquals(m,TestUtils.serializeAndRecover(m));
+    }
+
+
+    private static class SetVisitor extends DefaultRealMatrixChangingVisitor {
+        @Override
+        public double visit(int i, int j, double value) {
+            return i + j / 1024.0;
+        }
+    }
+
+    private static class GetVisitor extends DefaultRealMatrixPreservingVisitor {
+        private int count = 0;
+        @Override
+        public void visit(int i, int j, double value) {
+            ++count;
+            assertEquals(i + j / 1024.0, value, 0.0);
+        }
+        public int getCount() {
+            return count;
+        }
+    }
+
+    //--------------- -----------------Protected methods
+
+    /** extracts the l  and u matrices from compact lu representation */
+    protected void splitLU(RealMatrix lu, double[][] lowerData, double[][] upperData) throws InvalidMatrixException {
+        if (!lu.isSquare() || lowerData.length != lowerData[0].length || upperData.length != upperData[0].length ||
+                lowerData.length != upperData.length
+                || lowerData.length != lu.getRowDimension()) {
+            throw new InvalidMatrixException("incorrect dimensions");
+        }
+        int n = lu.getRowDimension();
+        for (int i = 0; i < n; i++) {
+            for (int j = 0; j < n; j++) {
+                if (j < i) {
+                    lowerData[i][j] = lu.getEntry(i, j);
+                    upperData[i][j] = 0d;
+                } else if (i == j) {
+                    lowerData[i][j] = 1d;
+                    upperData[i][j] = lu.getEntry(i, j);
+                } else {
+                    lowerData[i][j] = 0d;
+                    upperData[i][j] = lu.getEntry(i, j);
+                }
+            }
+        }
+    }
+
+    /** Returns the result of applying the given row permutation to the matrix */
+    protected RealMatrix permuteRows(RealMatrix matrix, int[] permutation) {
+        if (!matrix.isSquare() || matrix.getRowDimension() != permutation.length) {
+            throw new IllegalArgumentException("dimension mismatch");
+        }
+        int n = matrix.getRowDimension();
+        int m = matrix.getColumnDimension();
+        double out[][] = new double[m][n];
+        for (int i = 0; i < n; i++) {
+            for (int j = 0; j < m; j++) {
+                out[i][j] = matrix.getEntry(permutation[i], j);
+            }
+        }
+        return new RealMatrixImpl(out);
+    }
+
+//    /** Useful for debugging */
+//    private void dumpMatrix(RealMatrix m) {
+//          for (int i = 0; i < m.getRowDimension(); i++) {
+//              String os = "";
+//              for (int j = 0; j < m.getColumnDimension(); j++) {
+//                  os += m.getEntry(i, j) + " ";
+//              }
+//              System.out.println(os);
+//          }
+//    }
+
+}
+
diff --git a/src/test/java/org/apache/commons/math/linear/RealVectorFormatAbstractTest.java b/src/test/java/org/apache/commons/math/linear/RealVectorFormatAbstractTest.java
new file mode 100644
index 0000000..800098d
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/linear/RealVectorFormatAbstractTest.java
@@ -0,0 +1,387 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.NumberFormat;
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.util.Locale;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.math.util.CompositeFormat;
+
+public abstract class RealVectorFormatAbstractTest extends TestCase {
+
+    RealVectorFormat realVectorFormat = null;
+    RealVectorFormat realVectorFormatSquare = null;
+
+    protected abstract Locale getLocale();
+
+    protected abstract char getDecimalCharacter();
+
+    @Override
+    public void setUp() throws Exception {
+        realVectorFormat = RealVectorFormat.getInstance(getLocale());
+        final NumberFormat nf = NumberFormat.getInstance(getLocale());
+        nf.setMaximumFractionDigits(2);
+        realVectorFormatSquare = new RealVectorFormat("[", "]", " : ", nf);
+    }
+
+    public void testSimpleNoDecimals() {
+        ArrayRealVector c = new ArrayRealVector(new double[] {1, 1, 1});
+        String expected = "{1; 1; 1}";
+        String actual = realVectorFormat.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void testSimpleWithDecimals() {
+        ArrayRealVector c = new ArrayRealVector(new double[] {1.23, 1.43, 1.63});
+        String expected =
+            "{1"    + getDecimalCharacter() +
+            "23; 1" + getDecimalCharacter() +
+            "43; 1" + getDecimalCharacter() +
+            "63}";
+        String actual = realVectorFormat.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void testSimpleWithDecimalsTrunc() {
+        ArrayRealVector c = new ArrayRealVector(new double[] {1.2323, 1.4343, 1.6333});
+        String expected =
+            "{1"    + getDecimalCharacter() +
+            "23; 1" + getDecimalCharacter() +
+            "43; 1" + getDecimalCharacter() +
+            "63}";
+        String actual = realVectorFormat.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void testNegativeX() {
+        ArrayRealVector c = new ArrayRealVector(new double[] {-1.2323, 1.4343, 1.6333});
+        String expected =
+            "{-1"    + getDecimalCharacter() +
+            "23; 1" + getDecimalCharacter() +
+            "43; 1" + getDecimalCharacter() +
+            "63}";
+        String actual = realVectorFormat.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void testNegativeY() {
+        ArrayRealVector c = new ArrayRealVector(new double[] {1.2323, -1.4343, 1.6333});
+        String expected =
+            "{1"    + getDecimalCharacter() +
+            "23; -1" + getDecimalCharacter() +
+            "43; 1" + getDecimalCharacter() +
+            "63}";
+        String actual = realVectorFormat.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void testNegativeZ() {
+        ArrayRealVector c = new ArrayRealVector(new double[] {1.2323, 1.4343, -1.6333});
+        String expected =
+            "{1"    + getDecimalCharacter() +
+            "23; 1" + getDecimalCharacter() +
+            "43; -1" + getDecimalCharacter() +
+            "63}";
+        String actual = realVectorFormat.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void testNonDefaultSetting() {
+        ArrayRealVector c = new ArrayRealVector(new double[] {1, 1, 1});
+        String expected = "[1 : 1 : 1]";
+        String actual = realVectorFormatSquare.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void testStaticFormatRealVectorImpl() {
+        Locale defaultLocal = Locale.getDefault();
+        Locale.setDefault(getLocale());
+
+        ArrayRealVector c = new ArrayRealVector(new double[] {232.222, -342.33, 432.444});
+        String expected =
+            "{232"    + getDecimalCharacter() +
+            "22; -342" + getDecimalCharacter() +
+            "33; 432" + getDecimalCharacter() +
+            "44}";
+        String actual = RealVectorFormat.formatRealVector(c);
+        assertEquals(expected, actual);
+
+        Locale.setDefault(defaultLocal);
+    }
+
+    public void testNan() {
+        ArrayRealVector c = new ArrayRealVector(new double[] {Double.NaN, Double.NaN, Double.NaN});
+        String expected = "{(NaN); (NaN); (NaN)}";
+        String actual = realVectorFormat.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void testPositiveInfinity() {
+        ArrayRealVector c = new ArrayRealVector(new double[] {
+                Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY
+        });
+        String expected = "{(Infinity); (Infinity); (Infinity)}";
+        String actual = realVectorFormat.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void tesNegativeInfinity() {
+        ArrayRealVector c = new ArrayRealVector(new double[] {
+                Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY
+        });
+        String expected = "{(-Infinity); (-Infinity); (-Infinity)}";
+        String actual = realVectorFormat.format(c);
+        assertEquals(expected, actual);
+    }
+
+    public void testParseSimpleNoDecimals() {
+        String source = "{1; 1; 1}";
+        ArrayRealVector expected = new ArrayRealVector(new double[] {1, 1, 1});
+        try {
+            ArrayRealVector actual = (ArrayRealVector) realVectorFormat.parseObject(source);
+            assertEquals(expected, actual);
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParseIgnoredWhitespace() {
+        ArrayRealVector expected = new ArrayRealVector(new double[] {1, 1, 1});
+        ParsePosition pos1 = new ParsePosition(0);
+        String source1 = "{1;1;1}";
+        assertEquals(expected, realVectorFormat.parseObject(source1, pos1));
+        assertEquals(source1.length(), pos1.getIndex());
+        ParsePosition pos2 = new ParsePosition(0);
+        String source2 = " { 1 ; 1 ; 1 } ";
+        assertEquals(expected, realVectorFormat.parseObject(source2, pos2));
+        assertEquals(source2.length() - 1, pos2.getIndex());
+    }
+
+    public void testParseSimpleWithDecimals() {
+        String source =
+            "{1" + getDecimalCharacter() +
+            "23; 1" + getDecimalCharacter() +
+            "43; 1" + getDecimalCharacter() +
+            "63}";
+        ArrayRealVector expected = new ArrayRealVector(new double[] {1.23, 1.43, 1.63});
+        try {
+            ArrayRealVector actual = (ArrayRealVector) realVectorFormat.parseObject(source);
+            assertEquals(expected, actual);
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParseSimpleWithDecimalsTrunc() {
+        String source =
+            "{1" + getDecimalCharacter() +
+            "2323; 1" + getDecimalCharacter() +
+            "4343; 1" + getDecimalCharacter() +
+            "6333}";
+        ArrayRealVector expected = new ArrayRealVector(new double[] {1.2323, 1.4343, 1.6333});
+        try {
+            ArrayRealVector actual = (ArrayRealVector) realVectorFormat.parseObject(source);
+            assertEquals(expected, actual);
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParseNegativeX() {
+        String source =
+            "{-1" + getDecimalCharacter() +
+            "2323; 1" + getDecimalCharacter() +
+            "4343; 1" + getDecimalCharacter() +
+            "6333}";
+        ArrayRealVector expected = new ArrayRealVector(new double[] {-1.2323, 1.4343, 1.6333});
+        try {
+            ArrayRealVector actual = (ArrayRealVector) realVectorFormat.parseObject(source);
+            assertEquals(expected, actual);
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParseNegativeY() {
+        String source =
+            "{1" + getDecimalCharacter() +
+            "2323; -1" + getDecimalCharacter() +
+            "4343; 1" + getDecimalCharacter() +
+            "6333}";
+        ArrayRealVector expected = new ArrayRealVector(new double[] {1.2323, -1.4343, 1.6333});
+        try {
+            ArrayRealVector actual = (ArrayRealVector) realVectorFormat.parseObject(source);
+            assertEquals(expected, actual);
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParseNegativeZ() {
+        String source =
+            "{1" + getDecimalCharacter() +
+            "2323; 1" + getDecimalCharacter() +
+            "4343; -1" + getDecimalCharacter() +
+            "6333}";
+        ArrayRealVector expected = new ArrayRealVector(new double[] {1.2323, 1.4343, -1.6333});
+        try {
+            ArrayRealVector actual = (ArrayRealVector) realVectorFormat.parseObject(source);
+            assertEquals(expected, actual);
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParseNegativeAll() {
+        String source =
+            "{-1" + getDecimalCharacter() +
+            "2323; -1" + getDecimalCharacter() +
+            "4343; -1" + getDecimalCharacter() +
+            "6333}";
+        ArrayRealVector expected = new ArrayRealVector(new double[] {-1.2323, -1.4343, -1.6333});
+        try {
+            ArrayRealVector actual = (ArrayRealVector) realVectorFormat.parseObject(source);
+            assertEquals(expected, actual);
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParseZeroX() {
+        String source =
+            "{0" + getDecimalCharacter() +
+            "0; -1" + getDecimalCharacter() +
+            "4343; 1" + getDecimalCharacter() +
+            "6333}";
+        ArrayRealVector expected = new ArrayRealVector(new double[] {0.0, -1.4343, 1.6333});
+        try {
+            ArrayRealVector actual = (ArrayRealVector) realVectorFormat.parseObject(source);
+            assertEquals(expected, actual);
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParseNonDefaultSetting() {
+        String source =
+            "[1" + getDecimalCharacter() +
+            "2323 : 1" + getDecimalCharacter() +
+            "4343 : 1" + getDecimalCharacter() +
+            "6333]";
+        ArrayRealVector expected = new ArrayRealVector(new double[] {1.2323, 1.4343, 1.6333});
+        try {
+            ArrayRealVector actual = (ArrayRealVector) realVectorFormatSquare.parseObject(source);
+            assertEquals(expected, actual);
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParseNan() {
+        String source = "{(NaN); (NaN); (NaN)}";
+        try {
+            ArrayRealVector actual = (ArrayRealVector) realVectorFormat.parseObject(source);
+            assertEquals(new ArrayRealVector(new double[] {Double.NaN, Double.NaN, Double.NaN}), actual);
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParsePositiveInfinity() {
+        String source = "{(Infinity); (Infinity); (Infinity)}";
+        try {
+            ArrayRealVector actual = (ArrayRealVector)realVectorFormat.parseObject(source);
+            assertEquals(new ArrayRealVector(new double[] {
+                    Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY
+            }), actual);
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParseNegativeInfinity() {
+        String source = "{(-Infinity); (-Infinity); (-Infinity)}";
+        try {
+            ArrayRealVector actual = (ArrayRealVector)realVectorFormat.parseObject(source);
+            assertEquals(new ArrayRealVector(new double[] {
+                    Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY
+            }), actual);
+        } catch (ParseException ex) {
+            fail(ex.getMessage());
+        }
+    }
+
+    public void testParseNoComponents() {
+        try {
+            realVectorFormat.parseObject("{ }");
+            fail("Expecting ParseException");
+        } catch (ParseException pe) {
+            // expected behavior
+        }
+    }
+
+    public void testParseManyComponents() throws ParseException {
+        ArrayRealVector parsed =
+            (ArrayRealVector) realVectorFormat.parseObject("{0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0}");
+        assertEquals(24, parsed.getDimension());
+    }
+
+    public void testConstructorSingleFormat() {
+        NumberFormat nf = NumberFormat.getInstance();
+        RealVectorFormat cf = new RealVectorFormat(nf);
+        assertNotNull(cf);
+        assertEquals(nf, cf.getFormat());
+    }
+
+    public void testFormatObject() {
+        try {
+            CompositeFormat cf = new RealVectorFormat();
+            Object object = new Object();
+            cf.format(object);
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // success
+        }
+    }
+
+    public void testForgottenPrefix() {
+        ParsePosition pos = new ParsePosition(0);
+        final String source = "1; 1; 1}";
+        assertNull("Should not parse <"+source+">",new RealVectorFormat().parse(source, pos));
+        assertEquals(0, pos.getErrorIndex());
+    }
+
+    public void testForgottenSeparator() {
+        ParsePosition pos = new ParsePosition(0);
+        final String source = "{1; 1 1}";
+        assertNull("Should not parse <"+source+">",new RealVectorFormat().parse(source, pos));
+        assertEquals(6, pos.getErrorIndex());
+    }
+
+    public void testForgottenSuffix() {
+        ParsePosition pos = new ParsePosition(0);
+        final String source = "{1; 1; 1 ";
+        assertNull("Should not parse <"+source+">",new RealVectorFormat().parse(source, pos));
+        assertEquals(8, pos.getErrorIndex());
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/linear/RealVectorFormatTest.java b/src/test/java/org/apache/commons/math/linear/RealVectorFormatTest.java
new file mode 100644
index 0000000..e7368b5
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/linear/RealVectorFormatTest.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.linear;
+
+import java.util.Locale;
+
+
+public class RealVectorFormatTest extends RealVectorFormatAbstractTest {
+
+    @Override
+    protected char getDecimalCharacter() {
+        return '.';
+    }
+
+    @Override
+    protected Locale getLocale() {
+        return Locale.US;
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/linear/SingularValueDecompositionImplTest.java b/src/test/java/org/apache/commons/math/linear/SingularValueDecompositionImplTest.java
new file mode 100644
index 0000000..1de28e2
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/linear/SingularValueDecompositionImplTest.java
@@ -0,0 +1,250 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.linear;
+
+import java.util.Random;
+
+import junit.framework.TestCase;
+
+public class SingularValueDecompositionImplTest extends TestCase {
+
+    private double[][] testSquare = {
+            { 24.0 / 25.0, 43.0 / 25.0 },
+            { 57.0 / 25.0, 24.0 / 25.0 }
+    };
+
+    private double[][] testNonSquare = {
+        {  -540.0 / 625.0,  963.0 / 625.0, -216.0 / 625.0 },
+        { -1730.0 / 625.0, -744.0 / 625.0, 1008.0 / 625.0 },
+        {  -720.0 / 625.0, 1284.0 / 625.0, -288.0 / 625.0 },
+        {  -360.0 / 625.0,  192.0 / 625.0, 1756.0 / 625.0 },
+    };
+
+    private static final double normTolerance = 10e-14;
+
+    public SingularValueDecompositionImplTest(String name) {
+        super(name);
+    }
+
+    public void testMoreRows() {
+        final double[] singularValues = { 123.456, 2.3, 1.001, 0.999 };
+        final int rows    = singularValues.length + 2;
+        final int columns = singularValues.length;
+        Random r = new Random(15338437322523l);
+        SingularValueDecomposition svd =
+            new SingularValueDecompositionImpl(createTestMatrix(r, rows, columns, singularValues));
+        double[] computedSV = svd.getSingularValues();
+        assertEquals(singularValues.length, computedSV.length);
+        for (int i = 0; i < singularValues.length; ++i) {
+            assertEquals(singularValues[i], computedSV[i], 1.0e-10);
+        }
+    }
+
+    public void testMoreColumns() {
+        final double[] singularValues = { 123.456, 2.3, 1.001, 0.999 };
+        final int rows    = singularValues.length;
+        final int columns = singularValues.length + 2;
+        Random r = new Random(732763225836210l);
+        SingularValueDecomposition svd =
+            new SingularValueDecompositionImpl(createTestMatrix(r, rows, columns, singularValues));
+        double[] computedSV = svd.getSingularValues();
+        assertEquals(singularValues.length, computedSV.length);
+        for (int i = 0; i < singularValues.length; ++i) {
+            assertEquals(singularValues[i], computedSV[i], 1.0e-10);
+        }
+    }
+
+    /** test dimensions */
+    public void testDimensions() {
+        RealMatrix matrix = MatrixUtils.createRealMatrix(testSquare);
+        final int m = matrix.getRowDimension();
+        final int n = matrix.getColumnDimension();
+        SingularValueDecomposition svd = new SingularValueDecompositionImpl(matrix);
+        assertEquals(m, svd.getU().getRowDimension());
+        assertEquals(m, svd.getU().getColumnDimension());
+        assertEquals(m, svd.getS().getColumnDimension());
+        assertEquals(n, svd.getS().getColumnDimension());
+        assertEquals(n, svd.getV().getRowDimension());
+        assertEquals(n, svd.getV().getColumnDimension());
+
+    }
+
+    /** Test based on a dimension 4 Hadamard matrix. */
+    public void testHadamard() {
+        RealMatrix matrix = new Array2DRowRealMatrix(new double[][] {
+                {15.0 / 2.0,  5.0 / 2.0,  9.0 / 2.0,  3.0 / 2.0 },
+                { 5.0 / 2.0, 15.0 / 2.0,  3.0 / 2.0,  9.0 / 2.0 },
+                { 9.0 / 2.0,  3.0 / 2.0, 15.0 / 2.0,  5.0 / 2.0 },
+                { 3.0 / 2.0,  9.0 / 2.0,  5.0 / 2.0, 15.0 / 2.0 }
+        }, false);
+        SingularValueDecomposition svd = new SingularValueDecompositionImpl(matrix);
+        assertEquals(16.0, svd.getSingularValues()[0], 1.0e-14);
+        assertEquals( 8.0, svd.getSingularValues()[1], 1.0e-14);
+        assertEquals( 4.0, svd.getSingularValues()[2], 1.0e-14);
+        assertEquals( 2.0, svd.getSingularValues()[3], 1.0e-14);
+
+        RealMatrix fullCovariance = new Array2DRowRealMatrix(new double[][] {
+                {  85.0 / 1024, -51.0 / 1024, -75.0 / 1024,  45.0 / 1024 },
+                { -51.0 / 1024,  85.0 / 1024,  45.0 / 1024, -75.0 / 1024 },
+                { -75.0 / 1024,  45.0 / 1024,  85.0 / 1024, -51.0 / 1024 },
+                {  45.0 / 1024, -75.0 / 1024, -51.0 / 1024,  85.0 / 1024 }
+        }, false);
+        assertEquals(0.0,
+                     fullCovariance.subtract(svd.getCovariance(0.0)).getNorm(),
+                     1.0e-14);
+
+        RealMatrix halfCovariance = new Array2DRowRealMatrix(new double[][] {
+                {   5.0 / 1024,  -3.0 / 1024,   5.0 / 1024,  -3.0 / 1024 },
+                {  -3.0 / 1024,   5.0 / 1024,  -3.0 / 1024,   5.0 / 1024 },
+                {   5.0 / 1024,  -3.0 / 1024,   5.0 / 1024,  -3.0 / 1024 },
+                {  -3.0 / 1024,   5.0 / 1024,  -3.0 / 1024,   5.0 / 1024 }
+        }, false);
+        assertEquals(0.0,
+                     halfCovariance.subtract(svd.getCovariance(6.0)).getNorm(),
+                     1.0e-14);
+
+    }
+
+    /** test A = USVt */
+    public void testAEqualUSVt() {
+        checkAEqualUSVt(MatrixUtils.createRealMatrix(testSquare));
+        checkAEqualUSVt(MatrixUtils.createRealMatrix(testNonSquare));
+        checkAEqualUSVt(MatrixUtils.createRealMatrix(testNonSquare).transpose());
+    }
+
+    public void checkAEqualUSVt(final RealMatrix matrix) {
+        SingularValueDecomposition svd = new SingularValueDecompositionImpl(matrix);
+        RealMatrix u = svd.getU();
+        RealMatrix s = svd.getS();
+        RealMatrix v = svd.getV();
+        double norm = u.multiply(s).multiply(v.transpose()).subtract(matrix).getNorm();
+        assertEquals(0, norm, normTolerance);
+
+    }
+
+    /** test that U is orthogonal */
+    public void testUOrthogonal() {
+        checkOrthogonal(new SingularValueDecompositionImpl(MatrixUtils.createRealMatrix(testSquare)).getU());
+        checkOrthogonal(new SingularValueDecompositionImpl(MatrixUtils.createRealMatrix(testNonSquare)).getU());
+        checkOrthogonal(new SingularValueDecompositionImpl(MatrixUtils.createRealMatrix(testNonSquare).transpose()).getU());
+    }
+
+    /** test that V is orthogonal */
+    public void testVOrthogonal() {
+        checkOrthogonal(new SingularValueDecompositionImpl(MatrixUtils.createRealMatrix(testSquare)).getV());
+        checkOrthogonal(new SingularValueDecompositionImpl(MatrixUtils.createRealMatrix(testNonSquare)).getV());
+        checkOrthogonal(new SingularValueDecompositionImpl(MatrixUtils.createRealMatrix(testNonSquare).transpose()).getV());
+    }
+
+    public void checkOrthogonal(final RealMatrix m) {
+        RealMatrix mTm = m.transpose().multiply(m);
+        RealMatrix id  = MatrixUtils.createRealIdentityMatrix(mTm.getRowDimension());
+        assertEquals(0, mTm.subtract(id).getNorm(), normTolerance);
+    }
+
+    /** test matrices values */
+    public void testMatricesValues1() {
+       SingularValueDecomposition svd =
+            new SingularValueDecompositionImpl(MatrixUtils.createRealMatrix(testSquare));
+        RealMatrix uRef = MatrixUtils.createRealMatrix(new double[][] {
+                { 3.0 / 5.0, -4.0 / 5.0 },
+                { 4.0 / 5.0,  3.0 / 5.0 }
+        });
+        RealMatrix sRef = MatrixUtils.createRealMatrix(new double[][] {
+                { 3.0, 0.0 },
+                { 0.0, 1.0 }
+        });
+        RealMatrix vRef = MatrixUtils.createRealMatrix(new double[][] {
+                { 4.0 / 5.0,  3.0 / 5.0 },
+                { 3.0 / 5.0, -4.0 / 5.0 }
+        });
+
+        // check values against known references
+        RealMatrix u = svd.getU();
+        assertEquals(0, u.subtract(uRef).getNorm(), normTolerance);
+        RealMatrix s = svd.getS();
+        assertEquals(0, s.subtract(sRef).getNorm(), normTolerance);
+        RealMatrix v = svd.getV();
+        assertEquals(0, v.subtract(vRef).getNorm(), normTolerance);
+
+        // check the same cached instance is returned the second time
+        assertTrue(u == svd.getU());
+        assertTrue(s == svd.getS());
+        assertTrue(v == svd.getV());
+
+    }
+
+    /** test matrices values */
+    // This test is useless since whereas the columns of U and V are linked
+    // together, the actual triplet (U,S,V) is not uniquely defined.
+    public void useless_testMatricesValues2() {
+
+        RealMatrix uRef = MatrixUtils.createRealMatrix(new double[][] {
+            {  0.0 / 5.0,  3.0 / 5.0,  0.0 / 5.0 },
+            { -4.0 / 5.0,  0.0 / 5.0, -3.0 / 5.0 },
+            {  0.0 / 5.0,  4.0 / 5.0,  0.0 / 5.0 },
+            { -3.0 / 5.0,  0.0 / 5.0,  4.0 / 5.0 }
+        });
+        RealMatrix sRef = MatrixUtils.createRealMatrix(new double[][] {
+            { 4.0, 0.0, 0.0 },
+            { 0.0, 3.0, 0.0 },
+            { 0.0, 0.0, 2.0 }
+        });
+        RealMatrix vRef = MatrixUtils.createRealMatrix(new double[][] {
+            {  80.0 / 125.0,  -60.0 / 125.0, 75.0 / 125.0 },
+            {  24.0 / 125.0,  107.0 / 125.0, 60.0 / 125.0 },
+            { -93.0 / 125.0,  -24.0 / 125.0, 80.0 / 125.0 }
+        });
+
+        // check values against known references
+        SingularValueDecomposition svd =
+            new SingularValueDecompositionImpl(MatrixUtils.createRealMatrix(testNonSquare));
+        RealMatrix u = svd.getU();
+        assertEquals(0, u.subtract(uRef).getNorm(), normTolerance);
+        RealMatrix s = svd.getS();
+        assertEquals(0, s.subtract(sRef).getNorm(), normTolerance);
+        RealMatrix v = svd.getV();
+        assertEquals(0, v.subtract(vRef).getNorm(), normTolerance);
+
+        // check the same cached instance is returned the second time
+        assertTrue(u == svd.getU());
+        assertTrue(s == svd.getS());
+        assertTrue(v == svd.getV());
+
+    }
+
+    /** test condition number */
+    public void testConditionNumber() {
+        SingularValueDecompositionImpl svd =
+            new SingularValueDecompositionImpl(MatrixUtils.createRealMatrix(testSquare));
+        // replace 1.0e-15 with 1.5e-15
+        assertEquals(3.0, svd.getConditionNumber(), 1.5e-15);
+    }
+
+    private RealMatrix createTestMatrix(final Random r, final int rows, final int columns,
+                                        final double[] singularValues) {
+        final RealMatrix u =
+            EigenDecompositionImplTest.createOrthogonalMatrix(r, rows);
+        final RealMatrix d =
+            EigenDecompositionImplTest.createDiagonalMatrix(singularValues, rows, columns);
+        final RealMatrix v =
+            EigenDecompositionImplTest.createOrthogonalMatrix(r, columns);
+        return u.multiply(d).multiply(v);
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/linear/SingularValueSolverTest.java b/src/test/java/org/apache/commons/math/linear/SingularValueSolverTest.java
new file mode 100644
index 0000000..b333bd0
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/linear/SingularValueSolverTest.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;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class SingularValueSolverTest {
+
+    private double[][] testSquare = {
+            { 24.0 / 25.0, 43.0 / 25.0 },
+            { 57.0 / 25.0, 24.0 / 25.0 }
+    };
+
+    private static final double normTolerance = 10e-14;
+
+    /** test solve dimension errors */
+    @Test
+    public void testSolveDimensionErrors() {
+        DecompositionSolver solver =
+            new SingularValueDecompositionImpl(MatrixUtils.createRealMatrix(testSquare)).getSolver();
+        RealMatrix b = MatrixUtils.createRealMatrix(new double[3][2]);
+        try {
+            solver.solve(b);
+            Assert.fail("an exception should have been thrown");
+        } catch (IllegalArgumentException iae) {
+            // expected behavior
+        }
+        try {
+            solver.solve(b.getColumn(0));
+            Assert.fail("an exception should have been thrown");
+        } catch (IllegalArgumentException iae) {
+            // expected behavior
+        }
+        try {
+            solver.solve(new ArrayRealVectorTest.RealVectorTestImpl(b.getColumn(0)));
+            Assert.fail("an exception should have been thrown");
+        } catch (IllegalArgumentException iae) {
+            // expected behavior
+        }
+    }
+
+    /** test least square solve */
+    @Test
+    public void testLeastSquareSolve() {
+        RealMatrix m =
+            MatrixUtils.createRealMatrix(new double[][] {
+                                   { 1.0, 0.0 },
+                                   { 0.0, 0.0 }
+                               });
+        DecompositionSolver solver = new SingularValueDecompositionImpl(m).getSolver();
+        RealMatrix b = MatrixUtils.createRealMatrix(new double[][] {
+            { 11, 12 }, { 21, 22 }
+        });
+        RealMatrix xMatrix = solver.solve(b);
+        Assert.assertEquals(11, xMatrix.getEntry(0, 0), 1.0e-15);
+        Assert.assertEquals(12, xMatrix.getEntry(0, 1), 1.0e-15);
+        Assert.assertEquals(0, xMatrix.getEntry(1, 0), 1.0e-15);
+        Assert.assertEquals(0, xMatrix.getEntry(1, 1), 1.0e-15);
+        double[] xCol = solver.solve(b.getColumn(0));
+        Assert.assertEquals(11, xCol[0], 1.0e-15);
+        Assert.assertEquals(0, xCol[1], 1.0e-15);
+        RealVector xColVec = solver.solve(b.getColumnVector(0));
+        Assert.assertEquals(11, xColVec.getEntry(0), 1.0e-15);
+        Assert.assertEquals(0, xColVec.getEntry(1), 1.0e-15);
+        RealVector xColOtherVec = solver.solve(new ArrayRealVectorTest.RealVectorTestImpl(b.getColumn(0)));
+        Assert.assertEquals(11, xColOtherVec.getEntry(0), 1.0e-15);
+        Assert.assertEquals(0, xColOtherVec.getEntry(1), 1.0e-15);
+    }
+
+    /** test solve */
+    @Test
+    public void testSolve() {
+        DecompositionSolver solver =
+            new SingularValueDecompositionImpl(MatrixUtils.createRealMatrix(testSquare)).getSolver();
+        RealMatrix b = MatrixUtils.createRealMatrix(new double[][] {
+                { 1, 2, 3 }, { 0, -5, 1 }
+        });
+        RealMatrix xRef = MatrixUtils.createRealMatrix(new double[][] {
+                { -8.0 / 25.0, -263.0 / 75.0, -29.0 / 75.0 },
+                { 19.0 / 25.0,   78.0 / 25.0,  49.0 / 25.0 }
+        });
+
+        // using RealMatrix
+        Assert.assertEquals(0, solver.solve(b).subtract(xRef).getNorm(), normTolerance);
+
+        // using double[]
+        for (int i = 0; i < b.getColumnDimension(); ++i) {
+            Assert.assertEquals(0,
+                         new ArrayRealVector(solver.solve(b.getColumn(i))).subtract(xRef.getColumnVector(i)).getNorm(),
+                         1.0e-13);
+        }
+
+        // using Array2DRowRealMatrix
+        for (int i = 0; i < b.getColumnDimension(); ++i) {
+            Assert.assertEquals(0,
+                         solver.solve(b.getColumnVector(i)).subtract(xRef.getColumnVector(i)).getNorm(),
+                         1.0e-13);
+        }
+
+        // using RealMatrix with an alternate implementation
+        for (int i = 0; i < b.getColumnDimension(); ++i) {
+            ArrayRealVectorTest.RealVectorTestImpl v =
+                new ArrayRealVectorTest.RealVectorTestImpl(b.getColumn(i));
+            Assert.assertEquals(0,
+                         solver.solve(v).subtract(xRef.getColumnVector(i)).getNorm(),
+                         1.0e-13);
+        }
+
+    }
+
+    /** test condition number */
+    @Test
+    public void testConditionNumber() {
+        SingularValueDecompositionImpl svd =
+            new SingularValueDecompositionImpl(MatrixUtils.createRealMatrix(testSquare));
+        // replace 1.0e-15 with 1.5e-15
+        Assert.assertEquals(3.0, svd.getConditionNumber(), 1.5e-15);
+    }
+
+    @Test
+    public void testMath320B() {
+        RealMatrix rm = new Array2DRowRealMatrix(new double[][] {
+            { 1.0, 2.0 }, { 1.0, 2.0 }
+        });
+        SingularValueDecomposition svd =
+            new SingularValueDecompositionImpl(rm);
+        RealMatrix recomposed = svd.getU().multiply(svd.getS()).multiply(svd.getVT());
+        Assert.assertEquals(0.0, recomposed.subtract(rm).getNorm(), 2.0e-15);
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/linear/SparseFieldMatrixTest.java b/src/test/java/org/apache/commons/math/linear/SparseFieldMatrixTest.java
new file mode 100644
index 0000000..90a6d4a
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/linear/SparseFieldMatrixTest.java
@@ -0,0 +1,684 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 junit.framework.TestCase;
+
+import org.apache.commons.math.Field;
+import org.apache.commons.math.fraction.Fraction;
+import org.apache.commons.math.fraction.FractionConversionException;
+import org.apache.commons.math.fraction.FractionField;
+
+/**
+ * Test cases for the {@link SparseFieldMatrix} class.
+ *
+ * @version $Revision: 902201 $ $Date: 2010-01-22 19:18:16 +0100 (ven. 22 janv. 2010) $
+ */
+public class SparseFieldMatrixTest extends TestCase {
+    // 3 x 3 identity matrix
+    protected Fraction[][] id = { {new Fraction(1), new Fraction(0), new Fraction(0) }, { new Fraction(0), new Fraction(1), new Fraction(0) }, { new Fraction(0), new Fraction(0), new Fraction(1) } };
+    // Test data for group operations
+    protected Fraction[][] testData = { { new Fraction(1), new Fraction(2), new Fraction(3) }, { new Fraction(2), new Fraction(5), new Fraction(3) },
+            { new Fraction(1), new Fraction(0), new Fraction(8) } };
+    protected Fraction[][] testDataLU = null;
+    protected Fraction[][] testDataPlus2 = { { new Fraction(3), new Fraction(4), new Fraction(5) }, { new Fraction(4), new Fraction(7), new Fraction(5) },
+            { new Fraction(3), new Fraction(2), new Fraction(10) } };
+    protected Fraction[][] testDataMinus = { { new Fraction(-1), new Fraction(-2), new Fraction(-3) },
+            { new Fraction(-2), new Fraction(-5), new Fraction(-3) }, { new Fraction(-1), new Fraction(0), new Fraction(-8) } };
+    protected Fraction[] testDataRow1 = { new Fraction(1), new Fraction(2), new Fraction(3) };
+    protected Fraction[] testDataCol3 = { new Fraction(3), new Fraction(3), new Fraction(8) };
+    protected Fraction[][] testDataInv = { { new Fraction(-40), new Fraction(16), new Fraction(9) }, { new Fraction(13), new Fraction(-5), new Fraction(-3) },
+            { new Fraction(5), new Fraction(-2), new Fraction(-1) } };
+    protected Fraction[] preMultTest = { new Fraction(8), new Fraction(12), new Fraction(33) };
+    protected Fraction[][] testData2 = { { new Fraction(1), new Fraction(2), new Fraction(3) }, { new Fraction(2), new Fraction(5), new Fraction(3) } };
+    protected Fraction[][] testData2T = { { new Fraction(1), new Fraction(2) }, { new Fraction(2), new Fraction(5) }, { new Fraction(3), new Fraction(3) } };
+    protected Fraction[][] testDataPlusInv = { { new Fraction(-39), new Fraction(18), new Fraction(12) },
+            { new Fraction(15), new Fraction(0), new Fraction(0) }, { new Fraction(6), new Fraction(-2), new Fraction(7) } };
+
+    // lu decomposition tests
+    protected Fraction[][] luData = { { new Fraction(2), new Fraction(3), new Fraction(3) }, { new Fraction(0), new Fraction(5), new Fraction(7) }, { new Fraction(6), new Fraction(9), new Fraction(8) } };
+    protected Fraction[][] luDataLUDecomposition = null;
+
+    // singular matrices
+    protected Fraction[][] singular = { { new Fraction(2), new Fraction(3) }, { new Fraction(2), new Fraction(3) } };
+    protected Fraction[][] bigSingular = { { new Fraction(1), new Fraction(2), new Fraction(3), new Fraction(4) },
+            { new Fraction(2), new Fraction(5), new Fraction(3), new Fraction(4) }, { new Fraction(7), new Fraction(3), new Fraction(256), new Fraction(1930) }, { new Fraction(3), new Fraction(7), new Fraction(6), new Fraction(8) } }; // 4th
+
+    // row
+    // =
+    // 1st
+    // +
+    // 2nd
+    protected Fraction[][] detData = { { new Fraction(1), new Fraction(2), new Fraction(3) }, { new Fraction(4), new Fraction(5), new Fraction(6) },
+            { new Fraction(7), new Fraction(8), new Fraction(10) } };
+    protected Fraction[][] detData2 = { { new Fraction(1), new Fraction(3) }, { new Fraction(2), new Fraction(4) } };
+
+    // vectors
+    protected Fraction[] testVector = { new Fraction(1), new Fraction(2), new Fraction(3) };
+    protected Fraction[] testVector2 = { new Fraction(1), new Fraction(2), new Fraction(3), new Fraction(4) };
+
+    // submatrix accessor tests
+    protected Fraction[][] subTestData = null;
+
+    // array selections
+    protected Fraction[][] subRows02Cols13 = { {new Fraction(2), new Fraction(4) }, { new Fraction(4), new Fraction(8) } };
+    protected Fraction[][] subRows03Cols12 = { { new Fraction(2), new Fraction(3) }, { new Fraction(5), new Fraction(6) } };
+    protected Fraction[][] subRows03Cols123 = { { new Fraction(2), new Fraction(3), new Fraction(4) }, { new Fraction(5), new Fraction(6), new Fraction(7) } };
+
+    // effective permutations
+    protected Fraction[][] subRows20Cols123 = { { new Fraction(4), new Fraction(6), new Fraction(8) }, { new Fraction(2), new Fraction(3), new Fraction(4) } };
+    protected Fraction[][] subRows31Cols31 = null;
+
+    // contiguous ranges
+    protected Fraction[][] subRows01Cols23 = null;
+    protected Fraction[][] subRows23Cols00 = { { new Fraction(2) }, { new Fraction(4) } };
+    protected Fraction[][] subRows00Cols33 = { { new Fraction(4) } };
+
+    // row matrices
+    protected Fraction[][] subRow0 = { { new Fraction(1), new Fraction(2), new Fraction(3), new Fraction(4) } };
+    protected Fraction[][] subRow3 = { { new Fraction(4), new Fraction(5), new Fraction(6), new Fraction(7) } };
+
+    // column matrices
+    protected Fraction[][] subColumn1 = null;
+    protected Fraction[][] subColumn3 = null;
+
+    // tolerances
+    protected double entryTolerance = 10E-16;
+    protected double normTolerance = 10E-14;
+    protected Field<Fraction> field = FractionField.getInstance();
+
+    public SparseFieldMatrixTest(String name) {
+        super(name);
+        setupFractionArrays();
+    }
+
+    private void setupFractionArrays() {
+        try {
+            testDataLU = new Fraction[][]{ { new Fraction(2), new Fraction(5), new Fraction(3) }, { new Fraction(.5d), new Fraction(-2.5d), new Fraction(6.5d) },
+                    { new Fraction(0.5d), new Fraction(0.2d), new Fraction(.2d) } };
+            luDataLUDecomposition = new Fraction[][]{ { new Fraction(6), new Fraction(9), new Fraction(8) },
+                { new Fraction(0), new Fraction(5), new Fraction(7) }, { new Fraction(0.33333333333333), new Fraction(0), new Fraction(0.33333333333333) } };
+            subTestData = new Fraction [][]{ { new Fraction(1), new Fraction(2), new Fraction(3), new Fraction(4) },
+                    { new Fraction(1.5), new Fraction(2.5), new Fraction(3.5), new Fraction(4.5) }, { new Fraction(2), new Fraction(4), new Fraction(6), new Fraction(8) }, { new Fraction(4), new Fraction(5), new Fraction(6), new Fraction(7) } };
+            subRows31Cols31 = new Fraction[][]{ { new Fraction(7), new Fraction(5) }, { new Fraction(4.5), new Fraction(2.5) } };
+            subRows01Cols23 = new Fraction[][]{ { new Fraction(3), new Fraction(4) }, { new Fraction(3.5), new Fraction(4.5) } };
+            subColumn1 = new Fraction [][]{ { new Fraction(2) }, { new Fraction(2.5) }, { new Fraction(4) }, { new Fraction(5) } };
+            subColumn3 = new Fraction[][]{ { new Fraction(4) }, { new Fraction(4.5) }, { new Fraction(8) }, { new Fraction(7) } };
+        } catch (FractionConversionException e) {
+            // ignore, can't happen
+        }
+
+
+    }
+
+    /** test dimensions */
+    public void testDimensions() {
+        SparseFieldMatrix<Fraction> m = createSparseMatrix(testData);
+        SparseFieldMatrix<Fraction> m2 = createSparseMatrix(testData2);
+        assertEquals("testData row dimension", 3, m.getRowDimension());
+        assertEquals("testData column dimension", 3, m.getColumnDimension());
+        assertTrue("testData is square", m.isSquare());
+        assertEquals("testData2 row dimension", m2.getRowDimension(), 2);
+        assertEquals("testData2 column dimension", m2.getColumnDimension(), 3);
+        assertTrue("testData2 is not square", !m2.isSquare());
+    }
+
+    /** test copy functions */
+    public void testCopyFunctions() {
+        SparseFieldMatrix<Fraction> m1 = createSparseMatrix(testData);
+        FieldMatrix<Fraction> m2 = m1.copy();
+        assertEquals(m1.getClass(), m2.getClass());
+        assertEquals((m2), m1);
+        SparseFieldMatrix<Fraction> m3 = createSparseMatrix(testData);
+        FieldMatrix<Fraction> m4 = m3.copy();
+        assertEquals(m3.getClass(), m4.getClass());
+        assertEquals((m4), m3);
+    }
+
+    /** test add */
+    public void testAdd() {
+        SparseFieldMatrix<Fraction> m = createSparseMatrix(testData);
+        SparseFieldMatrix<Fraction> mInv = createSparseMatrix(testDataInv);
+        SparseFieldMatrix<Fraction> mDataPlusInv = createSparseMatrix(testDataPlusInv);
+        FieldMatrix<Fraction> mPlusMInv = m.add(mInv);
+        for (int row = 0; row < m.getRowDimension(); row++) {
+            for (int col = 0; col < m.getColumnDimension(); col++) {
+                assertEquals("sum entry entry",
+                    mDataPlusInv.getEntry(row, col).doubleValue(), mPlusMInv.getEntry(row, col).doubleValue(),
+                    entryTolerance);
+            }
+        }
+    }
+
+    /** test add failure */
+    public void testAddFail() {
+        SparseFieldMatrix<Fraction> m = createSparseMatrix(testData);
+        SparseFieldMatrix<Fraction> m2 = createSparseMatrix(testData2);
+        try {
+            m.add(m2);
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+
+    /** test m-n = m + -n */
+    public void testPlusMinus() {
+        SparseFieldMatrix<Fraction> m = createSparseMatrix(testData);
+        SparseFieldMatrix<Fraction> n = createSparseMatrix(testDataInv);
+        assertClose("m-n = m + -n", m.subtract(n),
+            n.scalarMultiply(new Fraction(-1)).add(m), entryTolerance);
+        try {
+            m.subtract(createSparseMatrix(testData2));
+            fail("Expecting illegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    /** test multiply */
+    public void testMultiply() {
+        SparseFieldMatrix<Fraction> m = createSparseMatrix(testData);
+        SparseFieldMatrix<Fraction> mInv = createSparseMatrix(testDataInv);
+        SparseFieldMatrix<Fraction> identity = createSparseMatrix(id);
+        SparseFieldMatrix<Fraction> m2 = createSparseMatrix(testData2);
+        assertClose("inverse multiply", m.multiply(mInv), identity,
+                entryTolerance);
+        assertClose("inverse multiply", m.multiply(new Array2DRowFieldMatrix<Fraction>(testDataInv)), identity,
+                    entryTolerance);
+        assertClose("inverse multiply", mInv.multiply(m), identity,
+                entryTolerance);
+        assertClose("identity multiply", m.multiply(identity), m,
+                entryTolerance);
+        assertClose("identity multiply", identity.multiply(mInv), mInv,
+                entryTolerance);
+        assertClose("identity multiply", m2.multiply(identity), m2,
+                entryTolerance);
+        try {
+            m.multiply(createSparseMatrix(bigSingular));
+            fail("Expecting illegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    // Additional Test for Array2DRowRealMatrixTest.testMultiply
+
+    private Fraction[][] d3 = new Fraction[][] { { new Fraction(1), new Fraction(2), new Fraction(3), new Fraction(4) }, { new Fraction(5), new Fraction(6), new Fraction(7), new Fraction(8) } };
+    private Fraction[][] d4 = new Fraction[][] { { new Fraction(1) }, { new Fraction(2) }, { new Fraction(3) }, { new Fraction(4) } };
+    private Fraction[][] d5 = new Fraction[][] { { new Fraction(30) }, { new Fraction(70) } };
+
+    public void testMultiply2() {
+        FieldMatrix<Fraction> m3 = createSparseMatrix(d3);
+        FieldMatrix<Fraction> m4 = createSparseMatrix(d4);
+        FieldMatrix<Fraction> m5 = createSparseMatrix(d5);
+        assertClose("m3*m4=m5", m3.multiply(m4), m5, entryTolerance);
+    }
+
+    /** test trace */
+    public void testTrace() {
+        FieldMatrix<Fraction> m = createSparseMatrix(id);
+        assertEquals("identity trace", 3d, m.getTrace().doubleValue(), entryTolerance);
+        m = createSparseMatrix(testData2);
+        try {
+            m.getTrace();
+            fail("Expecting NonSquareMatrixException");
+        } catch (NonSquareMatrixException ex) {
+            // ignored
+        }
+    }
+
+    /** test sclarAdd */
+    public void testScalarAdd() {
+        FieldMatrix<Fraction> m = createSparseMatrix(testData);
+        assertClose("scalar add", createSparseMatrix(testDataPlus2),
+            m.scalarAdd(new Fraction(2)), entryTolerance);
+    }
+
+    /** test operate */
+    public void testOperate() {
+        FieldMatrix<Fraction> m = createSparseMatrix(id);
+        assertClose("identity operate", testVector, m.operate(testVector),
+                entryTolerance);
+        assertClose("identity operate", testVector, m.operate(
+                new ArrayFieldVector<Fraction>(testVector)).getData(), entryTolerance);
+        m = createSparseMatrix(bigSingular);
+        try {
+            m.operate(testVector);
+            fail("Expecting illegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    /** test issue MATH-209 */
+    public void testMath209() {
+        FieldMatrix<Fraction> a = createSparseMatrix(new Fraction[][] {
+                { new Fraction(1), new Fraction(2) }, { new Fraction(3), new Fraction(4) }, { new Fraction(5), new Fraction(6) } });
+        Fraction[] b = a.operate(new Fraction[] { new Fraction(1), new Fraction(1) });
+        assertEquals(a.getRowDimension(), b.length);
+        assertEquals(3.0, b[0].doubleValue(), 1.0e-12);
+        assertEquals(7.0, b[1].doubleValue(), 1.0e-12);
+        assertEquals(11.0, b[2].doubleValue(), 1.0e-12);
+    }
+
+    /** test transpose */
+    public void testTranspose() {
+
+        FieldMatrix<Fraction> m = createSparseMatrix(testData);
+        FieldMatrix<Fraction> mIT = new FieldLUDecompositionImpl<Fraction>(m).getSolver().getInverse().transpose();
+        FieldMatrix<Fraction> mTI = new FieldLUDecompositionImpl<Fraction>(m.transpose()).getSolver().getInverse();
+        assertClose("inverse-transpose", mIT, mTI, normTolerance);
+        m = createSparseMatrix(testData2);
+        FieldMatrix<Fraction> mt = createSparseMatrix(testData2T);
+        assertClose("transpose",mt,m.transpose(),normTolerance);
+    }
+
+    /** test preMultiply by vector */
+    public void testPremultiplyVector() {
+        FieldMatrix<Fraction> m = createSparseMatrix(testData);
+        assertClose("premultiply", m.preMultiply(testVector), preMultTest,
+            normTolerance);
+        assertClose("premultiply", m.preMultiply(
+            new ArrayFieldVector<Fraction>(testVector).getData()), preMultTest, normTolerance);
+        m = createSparseMatrix(bigSingular);
+        try {
+            m.preMultiply(testVector);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    public void testPremultiply() {
+        FieldMatrix<Fraction> m3 = createSparseMatrix(d3);
+        FieldMatrix<Fraction> m4 = createSparseMatrix(d4);
+        FieldMatrix<Fraction> m5 = createSparseMatrix(d5);
+        assertClose("m3*m4=m5", m4.preMultiply(m3), m5, entryTolerance);
+
+        SparseFieldMatrix<Fraction> m = createSparseMatrix(testData);
+        SparseFieldMatrix<Fraction> mInv = createSparseMatrix(testDataInv);
+        SparseFieldMatrix<Fraction> identity = createSparseMatrix(id);
+        assertClose("inverse multiply", m.preMultiply(mInv), identity,
+                entryTolerance);
+        assertClose("inverse multiply", mInv.preMultiply(m), identity,
+                entryTolerance);
+        assertClose("identity multiply", m.preMultiply(identity), m,
+                entryTolerance);
+        assertClose("identity multiply", identity.preMultiply(mInv), mInv,
+                entryTolerance);
+        try {
+            m.preMultiply(createSparseMatrix(bigSingular));
+            fail("Expecting illegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    public void testGetVectors() {
+        FieldMatrix<Fraction> m = createSparseMatrix(testData);
+        assertClose("get row", m.getRow(0), testDataRow1, entryTolerance);
+        assertClose("get col", m.getColumn(2), testDataCol3, entryTolerance);
+        try {
+            m.getRow(10);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // ignored
+        }
+        try {
+            m.getColumn(-1);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // ignored
+        }
+    }
+
+    public void testGetEntry() {
+        FieldMatrix<Fraction> m = createSparseMatrix(testData);
+        assertEquals("get entry", m.getEntry(0, 1).doubleValue(), 2d, entryTolerance);
+        try {
+            m.getEntry(10, 4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    /** test examples in user guide */
+    public void testExamples() {
+        // Create a real matrix with two rows and three columns
+        Fraction[][] matrixData = { { new Fraction(1), new Fraction(2), new Fraction(3) }, { new Fraction(2), new Fraction(5), new Fraction(3) } };
+        FieldMatrix<Fraction> m = createSparseMatrix(matrixData);
+        // One more with three rows, two columns
+        Fraction[][] matrixData2 = { { new Fraction(1), new Fraction(2) }, { new Fraction(2), new Fraction(5) }, { new Fraction(1), new Fraction(7) } };
+        FieldMatrix<Fraction> n = createSparseMatrix(matrixData2);
+        // Now multiply m by n
+        FieldMatrix<Fraction> p = m.multiply(n);
+        assertEquals(2, p.getRowDimension());
+        assertEquals(2, p.getColumnDimension());
+        // Invert p
+        FieldMatrix<Fraction> pInverse = new FieldLUDecompositionImpl<Fraction>(p).getSolver().getInverse();
+        assertEquals(2, pInverse.getRowDimension());
+        assertEquals(2, pInverse.getColumnDimension());
+
+        // Solve example
+        Fraction[][] coefficientsData = { { new Fraction(2), new Fraction(3), new Fraction(-2) }, { new Fraction(-1), new Fraction(7), new Fraction(6) },
+                { new Fraction(4), new Fraction(-3), new Fraction(-5) } };
+        FieldMatrix<Fraction> coefficients = createSparseMatrix(coefficientsData);
+        Fraction[] constants = { new Fraction(1), new Fraction(-2), new Fraction(1) };
+        Fraction[] solution = new FieldLUDecompositionImpl<Fraction>(coefficients).getSolver().solve(constants);
+        assertEquals((new Fraction(2).multiply((solution[0])).add(new Fraction(3).multiply(solution[1])).subtract(new Fraction(2).multiply(solution[2]))).doubleValue(),
+                constants[0].doubleValue(), 1E-12);
+        assertEquals(((new Fraction(-1).multiply(solution[0])).add(new Fraction(7).multiply(solution[1])).add(new Fraction(6).multiply(solution[2]))).doubleValue(),
+                constants[1].doubleValue(), 1E-12);
+        assertEquals(((new Fraction(4).multiply(solution[0])).subtract(new Fraction(3).multiply( solution[1])).subtract(new Fraction(5).multiply(solution[2]))).doubleValue(),
+                constants[2].doubleValue(), 1E-12);
+
+    }
+
+    // test submatrix accessors
+    public void testSubMatrix() {
+        FieldMatrix<Fraction> m = createSparseMatrix(subTestData);
+        FieldMatrix<Fraction> mRows23Cols00 = createSparseMatrix(subRows23Cols00);
+        FieldMatrix<Fraction> mRows00Cols33 = createSparseMatrix(subRows00Cols33);
+        FieldMatrix<Fraction> mRows01Cols23 = createSparseMatrix(subRows01Cols23);
+        FieldMatrix<Fraction> mRows02Cols13 = createSparseMatrix(subRows02Cols13);
+        FieldMatrix<Fraction> mRows03Cols12 = createSparseMatrix(subRows03Cols12);
+        FieldMatrix<Fraction> mRows03Cols123 = createSparseMatrix(subRows03Cols123);
+        FieldMatrix<Fraction> mRows20Cols123 = createSparseMatrix(subRows20Cols123);
+        FieldMatrix<Fraction> mRows31Cols31 = createSparseMatrix(subRows31Cols31);
+        assertEquals("Rows23Cols00", mRows23Cols00, m.getSubMatrix(2, 3, 0, 0));
+        assertEquals("Rows00Cols33", mRows00Cols33, m.getSubMatrix(0, 0, 3, 3));
+        assertEquals("Rows01Cols23", mRows01Cols23, m.getSubMatrix(0, 1, 2, 3));
+        assertEquals("Rows02Cols13", mRows02Cols13,
+            m.getSubMatrix(new int[] { 0, 2 }, new int[] { 1, 3 }));
+        assertEquals("Rows03Cols12", mRows03Cols12,
+            m.getSubMatrix(new int[] { 0, 3 }, new int[] { 1, 2 }));
+        assertEquals("Rows03Cols123", mRows03Cols123,
+            m.getSubMatrix(new int[] { 0, 3 }, new int[] { 1, 2, 3 }));
+        assertEquals("Rows20Cols123", mRows20Cols123,
+            m.getSubMatrix(new int[] { 2, 0 }, new int[] { 1, 2, 3 }));
+        assertEquals("Rows31Cols31", mRows31Cols31,
+            m.getSubMatrix(new int[] { 3, 1 }, new int[] { 3, 1 }));
+        assertEquals("Rows31Cols31", mRows31Cols31,
+            m.getSubMatrix(new int[] { 3, 1 }, new int[] { 3, 1 }));
+
+        try {
+            m.getSubMatrix(1, 0, 2, 4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getSubMatrix(-1, 1, 2, 2);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getSubMatrix(1, 0, 2, 2);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getSubMatrix(1, 0, 2, 4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getSubMatrix(new int[] {}, new int[] { 0 });
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getSubMatrix(new int[] { 0 }, new int[] { 4 });
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testGetRowMatrix() {
+        FieldMatrix<Fraction> m = createSparseMatrix(subTestData);
+        FieldMatrix<Fraction> mRow0 = createSparseMatrix(subRow0);
+        FieldMatrix<Fraction> mRow3 = createSparseMatrix(subRow3);
+        assertEquals("Row0", mRow0, m.getRowMatrix(0));
+        assertEquals("Row3", mRow3, m.getRowMatrix(3));
+        try {
+            m.getRowMatrix(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getRowMatrix(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testGetColumnMatrix() {
+        FieldMatrix<Fraction> m = createSparseMatrix(subTestData);
+        FieldMatrix<Fraction> mColumn1 = createSparseMatrix(subColumn1);
+        FieldMatrix<Fraction> mColumn3 = createSparseMatrix(subColumn3);
+        assertEquals("Column1", mColumn1, m.getColumnMatrix(1));
+        assertEquals("Column3", mColumn3, m.getColumnMatrix(3));
+        try {
+            m.getColumnMatrix(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getColumnMatrix(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testGetRowVector() {
+        FieldMatrix<Fraction> m = createSparseMatrix(subTestData);
+        FieldVector<Fraction> mRow0 = new ArrayFieldVector<Fraction>(subRow0[0]);
+        FieldVector<Fraction> mRow3 = new ArrayFieldVector<Fraction>(subRow3[0]);
+        assertEquals("Row0", mRow0, m.getRowVector(0));
+        assertEquals("Row3", mRow3, m.getRowVector(3));
+        try {
+            m.getRowVector(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getRowVector(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testGetColumnVector() {
+        FieldMatrix<Fraction> m = createSparseMatrix(subTestData);
+        FieldVector<Fraction> mColumn1 = columnToVector(subColumn1);
+        FieldVector<Fraction> mColumn3 = columnToVector(subColumn3);
+        assertEquals("Column1", mColumn1, m.getColumnVector(1));
+        assertEquals("Column3", mColumn3, m.getColumnVector(3));
+        try {
+            m.getColumnVector(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getColumnVector(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    private FieldVector<Fraction> columnToVector(Fraction[][] column) {
+        Fraction[] data = new Fraction[column.length];
+        for (int i = 0; i < data.length; ++i) {
+            data[i] = column[i][0];
+        }
+        return new ArrayFieldVector<Fraction>(data, false);
+    }
+
+    public void testEqualsAndHashCode() {
+        SparseFieldMatrix<Fraction> m = createSparseMatrix(testData);
+        SparseFieldMatrix<Fraction> m1 = (SparseFieldMatrix<Fraction>) m.copy();
+        SparseFieldMatrix<Fraction> mt = (SparseFieldMatrix<Fraction>) m.transpose();
+        assertTrue(m.hashCode() != mt.hashCode());
+        assertEquals(m.hashCode(), m1.hashCode());
+        assertEquals(m, m);
+        assertEquals(m, m1);
+        assertFalse(m.equals(null));
+        assertFalse(m.equals(mt));
+        assertFalse(m.equals(createSparseMatrix(bigSingular)));
+    }
+
+    /* Disable for now
+    public void testToString() {
+        SparseFieldMatrix<Fraction> m = createSparseMatrix(testData);
+        assertEquals("SparseFieldMatrix<Fraction>{{1.0,2.0,3.0},{2.0,5.0,3.0},{1.0,0.0,8.0}}",
+            m.toString());
+        m = new SparseFieldMatrix<Fraction>(field, 1, 1);
+        assertEquals("SparseFieldMatrix<Fraction>{{0.0}}", m.toString());
+    }
+    */
+
+    public void testSetSubMatrix() throws Exception {
+        SparseFieldMatrix<Fraction> m = createSparseMatrix(testData);
+        m.setSubMatrix(detData2, 1, 1);
+        FieldMatrix<Fraction> expected = createSparseMatrix(new Fraction[][] {
+                { new Fraction(1), new Fraction(2), new Fraction(3) }, { new Fraction(2), new Fraction(1), new Fraction(3) }, { new Fraction(1), new Fraction(2), new Fraction(4) } });
+        assertEquals(expected, m);
+
+        m.setSubMatrix(detData2, 0, 0);
+        expected = createSparseMatrix(new Fraction[][] {
+                { new Fraction(1), new Fraction(3), new Fraction(3) }, { new Fraction(2), new Fraction(4), new Fraction(3) }, { new Fraction(1), new Fraction(2), new Fraction(4) } });
+        assertEquals(expected, m);
+
+        m.setSubMatrix(testDataPlus2, 0, 0);
+        expected = createSparseMatrix(new Fraction[][] {
+                { new Fraction(3), new Fraction(4), new Fraction(5) }, { new Fraction(4), new Fraction(7), new Fraction(5) }, { new Fraction(3), new Fraction(2), new Fraction(10) } });
+        assertEquals(expected, m);
+
+        // javadoc example
+        SparseFieldMatrix<Fraction> matrix =
+            createSparseMatrix(new Fraction[][] {
+        { new Fraction(1), new Fraction(2), new Fraction(3), new Fraction(4) }, { new Fraction(5), new Fraction(6), new Fraction(7), new Fraction(8) }, { new Fraction(9), new Fraction(0), new Fraction(1), new Fraction(2) } });
+        matrix.setSubMatrix(new Fraction[][] { { new Fraction(3), new Fraction(4) }, { new Fraction(5), new Fraction(6) } }, 1, 1);
+        expected = createSparseMatrix(new Fraction[][] {
+                { new Fraction(1), new Fraction(2), new Fraction(3), new Fraction(4) }, { new Fraction(5), new Fraction(3), new Fraction(4), new Fraction(8) }, { new Fraction(9), new Fraction(5), new Fraction(6), new Fraction(2) } });
+        assertEquals(expected, matrix);
+
+        // dimension overflow
+        try {
+            m.setSubMatrix(testData, 1, 1);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException e) {
+            // expected
+        }
+        // dimension underflow
+        try {
+            m.setSubMatrix(testData, -1, 1);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException e) {
+            // expected
+        }
+        try {
+            m.setSubMatrix(testData, 1, -1);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException e) {
+            // expected
+        }
+
+        // null
+        try {
+            m.setSubMatrix(null, 1, 1);
+            fail("expecting NullPointerException");
+        } catch (NullPointerException e) {
+            // expected
+        }
+        try {
+            new SparseFieldMatrix<Fraction>(field, 0, 0);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        // ragged
+        try {
+            m.setSubMatrix(new Fraction[][] { { new Fraction(1) }, { new Fraction(2), new Fraction(3) } }, 0, 0);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        // empty
+        try {
+            m.setSubMatrix(new Fraction[][] { {} }, 0, 0);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+    }
+
+    // --------------- -----------------Protected methods
+
+    /** verifies that two matrices are close (1-norm) */
+    protected void assertClose(String msg, FieldMatrix<Fraction> m, FieldMatrix<Fraction> n,
+            double tolerance) {
+        for(int i=0; i < m.getRowDimension(); i++){
+            for(int j=0; j < m.getColumnDimension(); j++){
+                assertEquals(msg, m.getEntry(i,j).doubleValue(), n.getEntry(i,j).doubleValue(), tolerance);
+            }
+
+        }
+    }
+
+    /** verifies that two vectors are close (sup norm) */
+    protected void assertClose(String msg, Fraction[] m, Fraction[] n,
+            double tolerance) {
+        if (m.length != n.length) {
+            fail("vectors not same length");
+        }
+        for (int i = 0; i < m.length; i++) {
+            assertEquals(msg + " " + i + " elements differ", m[i].doubleValue(), n[i].doubleValue(),
+                    tolerance);
+        }
+    }
+
+    private SparseFieldMatrix<Fraction> createSparseMatrix(Fraction[][] data) {
+        SparseFieldMatrix<Fraction> matrix = new SparseFieldMatrix<Fraction>(field, data.length, data[0].length);
+        for (int row = 0; row < data.length; row++) {
+            for (int col = 0; col < data[row].length; col++) {
+                matrix.setEntry(row, col, data[row][col]);
+            }
+        }
+        return matrix;
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/linear/SparseFieldVectorTest.java b/src/test/java/org/apache/commons/math/linear/SparseFieldVectorTest.java
new file mode 100644
index 0000000..9e63bf3
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/linear/SparseFieldVectorTest.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.linear;
+
+
+import org.apache.commons.math.fraction.Fraction;
+import org.apache.commons.math.fraction.FractionConversionException;
+import org.apache.commons.math.fraction.FractionField;
+
+
+import junit.framework.TestCase;
+
+/**
+ * Test cases for the {@link SparseFieldVector} class.
+ *
+ * @version $Revision: 1003907 $ $Date: 2010-10-03 00:23:34 +0200 (dim. 03 oct. 2010) $
+ */
+public class SparseFieldVectorTest extends TestCase {
+
+    //
+    protected Fraction[][] ma1 = {{new Fraction(1), new Fraction(2), new Fraction(3)}, {new Fraction(4), new Fraction(5), new Fraction(6)}, {new Fraction(7), new Fraction(8), new Fraction(9)}};
+    protected Fraction[] vec1 = {new Fraction(1), new Fraction(2), new Fraction(3)};
+    protected Fraction[] vec2 = {new Fraction(4), new Fraction(5), new Fraction(6)};
+    protected Fraction[] vec3 = {new Fraction(7), new Fraction(8), new Fraction(9)};
+    protected Fraction[] vec4 = {new Fraction(1), new Fraction(2), new Fraction(3), new Fraction(4), new Fraction(5), new Fraction(6), new Fraction(7), new Fraction(8), new Fraction(9)};
+    protected Fraction[] vec_null = {new Fraction(0), new Fraction(0), new Fraction(0)};
+    protected Fraction[] dvec1 = {new Fraction(1), new Fraction(2), new Fraction(3), new Fraction(4), new Fraction(5), new Fraction(6), new Fraction(7), new Fraction(8),new Fraction(9)};
+    protected Fraction[][] mat1 = {{new Fraction(1), new Fraction(2), new Fraction(3)}, {new Fraction(4), new Fraction(5), new Fraction(6)},{ new Fraction(7), new Fraction(8), new Fraction(9)}};
+
+    // tolerances
+    protected double entryTolerance = 10E-16;
+    protected double normTolerance = 10E-14;
+
+    protected FractionField field = FractionField.getInstance();
+
+    public void testMapFunctions() throws FractionConversionException {
+        SparseFieldVector<Fraction> v1 = new SparseFieldVector<Fraction>(field,vec1);
+
+        //octave =  v1 .+ 2.0
+        FieldVector<Fraction> v_mapAdd = v1.mapAdd(new Fraction(2));
+        Fraction[] result_mapAdd = {new Fraction(3), new Fraction(4), new Fraction(5)};
+        assertEquals("compare vectors" ,result_mapAdd,v_mapAdd.getData());
+
+        //octave =  v1 .+ 2.0
+        FieldVector<Fraction> v_mapAddToSelf = v1.copy();
+        v_mapAddToSelf.mapAddToSelf(new Fraction(2));
+        Fraction[] result_mapAddToSelf = {new Fraction(3), new Fraction(4), new Fraction(5)};
+        assertEquals("compare vectors" ,result_mapAddToSelf,v_mapAddToSelf.getData());
+
+        //octave =  v1 .- 2.0
+        FieldVector<Fraction> v_mapSubtract = v1.mapSubtract(new Fraction(2));
+        Fraction[] result_mapSubtract = {new Fraction(-1), new Fraction(0), new Fraction(1)};
+        assertEquals("compare vectors" ,result_mapSubtract,v_mapSubtract.getData());
+
+        //octave =  v1 .- 2.0
+        FieldVector<Fraction> v_mapSubtractToSelf = v1.copy();
+        v_mapSubtractToSelf.mapSubtractToSelf(new Fraction(2));
+        Fraction[] result_mapSubtractToSelf = {new Fraction(-1), new Fraction(0), new Fraction(1)};
+        assertEquals("compare vectors" ,result_mapSubtractToSelf,v_mapSubtractToSelf.getData());
+
+        //octave =  v1 .* 2.0
+        FieldVector<Fraction> v_mapMultiply = v1.mapMultiply(new Fraction(2));
+        Fraction[] result_mapMultiply = {new Fraction(2), new Fraction(4), new Fraction(6)};
+        assertEquals("compare vectors" ,result_mapMultiply,v_mapMultiply.getData());
+
+        //octave =  v1 .* 2.0
+        FieldVector<Fraction> v_mapMultiplyToSelf = v1.copy();
+        v_mapMultiplyToSelf.mapMultiplyToSelf(new Fraction(2));
+        Fraction[] result_mapMultiplyToSelf = {new Fraction(2), new Fraction(4), new Fraction(6)};
+        assertEquals("compare vectors" ,result_mapMultiplyToSelf,v_mapMultiplyToSelf.getData());
+
+        //octave =  v1 ./ 2.0
+        FieldVector<Fraction> v_mapDivide = v1.mapDivide(new Fraction(2));
+        Fraction[] result_mapDivide = {new Fraction(.5d), new Fraction(1), new Fraction(1.5d)};
+        assertEquals("compare vectors" ,result_mapDivide,v_mapDivide.getData());
+
+        //octave =  v1 ./ 2.0
+        FieldVector<Fraction> v_mapDivideToSelf = v1.copy();
+        v_mapDivideToSelf.mapDivideToSelf(new Fraction(2));
+        Fraction[] result_mapDivideToSelf = {new Fraction(.5d), new Fraction(1), new Fraction(1.5d)};
+        assertEquals("compare vectors" ,result_mapDivideToSelf,v_mapDivideToSelf.getData());
+
+        //octave =  v1 .^-1
+        FieldVector<Fraction> v_mapInv = v1.mapInv();
+        Fraction[] result_mapInv = {new Fraction(1),new Fraction(0.5d),new Fraction(3.333333333333333e-01d)};
+        assertEquals("compare vectors" ,result_mapInv,v_mapInv.getData());
+
+        //octave =  v1 .^-1
+        FieldVector<Fraction> v_mapInvToSelf = v1.copy();
+        v_mapInvToSelf.mapInvToSelf();
+        Fraction[] result_mapInvToSelf = {new Fraction(1),new Fraction(0.5d),new Fraction(3.333333333333333e-01d)};
+        assertEquals("compare vectors" ,result_mapInvToSelf,v_mapInvToSelf.getData());
+
+
+    }
+
+    public void testBasicFunctions() throws FractionConversionException {
+        SparseFieldVector<Fraction> v1 = new SparseFieldVector<Fraction>(field,vec1);
+        SparseFieldVector<Fraction> v2 = new SparseFieldVector<Fraction>(field,vec2);
+
+        SparseFieldVector<Fraction> v2_t = new SparseFieldVector<Fraction>(field,vec2);
+
+        //octave =  v1 + v2
+        FieldVector<Fraction> v_add = v1.add(v2);
+        Fraction[] result_add = {new Fraction(5), new Fraction(7), new Fraction(9)};
+        assertEquals("compare vect" ,v_add.getData(),result_add);
+
+        SparseFieldVector<Fraction> vt2 = new SparseFieldVector<Fraction>(field,vec2);
+        FieldVector<Fraction> v_add_i = v1.add(vt2);
+        Fraction[] result_add_i = {new Fraction(5), new Fraction(7), new Fraction(9)};
+        assertEquals("compare vect" ,v_add_i.getData(),result_add_i);
+
+        //octave =  v1 - v2
+        SparseFieldVector<Fraction> v_subtract = v1.subtract(v2);
+        Fraction[] result_subtract = {new Fraction(-3), new Fraction(-3), new Fraction(-3)};
+        assertClose("compare vect" ,v_subtract.getData(),result_subtract,normTolerance);
+
+        FieldVector<Fraction> v_subtract_i = v1.subtract(vt2);
+        Fraction[] result_subtract_i = {new Fraction(-3), new Fraction(-3), new Fraction(-3)};
+        assertClose("compare vect" ,v_subtract_i.getData(),result_subtract_i,normTolerance);
+
+        // octave v1 .* v2
+        FieldVector<Fraction>  v_ebeMultiply = v1.ebeMultiply(v2);
+        Fraction[] result_ebeMultiply = {new Fraction(4), new Fraction(10), new Fraction(18)};
+        assertClose("compare vect" ,v_ebeMultiply.getData(),result_ebeMultiply,normTolerance);
+
+        FieldVector<Fraction>  v_ebeMultiply_2 = v1.ebeMultiply(v2_t);
+        Fraction[] result_ebeMultiply_2 = {new Fraction(4), new Fraction(10), new Fraction(18)};
+        assertClose("compare vect" ,v_ebeMultiply_2.getData(),result_ebeMultiply_2,normTolerance);
+
+        // octave v1 ./ v2
+        FieldVector<Fraction>  v_ebeDivide = v1.ebeDivide(v2);
+        Fraction[] result_ebeDivide = {new Fraction(0.25d), new Fraction(0.4d), new Fraction(0.5d)};
+        assertClose("compare vect" ,v_ebeDivide.getData(),result_ebeDivide,normTolerance);
+
+        FieldVector<Fraction>  v_ebeDivide_2 = v1.ebeDivide(v2_t);
+        Fraction[] result_ebeDivide_2 = {new Fraction(0.25d), new Fraction(0.4d), new Fraction(0.5d)};
+        assertClose("compare vect" ,v_ebeDivide_2.getData(),result_ebeDivide_2,normTolerance);
+
+        // octave  dot(v1,v2)
+        Fraction dot =  v1.dotProduct(v2);
+        assertEquals("compare val ",new Fraction(32), dot);
+
+        // octave  dot(v1,v2_t)
+        Fraction dot_2 =  v1.dotProduct(v2_t);
+        assertEquals("compare val ",new Fraction(32), dot_2);
+
+        FieldMatrix<Fraction> m_outerProduct = v1.outerProduct(v2);
+        assertEquals("compare val ",new Fraction(4), m_outerProduct.getEntry(0,0));
+
+        FieldMatrix<Fraction> m_outerProduct_2 = v1.outerProduct(v2_t);
+        assertEquals("compare val ",new Fraction(4), m_outerProduct_2.getEntry(0,0));
+
+    }
+
+
+    public void testMisc() {
+        SparseFieldVector<Fraction> v1 = new SparseFieldVector<Fraction>(field,vec1);
+
+        String out1 = v1.toString();
+        assertTrue("some output ",  out1.length()!=0);
+        try {
+            v1.checkVectorDimensions(2);
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected behavior
+        }
+
+
+    }
+
+    public void testPredicates() {
+
+        SparseFieldVector<Fraction> v = new SparseFieldVector<Fraction>(field, new Fraction[] { new Fraction(0), new Fraction(1), new Fraction(2) });
+
+        v.setEntry(0, field.getZero());
+        assertEquals(v, new SparseFieldVector<Fraction>(field, new Fraction[] { new Fraction(0), new Fraction(1), new Fraction(2) }));
+        assertNotSame(v, new SparseFieldVector<Fraction>(field, new Fraction[] { new Fraction(0), new Fraction(1), new Fraction(2), new Fraction(3) }));
+
+    }
+
+    /** verifies that two vectors are close (sup norm) */
+    protected void assertEquals(String msg, Fraction[] m, Fraction[] n) {
+        if (m.length != n.length) {
+            fail("vectors have different lengths");
+        }
+        for (int i = 0; i < m.length; i++) {
+            assertEquals(msg + " " +  i + " elements differ", m[i],n[i]);
+        }
+    }
+
+    /** verifies that two vectors are close (sup norm) */
+    protected void assertClose(String msg, Fraction[] m, Fraction[] n, double tolerance) {
+        if (m.length != n.length) {
+            fail("vectors have different lengths");
+        }
+        for (int i = 0; i < m.length; i++) {
+            assertEquals(msg + " " +  i + " elements differ", m[i].doubleValue(),n[i].doubleValue(), tolerance);
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/linear/SparseRealMatrixTest.java b/src/test/java/org/apache/commons/math/linear/SparseRealMatrixTest.java
new file mode 100644
index 0000000..ec4fa40
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/linear/SparseRealMatrixTest.java
@@ -0,0 +1,671 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 junit.framework.TestCase;
+
+import org.apache.commons.math.TestUtils;
+
+/**
+ * Test cases for the {@link OpenMapRealMatrix} class.
+ *
+ * @version $Revision: 902201 $ $Date: 2008-11-07 06:48:13 -0800 (Fri, 07 Nov
+ *          2008) $
+ */
+public final class SparseRealMatrixTest extends TestCase {
+
+    // 3 x 3 identity matrix
+    protected double[][] id = { { 1d, 0d, 0d }, { 0d, 1d, 0d }, { 0d, 0d, 1d } };
+    // Test data for group operations
+    protected double[][] testData = { { 1d, 2d, 3d }, { 2d, 5d, 3d },
+            { 1d, 0d, 8d } };
+    protected double[][] testDataLU = { { 2d, 5d, 3d }, { .5d, -2.5d, 6.5d },
+            { 0.5d, 0.2d, .2d } };
+    protected double[][] testDataPlus2 = { { 3d, 4d, 5d }, { 4d, 7d, 5d },
+            { 3d, 2d, 10d } };
+    protected double[][] testDataMinus = { { -1d, -2d, -3d },
+            { -2d, -5d, -3d }, { -1d, 0d, -8d } };
+    protected double[] testDataRow1 = { 1d, 2d, 3d };
+    protected double[] testDataCol3 = { 3d, 3d, 8d };
+    protected double[][] testDataInv = { { -40d, 16d, 9d }, { 13d, -5d, -3d },
+            { 5d, -2d, -1d } };
+    protected double[] preMultTest = { 8, 12, 33 };
+    protected double[][] testData2 = { { 1d, 2d, 3d }, { 2d, 5d, 3d } };
+    protected double[][] testData2T = { { 1d, 2d }, { 2d, 5d }, { 3d, 3d } };
+    protected double[][] testDataPlusInv = { { -39d, 18d, 12d },
+            { 15d, 0d, 0d }, { 6d, -2d, 7d } };
+
+    // lu decomposition tests
+    protected double[][] luData = { { 2d, 3d, 3d }, { 0d, 5d, 7d }, { 6d, 9d, 8d } };
+    protected double[][] luDataLUDecomposition = { { 6d, 9d, 8d },
+            { 0d, 5d, 7d }, { 0.33333333333333, 0d, 0.33333333333333 } };
+
+    // singular matrices
+    protected double[][] singular = { { 2d, 3d }, { 2d, 3d } };
+    protected double[][] bigSingular = { { 1d, 2d, 3d, 4d },
+            { 2d, 5d, 3d, 4d }, { 7d, 3d, 256d, 1930d }, { 3d, 7d, 6d, 8d } }; // 4th
+
+    // row
+    // =
+    // 1st
+    // +
+    // 2nd
+    protected double[][] detData = { { 1d, 2d, 3d }, { 4d, 5d, 6d },
+            { 7d, 8d, 10d } };
+    protected double[][] detData2 = { { 1d, 3d }, { 2d, 4d } };
+
+    // vectors
+    protected double[] testVector = { 1, 2, 3 };
+    protected double[] testVector2 = { 1, 2, 3, 4 };
+
+    // submatrix accessor tests
+    protected double[][] subTestData = { { 1, 2, 3, 4 },
+            { 1.5, 2.5, 3.5, 4.5 }, { 2, 4, 6, 8 }, { 4, 5, 6, 7 } };
+
+    // array selections
+    protected double[][] subRows02Cols13 = { { 2, 4 }, { 4, 8 } };
+    protected double[][] subRows03Cols12 = { { 2, 3 }, { 5, 6 } };
+    protected double[][] subRows03Cols123 = { { 2, 3, 4 }, { 5, 6, 7 } };
+
+    // effective permutations
+    protected double[][] subRows20Cols123 = { { 4, 6, 8 }, { 2, 3, 4 } };
+    protected double[][] subRows31Cols31 = { { 7, 5 }, { 4.5, 2.5 } };
+
+    // contiguous ranges
+    protected double[][] subRows01Cols23 = { { 3, 4 }, { 3.5, 4.5 } };
+    protected double[][] subRows23Cols00 = { { 2 }, { 4 } };
+    protected double[][] subRows00Cols33 = { { 4 } };
+
+    // row matrices
+    protected double[][] subRow0 = { { 1, 2, 3, 4 } };
+    protected double[][] subRow3 = { { 4, 5, 6, 7 } };
+
+    // column matrices
+    protected double[][] subColumn1 = { { 2 }, { 2.5 }, { 4 }, { 5 } };
+    protected double[][] subColumn3 = { { 4 }, { 4.5 }, { 8 }, { 7 } };
+
+    // tolerances
+    protected double entryTolerance = 10E-16;
+    protected double normTolerance = 10E-14;
+
+    public SparseRealMatrixTest(String name) {
+        super(name);
+    }
+
+    /** test dimensions */
+    public void testDimensions() {
+        OpenMapRealMatrix m = createSparseMatrix(testData);
+        OpenMapRealMatrix m2 = createSparseMatrix(testData2);
+        assertEquals("testData row dimension", 3, m.getRowDimension());
+        assertEquals("testData column dimension", 3, m.getColumnDimension());
+        assertTrue("testData is square", m.isSquare());
+        assertEquals("testData2 row dimension", m2.getRowDimension(), 2);
+        assertEquals("testData2 column dimension", m2.getColumnDimension(), 3);
+        assertTrue("testData2 is not square", !m2.isSquare());
+    }
+
+    /** test copy functions */
+    public void testCopyFunctions() {
+        OpenMapRealMatrix m1 = createSparseMatrix(testData);
+        RealMatrix m2 = m1.copy();
+        assertEquals(m1.getClass(), m2.getClass());
+        assertEquals((m2), m1);
+        OpenMapRealMatrix m3 = createSparseMatrix(testData);
+        RealMatrix m4 = m3.copy();
+        assertEquals(m3.getClass(), m4.getClass());
+        assertEquals((m4), m3);
+    }
+
+    /** test add */
+    public void testAdd() {
+        OpenMapRealMatrix m = createSparseMatrix(testData);
+        OpenMapRealMatrix mInv = createSparseMatrix(testDataInv);
+        OpenMapRealMatrix mDataPlusInv = createSparseMatrix(testDataPlusInv);
+        RealMatrix mPlusMInv = m.add(mInv);
+        for (int row = 0; row < m.getRowDimension(); row++) {
+            for (int col = 0; col < m.getColumnDimension(); col++) {
+                assertEquals("sum entry entry",
+                    mDataPlusInv.getEntry(row, col), mPlusMInv.getEntry(row, col),
+                    entryTolerance);
+            }
+        }
+    }
+
+    /** test add failure */
+    public void testAddFail() {
+        OpenMapRealMatrix m = createSparseMatrix(testData);
+        OpenMapRealMatrix m2 = createSparseMatrix(testData2);
+        try {
+            m.add(m2);
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    /** test norm */
+    public void testNorm() {
+        OpenMapRealMatrix m = createSparseMatrix(testData);
+        OpenMapRealMatrix m2 = createSparseMatrix(testData2);
+        assertEquals("testData norm", 14d, m.getNorm(), entryTolerance);
+        assertEquals("testData2 norm", 7d, m2.getNorm(), entryTolerance);
+    }
+
+    /** test m-n = m + -n */
+    public void testPlusMinus() {
+        OpenMapRealMatrix m = createSparseMatrix(testData);
+        OpenMapRealMatrix n = createSparseMatrix(testDataInv);
+        assertClose("m-n = m + -n", m.subtract(n),
+            n.scalarMultiply(-1d).add(m), entryTolerance);
+        try {
+            m.subtract(createSparseMatrix(testData2));
+            fail("Expecting illegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    /** test multiply */
+    public void testMultiply() {
+        OpenMapRealMatrix m = createSparseMatrix(testData);
+        OpenMapRealMatrix mInv = createSparseMatrix(testDataInv);
+        OpenMapRealMatrix identity = createSparseMatrix(id);
+        OpenMapRealMatrix m2 = createSparseMatrix(testData2);
+        assertClose("inverse multiply", m.multiply(mInv), identity,
+                entryTolerance);
+        assertClose("inverse multiply", m.multiply(new BlockRealMatrix(testDataInv)), identity,
+                    entryTolerance);
+        assertClose("inverse multiply", mInv.multiply(m), identity,
+                entryTolerance);
+        assertClose("identity multiply", m.multiply(identity), m,
+                entryTolerance);
+        assertClose("identity multiply", identity.multiply(mInv), mInv,
+                entryTolerance);
+        assertClose("identity multiply", m2.multiply(identity), m2,
+                entryTolerance);
+        try {
+            m.multiply(createSparseMatrix(bigSingular));
+            fail("Expecting illegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    // Additional Test for Array2DRowRealMatrixTest.testMultiply
+
+    private double[][] d3 = new double[][] { { 1, 2, 3, 4 }, { 5, 6, 7, 8 } };
+    private double[][] d4 = new double[][] { { 1 }, { 2 }, { 3 }, { 4 } };
+    private double[][] d5 = new double[][] { { 30 }, { 70 } };
+
+    public void testMultiply2() {
+        RealMatrix m3 = createSparseMatrix(d3);
+        RealMatrix m4 = createSparseMatrix(d4);
+        RealMatrix m5 = createSparseMatrix(d5);
+        assertClose("m3*m4=m5", m3.multiply(m4), m5, entryTolerance);
+    }
+
+    /** test trace */
+    public void testTrace() {
+        RealMatrix m = createSparseMatrix(id);
+        assertEquals("identity trace", 3d, m.getTrace(), entryTolerance);
+        m = createSparseMatrix(testData2);
+        try {
+            m.getTrace();
+            fail("Expecting NonSquareMatrixException");
+        } catch (NonSquareMatrixException ex) {
+            // ignored
+        }
+    }
+
+    /** test sclarAdd */
+    public void testScalarAdd() {
+        RealMatrix m = createSparseMatrix(testData);
+        assertClose("scalar add", createSparseMatrix(testDataPlus2),
+            m.scalarAdd(2d), entryTolerance);
+    }
+
+    /** test operate */
+    public void testOperate() {
+        RealMatrix m = createSparseMatrix(id);
+        assertClose("identity operate", testVector, m.operate(testVector),
+                entryTolerance);
+        assertClose("identity operate", testVector, m.operate(
+                new ArrayRealVector(testVector)).getData(), entryTolerance);
+        m = createSparseMatrix(bigSingular);
+        try {
+            m.operate(testVector);
+            fail("Expecting illegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    /** test issue MATH-209 */
+    public void testMath209() {
+        RealMatrix a = createSparseMatrix(new double[][] {
+                { 1, 2 }, { 3, 4 }, { 5, 6 } });
+        double[] b = a.operate(new double[] { 1, 1 });
+        assertEquals(a.getRowDimension(), b.length);
+        assertEquals(3.0, b[0], 1.0e-12);
+        assertEquals(7.0, b[1], 1.0e-12);
+        assertEquals(11.0, b[2], 1.0e-12);
+    }
+
+    /** test transpose */
+    public void testTranspose() {
+
+        RealMatrix m = createSparseMatrix(testData);
+        RealMatrix mIT = new LUDecompositionImpl(m).getSolver().getInverse().transpose();
+        RealMatrix mTI = new LUDecompositionImpl(m.transpose()).getSolver().getInverse();
+        assertClose("inverse-transpose", mIT, mTI, normTolerance);
+        m = createSparseMatrix(testData2);
+        RealMatrix mt = createSparseMatrix(testData2T);
+        assertClose("transpose",mt,m.transpose(),normTolerance);
+    }
+
+    /** test preMultiply by vector */
+    public void testPremultiplyVector() {
+        RealMatrix m = createSparseMatrix(testData);
+        assertClose("premultiply", m.preMultiply(testVector), preMultTest,
+            normTolerance);
+        assertClose("premultiply", m.preMultiply(
+            new ArrayRealVector(testVector).getData()), preMultTest, normTolerance);
+        m = createSparseMatrix(bigSingular);
+        try {
+            m.preMultiply(testVector);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    public void testPremultiply() {
+        RealMatrix m3 = createSparseMatrix(d3);
+        RealMatrix m4 = createSparseMatrix(d4);
+        RealMatrix m5 = createSparseMatrix(d5);
+        assertClose("m3*m4=m5", m4.preMultiply(m3), m5, entryTolerance);
+
+        OpenMapRealMatrix m = createSparseMatrix(testData);
+        OpenMapRealMatrix mInv = createSparseMatrix(testDataInv);
+        OpenMapRealMatrix identity = createSparseMatrix(id);
+        assertClose("inverse multiply", m.preMultiply(mInv), identity,
+                entryTolerance);
+        assertClose("inverse multiply", mInv.preMultiply(m), identity,
+                entryTolerance);
+        assertClose("identity multiply", m.preMultiply(identity), m,
+                entryTolerance);
+        assertClose("identity multiply", identity.preMultiply(mInv), mInv,
+                entryTolerance);
+        try {
+            m.preMultiply(createSparseMatrix(bigSingular));
+            fail("Expecting illegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    public void testGetVectors() {
+        RealMatrix m = createSparseMatrix(testData);
+        assertClose("get row", m.getRow(0), testDataRow1, entryTolerance);
+        assertClose("get col", m.getColumn(2), testDataCol3, entryTolerance);
+        try {
+            m.getRow(10);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // ignored
+        }
+        try {
+            m.getColumn(-1);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // ignored
+        }
+    }
+
+    public void testGetEntry() {
+        RealMatrix m = createSparseMatrix(testData);
+        assertEquals("get entry", m.getEntry(0, 1), 2d, entryTolerance);
+        try {
+            m.getEntry(10, 4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    /** test examples in user guide */
+    public void testExamples() {
+        // Create a real matrix with two rows and three columns
+        double[][] matrixData = { { 1d, 2d, 3d }, { 2d, 5d, 3d } };
+        RealMatrix m = createSparseMatrix(matrixData);
+        // One more with three rows, two columns
+        double[][] matrixData2 = { { 1d, 2d }, { 2d, 5d }, { 1d, 7d } };
+        RealMatrix n = createSparseMatrix(matrixData2);
+        // Now multiply m by n
+        RealMatrix p = m.multiply(n);
+        assertEquals(2, p.getRowDimension());
+        assertEquals(2, p.getColumnDimension());
+        // Invert p
+        RealMatrix pInverse = new LUDecompositionImpl(p).getSolver().getInverse();
+        assertEquals(2, pInverse.getRowDimension());
+        assertEquals(2, pInverse.getColumnDimension());
+
+        // Solve example
+        double[][] coefficientsData = { { 2, 3, -2 }, { -1, 7, 6 },
+                { 4, -3, -5 } };
+        RealMatrix coefficients = createSparseMatrix(coefficientsData);
+        double[] constants = { 1, -2, 1 };
+        double[] solution = new LUDecompositionImpl(coefficients).getSolver().solve(constants);
+        assertEquals(2 * solution[0] + 3 * solution[1] - 2 * solution[2],
+                constants[0], 1E-12);
+        assertEquals(-1 * solution[0] + 7 * solution[1] + 6 * solution[2],
+                constants[1], 1E-12);
+        assertEquals(4 * solution[0] - 3 * solution[1] - 5 * solution[2],
+                constants[2], 1E-12);
+
+    }
+
+    // test submatrix accessors
+    public void testSubMatrix() {
+        RealMatrix m = createSparseMatrix(subTestData);
+        RealMatrix mRows23Cols00 = createSparseMatrix(subRows23Cols00);
+        RealMatrix mRows00Cols33 = createSparseMatrix(subRows00Cols33);
+        RealMatrix mRows01Cols23 = createSparseMatrix(subRows01Cols23);
+        RealMatrix mRows02Cols13 = createSparseMatrix(subRows02Cols13);
+        RealMatrix mRows03Cols12 = createSparseMatrix(subRows03Cols12);
+        RealMatrix mRows03Cols123 = createSparseMatrix(subRows03Cols123);
+        RealMatrix mRows20Cols123 = createSparseMatrix(subRows20Cols123);
+        RealMatrix mRows31Cols31 = createSparseMatrix(subRows31Cols31);
+        assertEquals("Rows23Cols00", mRows23Cols00, m.getSubMatrix(2, 3, 0, 0));
+        assertEquals("Rows00Cols33", mRows00Cols33, m.getSubMatrix(0, 0, 3, 3));
+        assertEquals("Rows01Cols23", mRows01Cols23, m.getSubMatrix(0, 1, 2, 3));
+        assertEquals("Rows02Cols13", mRows02Cols13,
+            m.getSubMatrix(new int[] { 0, 2 }, new int[] { 1, 3 }));
+        assertEquals("Rows03Cols12", mRows03Cols12,
+            m.getSubMatrix(new int[] { 0, 3 }, new int[] { 1, 2 }));
+        assertEquals("Rows03Cols123", mRows03Cols123,
+            m.getSubMatrix(new int[] { 0, 3 }, new int[] { 1, 2, 3 }));
+        assertEquals("Rows20Cols123", mRows20Cols123,
+            m.getSubMatrix(new int[] { 2, 0 }, new int[] { 1, 2, 3 }));
+        assertEquals("Rows31Cols31", mRows31Cols31,
+            m.getSubMatrix(new int[] { 3, 1 }, new int[] { 3, 1 }));
+        assertEquals("Rows31Cols31", mRows31Cols31,
+            m.getSubMatrix(new int[] { 3, 1 }, new int[] { 3, 1 }));
+
+        try {
+            m.getSubMatrix(1, 0, 2, 4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getSubMatrix(-1, 1, 2, 2);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getSubMatrix(1, 0, 2, 2);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getSubMatrix(1, 0, 2, 4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getSubMatrix(new int[] {}, new int[] { 0 });
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getSubMatrix(new int[] { 0 }, new int[] { 4 });
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testGetRowMatrix() {
+        RealMatrix m = createSparseMatrix(subTestData);
+        RealMatrix mRow0 = createSparseMatrix(subRow0);
+        RealMatrix mRow3 = createSparseMatrix(subRow3);
+        assertEquals("Row0", mRow0, m.getRowMatrix(0));
+        assertEquals("Row3", mRow3, m.getRowMatrix(3));
+        try {
+            m.getRowMatrix(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getRowMatrix(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testGetColumnMatrix() {
+        RealMatrix m = createSparseMatrix(subTestData);
+        RealMatrix mColumn1 = createSparseMatrix(subColumn1);
+        RealMatrix mColumn3 = createSparseMatrix(subColumn3);
+        assertEquals("Column1", mColumn1, m.getColumnMatrix(1));
+        assertEquals("Column3", mColumn3, m.getColumnMatrix(3));
+        try {
+            m.getColumnMatrix(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getColumnMatrix(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testGetRowVector() {
+        RealMatrix m = createSparseMatrix(subTestData);
+        RealVector mRow0 = new ArrayRealVector(subRow0[0]);
+        RealVector mRow3 = new ArrayRealVector(subRow3[0]);
+        assertEquals("Row0", mRow0, m.getRowVector(0));
+        assertEquals("Row3", mRow3, m.getRowVector(3));
+        try {
+            m.getRowVector(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getRowVector(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    public void testGetColumnVector() {
+        RealMatrix m = createSparseMatrix(subTestData);
+        RealVector mColumn1 = columnToVector(subColumn1);
+        RealVector mColumn3 = columnToVector(subColumn3);
+        assertEquals("Column1", mColumn1, m.getColumnVector(1));
+        assertEquals("Column3", mColumn3, m.getColumnVector(3));
+        try {
+            m.getColumnVector(-1);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+        try {
+            m.getColumnVector(4);
+            fail("Expecting MatrixIndexException");
+        } catch (MatrixIndexException ex) {
+            // expected
+        }
+    }
+
+    private RealVector columnToVector(double[][] column) {
+        double[] data = new double[column.length];
+        for (int i = 0; i < data.length; ++i) {
+            data[i] = column[i][0];
+        }
+        return new ArrayRealVector(data, false);
+    }
+
+    public void testEqualsAndHashCode() {
+        OpenMapRealMatrix m = createSparseMatrix(testData);
+        OpenMapRealMatrix m1 = m.copy();
+        OpenMapRealMatrix mt = (OpenMapRealMatrix) m.transpose();
+        assertTrue(m.hashCode() != mt.hashCode());
+        assertEquals(m.hashCode(), m1.hashCode());
+        assertEquals(m, m);
+        assertEquals(m, m1);
+        assertFalse(m.equals(null));
+        assertFalse(m.equals(mt));
+        assertFalse(m.equals(createSparseMatrix(bigSingular)));
+    }
+
+    public void testToString() {
+        OpenMapRealMatrix m = createSparseMatrix(testData);
+        assertEquals("OpenMapRealMatrix{{1.0,2.0,3.0},{2.0,5.0,3.0},{1.0,0.0,8.0}}",
+            m.toString());
+        m = new OpenMapRealMatrix(1, 1);
+        assertEquals("OpenMapRealMatrix{{0.0}}", m.toString());
+    }
+
+    public void testSetSubMatrix() throws Exception {
+        OpenMapRealMatrix m = createSparseMatrix(testData);
+        m.setSubMatrix(detData2, 1, 1);
+        RealMatrix expected = createSparseMatrix(new double[][] {
+                { 1.0, 2.0, 3.0 }, { 2.0, 1.0, 3.0 }, { 1.0, 2.0, 4.0 } });
+        assertEquals(expected, m);
+
+        m.setSubMatrix(detData2, 0, 0);
+        expected = createSparseMatrix(new double[][] {
+                { 1.0, 3.0, 3.0 }, { 2.0, 4.0, 3.0 }, { 1.0, 2.0, 4.0 } });
+        assertEquals(expected, m);
+
+        m.setSubMatrix(testDataPlus2, 0, 0);
+        expected = createSparseMatrix(new double[][] {
+                { 3.0, 4.0, 5.0 }, { 4.0, 7.0, 5.0 }, { 3.0, 2.0, 10.0 } });
+        assertEquals(expected, m);
+
+        // javadoc example
+        OpenMapRealMatrix matrix =
+            createSparseMatrix(new double[][] {
+        { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 0, 1, 2 } });
+        matrix.setSubMatrix(new double[][] { { 3, 4 }, { 5, 6 } }, 1, 1);
+        expected = createSparseMatrix(new double[][] {
+                { 1, 2, 3, 4 }, { 5, 3, 4, 8 }, { 9, 5, 6, 2 } });
+        assertEquals(expected, matrix);
+
+        // dimension overflow
+        try {
+            m.setSubMatrix(testData, 1, 1);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException e) {
+            // expected
+        }
+        // dimension underflow
+        try {
+            m.setSubMatrix(testData, -1, 1);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException e) {
+            // expected
+        }
+        try {
+            m.setSubMatrix(testData, 1, -1);
+            fail("expecting MatrixIndexException");
+        } catch (MatrixIndexException e) {
+            // expected
+        }
+
+        // null
+        try {
+            m.setSubMatrix(null, 1, 1);
+            fail("expecting NullPointerException");
+        } catch (NullPointerException e) {
+            // expected
+        }
+        try {
+            new OpenMapRealMatrix(0, 0);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        // ragged
+        try {
+            m.setSubMatrix(new double[][] { { 1 }, { 2, 3 } }, 0, 0);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+        // empty
+        try {
+            m.setSubMatrix(new double[][] { {} }, 0, 0);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+
+    }
+
+    public void testSerial()  {
+        OpenMapRealMatrix m = createSparseMatrix(testData);
+        assertEquals(m,TestUtils.serializeAndRecover(m));
+    }
+
+    // --------------- -----------------Protected methods
+
+    /** verifies that two matrices are close (1-norm) */
+    protected void assertClose(String msg, RealMatrix m, RealMatrix n,
+            double tolerance) {
+        assertTrue(msg, m.subtract(n).getNorm() < tolerance);
+    }
+
+    /** verifies that two vectors are close (sup norm) */
+    protected void assertClose(String msg, double[] m, double[] n,
+            double tolerance) {
+        if (m.length != n.length) {
+            fail("vectors not same length");
+        }
+        for (int i = 0; i < m.length; i++) {
+            assertEquals(msg + " " + i + " elements differ", m[i], n[i],
+                    tolerance);
+        }
+    }
+
+    private OpenMapRealMatrix createSparseMatrix(double[][] data) {
+        OpenMapRealMatrix matrix = new OpenMapRealMatrix(data.length, data[0].length);
+        for (int row = 0; row < data.length; row++) {
+            for (int col = 0; col < data[row].length; col++) {
+                matrix.setEntry(row, col, data[row][col]);
+            }
+        }
+        return matrix;
+    }
+
+
+}
diff --git a/src/test/java/org/apache/commons/math/linear/SparseRealVectorTest.java b/src/test/java/org/apache/commons/math/linear/SparseRealVectorTest.java
new file mode 100644
index 0000000..bef7de9
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/linear/SparseRealVectorTest.java
@@ -0,0 +1,1194 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.Iterator;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.linear.MatrixVisitorException;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Test cases for the {@link OpenMapRealVector} class.
+ *
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ */
+public class SparseRealVectorTest extends TestCase {
+
+    //
+    protected double[][] ma1 = {{1d, 2d, 3d}, {4d, 5d, 6d}, {7d, 8d, 9d}};
+    protected double[] vec1 = {1d, 2d, 3d};
+    protected double[] vec2 = {4d, 5d, 6d};
+    protected double[] vec3 = {7d, 8d, 9d};
+    protected double[] vec4 = {1d, 2d, 3d, 4d, 5d, 6d, 7d, 8d, 9d};
+    protected double[] vec5 = { -4d, 0d, 3d, 1d, -6d, 3d};
+    protected double[] vec_null = {0d, 0d, 0d};
+    protected Double[] dvec1 = {1d, 2d, 3d, 4d, 5d, 6d, 7d, 8d, 9d};
+    protected double[][] mat1 = {{1d, 2d, 3d}, {4d, 5d, 6d},{ 7d, 8d, 9d}};
+
+    // tolerances
+    protected double entryTolerance = 10E-16;
+    protected double normTolerance = 10E-14;
+
+    // Testclass to test the RealVector interface
+    // only with enough content to support the test
+    public static class SparseRealVectorTestImpl extends AbstractRealVector implements Serializable {
+
+        private static final long serialVersionUID = -6251371752518113791L;
+        /** Entries of the vector. */
+        protected double data[];
+
+        public SparseRealVectorTestImpl(double[] d) {
+            data = d.clone();
+        }
+
+        private MatrixVisitorException unsupported() {
+            return new MatrixVisitorException("Not supported, unneeded for test purposes", new Object[0]);
+        }
+
+        @Override
+        public RealVector map(UnivariateRealFunction function) throws MatrixVisitorException {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapToSelf(UnivariateRealFunction function) throws MatrixVisitorException {
+            throw unsupported();
+        }
+
+        @Override
+        public Iterator<Entry> iterator() {
+            throw unsupported();
+        }
+
+        @Override
+        public AbstractRealVector copy() {
+            return new SparseRealVectorTestImpl(data);
+        }
+
+        @Override
+        public RealVector add(RealVector v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector add(double[] v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector subtract(RealVector v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector subtract(double[] v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapAdd(double d) {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapAddToSelf(double d) {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapSubtract(double d) {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapSubtractToSelf(double d) {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapMultiply(double d) {
+            double[] out = new double[data.length];
+            for (int i = 0; i < data.length; i++) {
+                out[i] = data[i] * d;
+            }
+            return new OpenMapRealVector(out);
+        }
+
+        @Override
+        public RealVector mapMultiplyToSelf(double d) {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapDivide(double d) {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapDivideToSelf(double d) {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapPow(double d) {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapPowToSelf(double d) {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapExp() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapExpToSelf() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapExpm1() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapExpm1ToSelf() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapLog() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapLogToSelf() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapLog10() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapLog10ToSelf() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapLog1p() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapLog1pToSelf() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapCosh() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapCoshToSelf() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapSinh() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapSinhToSelf() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapTanh() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapTanhToSelf() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapCos() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapCosToSelf() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapSin() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapSinToSelf() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapTan() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapTanToSelf() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapAcos() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapAcosToSelf() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapAsin() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapAsinToSelf() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapAtan() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapAtanToSelf() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapInv() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapInvToSelf() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapAbs() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapAbsToSelf() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapSqrt() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapSqrtToSelf() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapCbrt() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapCbrtToSelf() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapCeil() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapCeilToSelf() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapFloor() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapFloorToSelf() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapRint() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapRintToSelf() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapSignum() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapSignumToSelf() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapUlp() {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector mapUlpToSelf() {
+            throw unsupported();
+        }
+
+        public RealVector ebeMultiply(RealVector v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector ebeMultiply(double[] v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        public RealVector ebeDivide(RealVector v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector ebeDivide(double[] v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        @Override
+        public double[] getData() {
+            return data.clone();
+        }
+
+        @Override
+        public double dotProduct(RealVector v) throws IllegalArgumentException {
+            double dot = 0;
+            for (int i = 0; i < data.length; i++) {
+                dot += data[i] * v.getEntry(i);
+            }
+            return dot;
+        }
+
+        @Override
+        public double dotProduct(double[] v) throws IllegalArgumentException {
+            double dot = 0;
+            for (int i = 0; i < data.length; i++) {
+                dot += data[i] * v[i];
+            }
+            return dot;
+        }
+
+        @Override
+        public double getNorm() {
+            throw unsupported();
+        }
+
+        @Override
+        public double getL1Norm() {
+            throw unsupported();
+        }
+
+        @Override
+        public double getLInfNorm() {
+            throw unsupported();
+        }
+
+        @Override
+        public double getDistance(RealVector v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        @Override
+        public double getDistance(double[] v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        @Override
+        public double getL1Distance(RealVector v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        @Override
+        public double getL1Distance(double[] v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        @Override
+        public double getLInfDistance(RealVector v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        @Override
+        public double getLInfDistance(double[] v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector unitVector() {
+            throw unsupported();
+        }
+
+        @Override
+        public void unitize() {
+            throw unsupported();
+        }
+
+        public RealVector projection(RealVector v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        @Override
+        public RealVector projection(double[] v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        @Override
+        public RealMatrix outerProduct(RealVector v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        @Override
+        public RealMatrix outerProduct(double[] v) throws IllegalArgumentException {
+            throw unsupported();
+        }
+
+        public double getEntry(int index) throws MatrixIndexException {
+            return data[index];
+        }
+
+        public int getDimension() {
+            return data.length;
+        }
+
+        public RealVector append(RealVector v) {
+            throw unsupported();
+        }
+
+        public RealVector append(double d) {
+            throw unsupported();
+        }
+
+        public RealVector append(double[] a) {
+            throw unsupported();
+        }
+
+        public RealVector getSubVector(int index, int n) throws MatrixIndexException {
+            throw unsupported();
+        }
+
+        public void setEntry(int index, double value) throws MatrixIndexException {
+            data[index] = value;
+        }
+
+        @Override
+        public void setSubVector(int index, RealVector v) throws MatrixIndexException {
+            throw unsupported();
+        }
+
+        @Override
+        public void setSubVector(int index, double[] v) throws MatrixIndexException {
+            throw unsupported();
+        }
+
+        @Override
+        public void set(double value) {
+            throw unsupported();
+        }
+
+        @Override
+        public double[] toArray() {
+            throw unsupported();
+        }
+
+        public boolean isNaN() {
+            throw unsupported();
+        }
+
+        public boolean isInfinite() {
+            throw unsupported();
+        }
+
+    }
+
+    public void testConstructors() {
+
+        OpenMapRealVector v0 = new OpenMapRealVector();
+        assertEquals("testData len", 0, v0.getDimension());
+
+        OpenMapRealVector v1 = new OpenMapRealVector(7);
+        assertEquals("testData len", 7, v1.getDimension());
+        assertEquals("testData is 0.0 ", 0.0, v1.getEntry(6));
+
+        OpenMapRealVector v3 = new OpenMapRealVector(vec1);
+        assertEquals("testData len", 3, v3.getDimension());
+        assertEquals("testData is 2.0 ", 2.0, v3.getEntry(1));
+
+        //SparseRealVector v4 = new SparseRealVector(vec4, 3, 2);
+        //assertEquals("testData len", 2, v4.getDimension());
+        //assertEquals("testData is 4.0 ", 4.0, v4.getEntry(0));
+        //try {
+        //    new SparseRealVector(vec4, 8, 3);
+        //    fail("IllegalArgumentException expected");
+        //} catch (IllegalArgumentException ex) {
+            // expected behavior
+        //}
+
+        RealVector v5_i = new OpenMapRealVector(dvec1);
+        assertEquals("testData len", 9, v5_i.getDimension());
+        assertEquals("testData is 9.0 ", 9.0, v5_i.getEntry(8));
+
+        OpenMapRealVector v5 = new OpenMapRealVector(dvec1);
+        assertEquals("testData len", 9, v5.getDimension());
+        assertEquals("testData is 9.0 ", 9.0, v5.getEntry(8));
+
+        OpenMapRealVector v7 = new OpenMapRealVector(v1);
+        assertEquals("testData len", 7, v7.getDimension());
+        assertEquals("testData is 0.0 ", 0.0, v7.getEntry(6));
+
+        SparseRealVectorTestImpl v7_i = new SparseRealVectorTestImpl(vec1);
+
+        OpenMapRealVector v7_2 = new OpenMapRealVector(v7_i);
+        assertEquals("testData len", 3, v7_2.getDimension());
+        assertEquals("testData is 0.0 ", 2.0d, v7_2.getEntry(1));
+
+        OpenMapRealVector v8 = new OpenMapRealVector(v1);
+        assertEquals("testData len", 7, v8.getDimension());
+        assertEquals("testData is 0.0 ", 0.0, v8.getEntry(6));
+
+    }
+
+    public void testDataInOut() {
+
+        OpenMapRealVector v1 = new OpenMapRealVector(vec1);
+        OpenMapRealVector v2 = new OpenMapRealVector(vec2);
+        OpenMapRealVector v4 = new OpenMapRealVector(vec4);
+        SparseRealVectorTestImpl v2_t = new SparseRealVectorTestImpl(vec2);
+
+        RealVector v_append_1 = v1.append(v2);
+        assertEquals("testData len", 6, v_append_1.getDimension());
+        assertEquals("testData is 4.0 ", 4.0, v_append_1.getEntry(3));
+
+        RealVector v_append_2 = v1.append(2.0);
+        assertEquals("testData len", 4, v_append_2.getDimension());
+        assertEquals("testData is 2.0 ", 2.0, v_append_2.getEntry(3));
+
+        RealVector v_append_3 = v1.append(vec2);
+        assertEquals("testData len", 6, v_append_3.getDimension());
+        assertEquals("testData is  ", 4.0, v_append_3.getEntry(3));
+
+        RealVector v_append_4 = v1.append(v2_t);
+        assertEquals("testData len", 6, v_append_4.getDimension());
+        assertEquals("testData is 4.0 ", 4.0, v_append_4.getEntry(3));
+
+        RealVector vout5 = v4.getSubVector(3, 3);
+        assertEquals("testData len", 3, vout5.getDimension());
+        assertEquals("testData is 4.0 ", 5.0, vout5.getEntry(1));
+        try {
+            v4.getSubVector(3, 7);
+            fail("MatrixIndexException expected");
+        } catch (MatrixIndexException ex) {
+            // expected behavior
+        }
+
+        OpenMapRealVector v_set1 = v1.copy();
+        v_set1.setEntry(1, 11.0);
+        assertEquals("testData is 11.0 ", 11.0, v_set1.getEntry(1));
+        try {
+            v_set1.setEntry(3, 11.0);
+            fail("MatrixIndexException expected");
+        } catch (MatrixIndexException ex) {
+            // expected behavior
+        }
+
+        OpenMapRealVector v_set2 = v4.copy();
+        v_set2.setSubVector(3, v1);
+        assertEquals("testData is 1.0 ", 1.0, v_set2.getEntry(3));
+        assertEquals("testData is 7.0 ", 7.0, v_set2.getEntry(6));
+        try {
+            v_set2.setSubVector(7, v1);
+            fail("MatrixIndexException expected");
+        } catch (MatrixIndexException ex) {
+            // expected behavior
+        }
+
+        OpenMapRealVector v_set3 = v1.copy();
+        v_set3.set(13.0);
+        assertEquals("testData is 13.0 ", 13.0, v_set3.getEntry(2));
+
+        try {
+            v_set3.getEntry(23);
+            fail("MatrixIndexException expected");
+        } catch (MatrixIndexException ex) {
+            // expected behavior
+        }
+
+        OpenMapRealVector v_set4 = v4.copy();
+        v_set4.setSubVector(3, v2_t);
+        assertEquals("testData is 1.0 ", 4.0, v_set4.getEntry(3));
+        assertEquals("testData is 7.0 ", 7.0, v_set4.getEntry(6));
+        try {
+            v_set4.setSubVector(7, v2_t);
+            fail("MatrixIndexException expected");
+        } catch (MatrixIndexException ex) {
+            // expected behavior
+        }
+
+
+    }
+
+    public void testMapFunctions() {
+        OpenMapRealVector v1 = new OpenMapRealVector(vec1);
+
+        //octave =  v1 .+ 2.0
+        RealVector v_mapAdd = v1.mapAdd(2.0d);
+        double[] result_mapAdd = {3d, 4d, 5d};
+        assertClose("compare vectors" ,result_mapAdd,v_mapAdd.getData(),normTolerance);
+
+        //octave =  v1 .+ 2.0
+        RealVector v_mapAddToSelf = v1.copy();
+        v_mapAddToSelf.mapAddToSelf(2.0d);
+        double[] result_mapAddToSelf = {3d, 4d, 5d};
+        assertClose("compare vectors" ,result_mapAddToSelf,v_mapAddToSelf.getData(),normTolerance);
+
+        //octave =  v1 .- 2.0
+        RealVector v_mapSubtract = v1.mapSubtract(2.0d);
+        double[] result_mapSubtract = {-1d, 0d, 1d};
+        assertClose("compare vectors" ,result_mapSubtract,v_mapSubtract.getData(),normTolerance);
+
+        //octave =  v1 .- 2.0
+        RealVector v_mapSubtractToSelf = v1.copy();
+        v_mapSubtractToSelf.mapSubtractToSelf(2.0d);
+        double[] result_mapSubtractToSelf = {-1d, 0d, 1d};
+        assertClose("compare vectors" ,result_mapSubtractToSelf,v_mapSubtractToSelf.getData(),normTolerance);
+
+        //octave =  v1 .* 2.0
+        RealVector v_mapMultiply = v1.mapMultiply(2.0d);
+        double[] result_mapMultiply = {2d, 4d, 6d};
+        assertClose("compare vectors" ,result_mapMultiply,v_mapMultiply.getData(),normTolerance);
+
+        //octave =  v1 .* 2.0
+        RealVector v_mapMultiplyToSelf = v1.copy();
+        v_mapMultiplyToSelf.mapMultiplyToSelf(2.0d);
+        double[] result_mapMultiplyToSelf = {2d, 4d, 6d};
+        assertClose("compare vectors" ,result_mapMultiplyToSelf,v_mapMultiplyToSelf.getData(),normTolerance);
+
+        //octave =  v1 ./ 2.0
+        RealVector v_mapDivide = v1.mapDivide(2.0d);
+        double[] result_mapDivide = {.5d, 1d, 1.5d};
+        assertClose("compare vectors" ,result_mapDivide,v_mapDivide.getData(),normTolerance);
+
+        //octave =  v1 ./ 2.0
+        RealVector v_mapDivideToSelf = v1.copy();
+        v_mapDivideToSelf.mapDivideToSelf(2.0d);
+        double[] result_mapDivideToSelf = {.5d, 1d, 1.5d};
+        assertClose("compare vectors" ,result_mapDivideToSelf,v_mapDivideToSelf.getData(),normTolerance);
+
+        //octave =  v1 .^ 2.0
+        RealVector v_mapPow = v1.mapPow(2.0d);
+        double[] result_mapPow = {1d, 4d, 9d};
+        assertClose("compare vectors" ,result_mapPow,v_mapPow.getData(),normTolerance);
+
+        //octave =  v1 .^ 2.0
+        RealVector v_mapPowToSelf = v1.copy();
+        v_mapPowToSelf.mapPowToSelf(2.0d);
+        double[] result_mapPowToSelf = {1d, 4d, 9d};
+        assertClose("compare vectors" ,result_mapPowToSelf,v_mapPowToSelf.getData(),normTolerance);
+
+        //octave =  exp(v1)
+        RealVector v_mapExp = v1.mapExp();
+        double[] result_mapExp = {2.718281828459045e+00d,7.389056098930650e+00d, 2.008553692318767e+01d};
+        assertClose("compare vectors" ,result_mapExp,v_mapExp.getData(),normTolerance);
+
+        //octave =  exp(v1)
+        RealVector v_mapExpToSelf = v1.copy();
+        v_mapExpToSelf.mapExpToSelf();
+        double[] result_mapExpToSelf = {2.718281828459045e+00d,7.389056098930650e+00d, 2.008553692318767e+01d};
+        assertClose("compare vectors" ,result_mapExpToSelf,v_mapExpToSelf.getData(),normTolerance);
+
+
+        //octave =  ???
+        RealVector v_mapExpm1 = v1.mapExpm1();
+        double[] result_mapExpm1 = {1.718281828459045d,6.38905609893065d, 19.085536923187668d};
+        assertClose("compare vectors" ,result_mapExpm1,v_mapExpm1.getData(),normTolerance);
+
+        //octave =  ???
+        RealVector v_mapExpm1ToSelf = v1.copy();
+        v_mapExpm1ToSelf.mapExpm1ToSelf();
+        double[] result_mapExpm1ToSelf = {1.718281828459045d,6.38905609893065d, 19.085536923187668d};
+        assertClose("compare vectors" ,result_mapExpm1ToSelf,v_mapExpm1ToSelf.getData(),normTolerance);
+
+        //octave =  log(v1)
+        RealVector v_mapLog = v1.mapLog();
+        double[] result_mapLog = {0d,6.931471805599453e-01d, 1.098612288668110e+00d};
+        assertClose("compare vectors" ,result_mapLog,v_mapLog.getData(),normTolerance);
+
+        //octave =  log(v1)
+        RealVector v_mapLogToSelf = v1.copy();
+        v_mapLogToSelf.mapLogToSelf();
+        double[] result_mapLogToSelf = {0d,6.931471805599453e-01d, 1.098612288668110e+00d};
+        assertClose("compare vectors" ,result_mapLogToSelf,v_mapLogToSelf.getData(),normTolerance);
+
+        //octave =  log10(v1)
+        RealVector v_mapLog10 = v1.mapLog10();
+        double[] result_mapLog10 = {0d,3.010299956639812e-01d, 4.771212547196624e-01d};
+        assertClose("compare vectors" ,result_mapLog10,v_mapLog10.getData(),normTolerance);
+
+        //octave =  log(v1)
+        RealVector v_mapLog10ToSelf = v1.copy();
+        v_mapLog10ToSelf.mapLog10ToSelf();
+        double[] result_mapLog10ToSelf = {0d,3.010299956639812e-01d, 4.771212547196624e-01d};
+        assertClose("compare vectors" ,result_mapLog10ToSelf,v_mapLog10ToSelf.getData(),normTolerance);
+
+        //octave =  ???
+        RealVector v_mapLog1p = v1.mapLog1p();
+        double[] result_mapLog1p = {0.6931471805599453d,1.0986122886681096d,1.3862943611198906d};
+        assertClose("compare vectors" ,result_mapLog1p,v_mapLog1p.getData(),normTolerance);
+
+        //octave =  ???
+        RealVector v_mapLog1pToSelf = v1.copy();
+        v_mapLog1pToSelf.mapLog1pToSelf();
+        double[] result_mapLog1pToSelf = {0.6931471805599453d,1.0986122886681096d,1.3862943611198906d};
+        assertClose("compare vectors" ,result_mapLog1pToSelf,v_mapLog1pToSelf.getData(),normTolerance);
+
+        //octave =  cosh(v1)
+        RealVector v_mapCosh = v1.mapCosh();
+        double[] result_mapCosh = {1.543080634815244e+00d,3.762195691083631e+00d, 1.006766199577777e+01d};
+        assertClose("compare vectors" ,result_mapCosh,v_mapCosh.getData(),normTolerance);
+
+        //octave =  cosh(v1)
+        RealVector v_mapCoshToSelf = v1.copy();
+        v_mapCoshToSelf.mapCoshToSelf();
+        double[] result_mapCoshToSelf = {1.543080634815244e+00d,3.762195691083631e+00d, 1.006766199577777e+01d};
+        assertClose("compare vectors" ,result_mapCoshToSelf,v_mapCoshToSelf.getData(),normTolerance);
+
+        //octave =  sinh(v1)
+        RealVector v_mapSinh = v1.mapSinh();
+        double[] result_mapSinh = {1.175201193643801e+00d,3.626860407847019e+00d, 1.001787492740990e+01d};
+        assertClose("compare vectors" ,result_mapSinh,v_mapSinh.getData(),normTolerance);
+
+        //octave =  sinh(v1)
+        RealVector v_mapSinhToSelf = v1.copy();
+        v_mapSinhToSelf.mapSinhToSelf();
+        double[] result_mapSinhToSelf = {1.175201193643801e+00d,3.626860407847019e+00d, 1.001787492740990e+01d};
+        assertClose("compare vectors" ,result_mapSinhToSelf,v_mapSinhToSelf.getData(),normTolerance);
+
+        //octave =  tanh(v1)
+        RealVector v_mapTanh = v1.mapTanh();
+        double[] result_mapTanh = {7.615941559557649e-01d,9.640275800758169e-01d,9.950547536867305e-01d};
+        assertClose("compare vectors" ,result_mapTanh,v_mapTanh.getData(),normTolerance);
+
+        //octave =  tanh(v1)
+        RealVector v_mapTanhToSelf = v1.copy();
+        v_mapTanhToSelf.mapTanhToSelf();
+        double[] result_mapTanhToSelf = {7.615941559557649e-01d,9.640275800758169e-01d,9.950547536867305e-01d};
+        assertClose("compare vectors" ,result_mapTanhToSelf,v_mapTanhToSelf.getData(),normTolerance);
+
+        //octave =  cos(v1)
+        RealVector v_mapCos = v1.mapCos();
+        double[] result_mapCos = {5.403023058681398e-01d,-4.161468365471424e-01d, -9.899924966004454e-01d};
+        assertClose("compare vectors" ,result_mapCos,v_mapCos.getData(),normTolerance);
+
+        //octave =  cos(v1)
+        RealVector v_mapCosToSelf = v1.copy();
+        v_mapCosToSelf.mapCosToSelf();
+        double[] result_mapCosToSelf = {5.403023058681398e-01d,-4.161468365471424e-01d, -9.899924966004454e-01d};
+        assertClose("compare vectors" ,result_mapCosToSelf,v_mapCosToSelf.getData(),normTolerance);
+
+        //octave =  sin(v1)
+        RealVector v_mapSin = v1.mapSin();
+        double[] result_mapSin = {8.414709848078965e-01d,9.092974268256817e-01d,1.411200080598672e-01d};
+        assertClose("compare vectors" ,result_mapSin,v_mapSin.getData(),normTolerance);
+
+        //octave =  sin(v1)
+        RealVector v_mapSinToSelf = v1.copy();
+        v_mapSinToSelf.mapSinToSelf();
+        double[] result_mapSinToSelf = {8.414709848078965e-01d,9.092974268256817e-01d,1.411200080598672e-01d};
+        assertClose("compare vectors" ,result_mapSinToSelf,v_mapSinToSelf.getData(),normTolerance);
+
+        //octave =  tan(v1)
+        RealVector v_mapTan = v1.mapTan();
+        double[] result_mapTan = {1.557407724654902e+00d,-2.185039863261519e+00d,-1.425465430742778e-01d};
+        assertClose("compare vectors" ,result_mapTan,v_mapTan.getData(),normTolerance);
+
+        //octave =  tan(v1)
+        RealVector v_mapTanToSelf = v1.copy();
+        v_mapTanToSelf.mapTanToSelf();
+        double[] result_mapTanToSelf = {1.557407724654902e+00d,-2.185039863261519e+00d,-1.425465430742778e-01d};
+        assertClose("compare vectors" ,result_mapTanToSelf,v_mapTanToSelf.getData(),normTolerance);
+
+        double[] vat_a = {0d, 0.5d, 1.0d};
+        OpenMapRealVector vat = new OpenMapRealVector(vat_a);
+
+        //octave =  acos(vat)
+        RealVector v_mapAcos = vat.mapAcos();
+        double[] result_mapAcos = {1.570796326794897e+00d,1.047197551196598e+00d, 0.0d};
+        assertClose("compare vectors" ,result_mapAcos,v_mapAcos.getData(),normTolerance);
+
+        //octave =  acos(vat)
+        RealVector v_mapAcosToSelf = vat.copy();
+        v_mapAcosToSelf.mapAcosToSelf();
+        double[] result_mapAcosToSelf = {1.570796326794897e+00d,1.047197551196598e+00d, 0.0d};
+        assertClose("compare vectors" ,result_mapAcosToSelf,v_mapAcosToSelf.getData(),normTolerance);
+
+        //octave =  asin(vat)
+        RealVector v_mapAsin = vat.mapAsin();
+        double[] result_mapAsin = {0.0d,5.235987755982989e-01d,1.570796326794897e+00d};
+        assertClose("compare vectors" ,result_mapAsin,v_mapAsin.getData(),normTolerance);
+
+        //octave =  asin(vat)
+        RealVector v_mapAsinToSelf = vat.copy();
+        v_mapAsinToSelf.mapAsinToSelf();
+        double[] result_mapAsinToSelf = {0.0d,5.235987755982989e-01d,1.570796326794897e+00d};
+        assertClose("compare vectors" ,result_mapAsinToSelf,v_mapAsinToSelf.getData(),normTolerance);
+
+        //octave =  atan(vat)
+        RealVector v_mapAtan = vat.mapAtan();
+        double[] result_mapAtan = {0.0d,4.636476090008061e-01d,7.853981633974483e-01d};
+        assertClose("compare vectors" ,result_mapAtan,v_mapAtan.getData(),normTolerance);
+
+        //octave =  atan(vat)
+        RealVector v_mapAtanToSelf = vat.copy();
+        v_mapAtanToSelf.mapAtanToSelf();
+        double[] result_mapAtanToSelf = {0.0d,4.636476090008061e-01d,7.853981633974483e-01d};
+        assertClose("compare vectors" ,result_mapAtanToSelf,v_mapAtanToSelf.getData(),normTolerance);
+
+        //octave =  v1 .^-1
+        RealVector v_mapInv = v1.mapInv();
+        double[] result_mapInv = {1d,0.5d,3.333333333333333e-01d};
+        assertClose("compare vectors" ,result_mapInv,v_mapInv.getData(),normTolerance);
+
+        //octave =  v1 .^-1
+        RealVector v_mapInvToSelf = v1.copy();
+        v_mapInvToSelf.mapInvToSelf();
+        double[] result_mapInvToSelf = {1d,0.5d,3.333333333333333e-01d};
+        assertClose("compare vectors" ,result_mapInvToSelf,v_mapInvToSelf.getData(),normTolerance);
+
+        double[] abs_a = {-1.0d, 0.0d, 1.0d};
+        OpenMapRealVector abs_v = new OpenMapRealVector(abs_a);
+
+        //octave =  abs(abs_v)
+        RealVector v_mapAbs = abs_v.mapAbs();
+        double[] result_mapAbs = {1d,0d,1d};
+        assertClose("compare vectors" ,result_mapAbs,v_mapAbs.getData(),normTolerance);
+
+        //octave = abs(abs_v)
+        RealVector v_mapAbsToSelf = abs_v.copy();
+        v_mapAbsToSelf.mapAbsToSelf();
+        double[] result_mapAbsToSelf = {1d,0d,1d};
+        assertClose("compare vectors" ,result_mapAbsToSelf,v_mapAbsToSelf.getData(),normTolerance);
+
+        //octave =   sqrt(v1)
+        RealVector v_mapSqrt = v1.mapSqrt();
+        double[] result_mapSqrt = {1d,1.414213562373095e+00d,1.732050807568877e+00d};
+        assertClose("compare vectors" ,result_mapSqrt,v_mapSqrt.getData(),normTolerance);
+
+        //octave =  sqrt(v1)
+        RealVector v_mapSqrtToSelf = v1.copy();
+        v_mapSqrtToSelf.mapSqrtToSelf();
+        double[] result_mapSqrtToSelf = {1d,1.414213562373095e+00d,1.732050807568877e+00d};
+        assertClose("compare vectors" ,result_mapSqrtToSelf,v_mapSqrtToSelf.getData(),normTolerance);
+
+        double[] cbrt_a = {-2.0d, 0.0d, 2.0d};
+        OpenMapRealVector cbrt_v = new OpenMapRealVector(cbrt_a);
+
+        //octave =  ???
+        RealVector v_mapCbrt = cbrt_v.mapCbrt();
+        double[] result_mapCbrt = {-1.2599210498948732d,0d,1.2599210498948732d};
+        assertClose("compare vectors" ,result_mapCbrt,v_mapCbrt.getData(),normTolerance);
+
+        //octave = ???
+        RealVector v_mapCbrtToSelf = cbrt_v.copy();
+        v_mapCbrtToSelf.mapCbrtToSelf();
+        double[] result_mapCbrtToSelf =  {-1.2599210498948732d,0d,1.2599210498948732d};
+        assertClose("compare vectors" ,result_mapCbrtToSelf,v_mapCbrtToSelf.getData(),normTolerance);
+
+        double[] ceil_a = {-1.1d, 0.9d, 1.1d};
+        OpenMapRealVector ceil_v = new OpenMapRealVector(ceil_a);
+
+        //octave =  ceil(ceil_v)
+        RealVector v_mapCeil = ceil_v.mapCeil();
+        double[] result_mapCeil = {-1d,1d,2d};
+        assertClose("compare vectors" ,result_mapCeil,v_mapCeil.getData(),normTolerance);
+
+        //octave = ceil(ceil_v)
+        RealVector v_mapCeilToSelf = ceil_v.copy();
+        v_mapCeilToSelf.mapCeilToSelf();
+        double[] result_mapCeilToSelf =  {-1d,1d,2d};
+        assertClose("compare vectors" ,result_mapCeilToSelf,v_mapCeilToSelf.getData(),normTolerance);
+
+        //octave =  floor(ceil_v)
+        RealVector v_mapFloor = ceil_v.mapFloor();
+        double[] result_mapFloor = {-2d,0d,1d};
+        assertClose("compare vectors" ,result_mapFloor,v_mapFloor.getData(),normTolerance);
+
+        //octave = floor(ceil_v)
+        RealVector v_mapFloorToSelf = ceil_v.copy();
+        v_mapFloorToSelf.mapFloorToSelf();
+        double[] result_mapFloorToSelf =  {-2d,0d,1d};
+        assertClose("compare vectors" ,result_mapFloorToSelf,v_mapFloorToSelf.getData(),normTolerance);
+
+        //octave =  ???
+        RealVector v_mapRint = ceil_v.mapRint();
+        double[] result_mapRint = {-1d,1d,1d};
+        assertClose("compare vectors" ,result_mapRint,v_mapRint.getData(),normTolerance);
+
+        //octave = ???
+        RealVector v_mapRintToSelf = ceil_v.copy();
+        v_mapRintToSelf.mapRintToSelf();
+        double[] result_mapRintToSelf =  {-1d,1d,1d};
+        assertClose("compare vectors" ,result_mapRintToSelf,v_mapRintToSelf.getData(),normTolerance);
+
+        //octave =  ???
+        RealVector v_mapSignum = ceil_v.mapSignum();
+        double[] result_mapSignum = {-1d,1d,1d};
+        assertClose("compare vectors" ,result_mapSignum,v_mapSignum.getData(),normTolerance);
+
+        //octave = ???
+        RealVector v_mapSignumToSelf = ceil_v.copy();
+        v_mapSignumToSelf.mapSignumToSelf();
+        double[] result_mapSignumToSelf =  {-1d,1d,1d};
+        assertClose("compare vectors" ,result_mapSignumToSelf,v_mapSignumToSelf.getData(),normTolerance);
+
+
+        // Is with the used resolutions of limited value as test
+        //octave =  ???
+        RealVector v_mapUlp = ceil_v.mapUlp();
+        double[] result_mapUlp = {2.220446049250313E-16d,1.1102230246251565E-16d,2.220446049250313E-16d};
+        assertClose("compare vectors" ,result_mapUlp,v_mapUlp.getData(),normTolerance);
+
+        //octave = ???
+        RealVector v_mapUlpToSelf = ceil_v.copy();
+        v_mapUlpToSelf.mapUlpToSelf();
+        double[] result_mapUlpToSelf = {2.220446049250313E-16d,1.1102230246251565E-16d,2.220446049250313E-16d};
+        assertClose("compare vectors" ,result_mapUlpToSelf,v_mapUlpToSelf.getData(),normTolerance);
+
+    }
+
+    public void testBasicFunctions() {
+        OpenMapRealVector v1 = new OpenMapRealVector(vec1);
+        OpenMapRealVector v2 = new OpenMapRealVector(vec2);
+        OpenMapRealVector v5 = new OpenMapRealVector(vec5);
+        OpenMapRealVector v_null = new OpenMapRealVector(vec_null);
+
+        SparseRealVectorTestImpl v2_t = new SparseRealVectorTestImpl(vec2);
+
+        // emacs calc: [-4, 0, 3, 1, -6, 3] A --> 8.4261497731763586307
+        double d_getNorm = v5.getNorm();
+        assertEquals("compare values  ", 8.4261497731763586307, d_getNorm);
+
+        // emacs calc: [-4, 0, 3, 1, -6, 3] vN --> 17
+        double d_getL1Norm = v5.getL1Norm();
+        assertEquals("compare values  ", 17.0, d_getL1Norm);
+
+        // emacs calc: [-4, 0, 3, 1, -6, 3] vn --> 6
+        double d_getLInfNorm = v5.getLInfNorm();
+        assertEquals("compare values  ", 6.0, d_getLInfNorm);
+
+        //octave =  sqrt(sumsq(v1-v2))
+        double dist = v1.getDistance(v2);
+        assertEquals("compare values  ",v1.subtract(v2).getNorm(), dist );
+
+        //octave =  sqrt(sumsq(v1-v2))
+        double dist_2 = v1.getDistance(v2_t);
+        assertEquals("compare values  ", v1.subtract(v2).getNorm(),dist_2 );
+
+        //octave =  ???
+        double d_getL1Distance = v1. getL1Distance(v2);
+        assertEquals("compare values  ",9d, d_getL1Distance );
+
+        double d_getL1Distance_2 = v1. getL1Distance(v2_t);
+        assertEquals("compare values  ",9d, d_getL1Distance_2 );
+
+        //octave =  ???
+        double d_getLInfDistance = v1. getLInfDistance(v2);
+        assertEquals("compare values  ",3d, d_getLInfDistance );
+
+        double d_getLInfDistance_2 = v1. getLInfDistance(v2_t);
+        assertEquals("compare values  ",3d, d_getLInfDistance_2 );
+
+        //octave =  v1 + v2
+        OpenMapRealVector v_add = v1.add(v2);
+        double[] result_add = {5d, 7d, 9d};
+        assertClose("compare vect" ,v_add.getData(),result_add,normTolerance);
+
+        SparseRealVectorTestImpl vt2 = new SparseRealVectorTestImpl(vec2);
+        RealVector v_add_i = v1.add(vt2);
+        double[] result_add_i = {5d, 7d, 9d};
+        assertClose("compare vect" ,v_add_i.getData(),result_add_i,normTolerance);
+
+        //octave =  v1 - v2
+        OpenMapRealVector v_subtract = v1.subtract(v2);
+        double[] result_subtract = {-3d, -3d, -3d};
+        assertClose("compare vect" ,v_subtract.getData(),result_subtract,normTolerance);
+
+        RealVector v_subtract_i = v1.subtract(vt2);
+        double[] result_subtract_i = {-3d, -3d, -3d};
+        assertClose("compare vect" ,v_subtract_i.getData(),result_subtract_i,normTolerance);
+
+        // octave v1 .* v2
+        RealVector  v_ebeMultiply = v1.ebeMultiply(v2);
+        double[] result_ebeMultiply = {4d, 10d, 18d};
+        assertClose("compare vect" ,v_ebeMultiply.getData(),result_ebeMultiply,normTolerance);
+
+        RealVector  v_ebeMultiply_2 = v1.ebeMultiply(v2_t);
+        double[] result_ebeMultiply_2 = {4d, 10d, 18d};
+        assertClose("compare vect" ,v_ebeMultiply_2.getData(),result_ebeMultiply_2,normTolerance);
+
+        // octave v1 ./ v2
+        RealVector  v_ebeDivide = v1.ebeDivide(v2);
+        double[] result_ebeDivide = {0.25d, 0.4d, 0.5d};
+        assertClose("compare vect" ,v_ebeDivide.getData(),result_ebeDivide,normTolerance);
+
+        RealVector  v_ebeDivide_2 = v1.ebeDivide(v2_t);
+        double[] result_ebeDivide_2 = {0.25d, 0.4d, 0.5d};
+        assertClose("compare vect" ,v_ebeDivide_2.getData(),result_ebeDivide_2,normTolerance);
+
+        // octave  dot(v1,v2)
+        double dot =  v1.dotProduct(v2);
+        assertEquals("compare val ",32d, dot);
+
+        // octave  dot(v1,v2_t)
+        double dot_2 =  v1.dotProduct(v2_t);
+        assertEquals("compare val ",32d, dot_2);
+
+        RealMatrix m_outerProduct = v1.outerProduct(v2);
+        assertEquals("compare val ",4d, m_outerProduct.getEntry(0,0));
+
+        RealMatrix m_outerProduct_2 = v1.outerProduct(v2_t);
+        assertEquals("compare val ",4d, m_outerProduct_2.getEntry(0,0));
+
+        RealVector v_unitVector = v1.unitVector();
+        RealVector v_unitVector_2 = v1.mapDivide(v1.getNorm());
+        assertClose("compare vect" ,v_unitVector.getData(),v_unitVector_2.getData(),normTolerance);
+
+        try {
+            v_null.unitVector();
+            fail("Expecting ArithmeticException");
+        } catch (ArithmeticException ex) {
+            // expected behavior
+        }
+
+        OpenMapRealVector v_unitize = v1.copy();
+        v_unitize.unitize();
+        assertClose("compare vect" ,v_unitVector_2.getData(),v_unitize.getData(),normTolerance);
+        try {
+            v_null.unitize();
+            fail("Expecting ArithmeticException");
+        } catch (ArithmeticException ex) {
+            // expected behavior
+        }
+
+        RealVector v_projection = v1.projection(v2);
+        double[] result_projection = {1.662337662337662, 2.0779220779220777, 2.493506493506493};
+        assertClose("compare vect", v_projection.getData(), result_projection, normTolerance);
+
+        RealVector v_projection_2 = v1.projection(v2_t);
+        double[] result_projection_2 = {1.662337662337662, 2.0779220779220777, 2.493506493506493};
+        assertClose("compare vect", v_projection_2.getData(), result_projection_2, normTolerance);
+
+    }
+
+    public void testMisc() {
+        OpenMapRealVector v1 = new OpenMapRealVector(vec1);
+
+        String out1 = v1.toString();
+        assertTrue("some output ",  out1.length()!=0);
+        try {
+            v1.checkVectorDimensions(2);
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected behavior
+        }
+
+
+    }
+
+    public void testPredicates() {
+
+        OpenMapRealVector v = new OpenMapRealVector(new double[] { 0, 1, 2 });
+
+        assertFalse(v.isNaN());
+        v.setEntry(1, Double.NaN);
+        assertTrue(v.isNaN());
+
+        assertFalse(v.isInfinite());
+        v.setEntry(0, Double.POSITIVE_INFINITY);
+        assertFalse(v.isInfinite()); // NaN has higher priority than infinity
+        v.setEntry(1, 1);
+        assertTrue(v.isInfinite());
+
+        v.setEntry(0, 0);
+        assertEquals(v, new OpenMapRealVector(new double[] { 0, 1, 2 }));
+        assertNotSame(v, new OpenMapRealVector(new double[] { 0, 1, 2 + FastMath.ulp(2)}));
+        assertNotSame(v, new OpenMapRealVector(new double[] { 0, 1, 2, 3 }));
+
+    }
+
+    public void testSerial()  {
+        OpenMapRealVector v = new OpenMapRealVector(new double[] { 0, 1, 2 });
+        assertEquals(v,TestUtils.serializeAndRecover(v));
+    }
+
+    /** verifies that two vectors are close (sup norm) */
+    protected void assertClose(String msg, double[] m, double[] n,
+            double tolerance) {
+        if (m.length != n.length) {
+            fail("vectors have different lengths");
+        }
+        for (int i = 0; i < m.length; i++) {
+            assertEquals(msg + " " +  i + " elements differ", m[i],n[i],tolerance);
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/linear/TriDiagonalTransformerTest.java b/src/test/java/org/apache/commons/math/linear/TriDiagonalTransformerTest.java
new file mode 100644
index 0000000..babd0b6
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/linear/TriDiagonalTransformerTest.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.linear;
+
+import java.util.Arrays;
+
+import org.apache.commons.math.util.FastMath;
+
+import junit.framework.TestCase;
+
+public class TriDiagonalTransformerTest extends TestCase {
+
+    private double[][] testSquare5 = {
+            { 1, 2, 3, 1, 1 },
+            { 2, 1, 1, 3, 1 },
+            { 3, 1, 1, 1, 2 },
+            { 1, 3, 1, 2, 1 },
+            { 1, 1, 2, 1, 3 }
+    };
+
+    private double[][] testSquare3 = {
+            { 1, 3, 4 },
+            { 3, 2, 2 },
+            { 4, 2, 0 }
+    };
+
+    public TriDiagonalTransformerTest(String name) {
+        super(name);
+    }
+
+    public void testNonSquare() {
+        try {
+            new TriDiagonalTransformer(MatrixUtils.createRealMatrix(new double[3][2]));
+            fail("an exception should have been thrown");
+        } catch (InvalidMatrixException ime) {
+            // expected behavior
+        }
+    }
+
+    public void testAEqualQTQt() {
+        checkAEqualQTQt(MatrixUtils.createRealMatrix(testSquare5));
+        checkAEqualQTQt(MatrixUtils.createRealMatrix(testSquare3));
+    }
+
+    private void checkAEqualQTQt(RealMatrix matrix) {
+        TriDiagonalTransformer transformer = new TriDiagonalTransformer(matrix);
+        RealMatrix q  = transformer.getQ();
+        RealMatrix qT = transformer.getQT();
+        RealMatrix t  = transformer.getT();
+        double norm = q.multiply(t).multiply(qT).subtract(matrix).getNorm();
+        assertEquals(0, norm, 4.0e-15);
+    }
+
+    public void testNoAccessBelowDiagonal() {
+        checkNoAccessBelowDiagonal(testSquare5);
+        checkNoAccessBelowDiagonal(testSquare3);
+    }
+
+    private void checkNoAccessBelowDiagonal(double[][] data) {
+        double[][] modifiedData = new double[data.length][];
+        for (int i = 0; i < data.length; ++i) {
+            modifiedData[i] = data[i].clone();
+            Arrays.fill(modifiedData[i], 0, i, Double.NaN);
+        }
+        RealMatrix matrix = MatrixUtils.createRealMatrix(modifiedData);
+        TriDiagonalTransformer transformer = new TriDiagonalTransformer(matrix);
+        RealMatrix q  = transformer.getQ();
+        RealMatrix qT = transformer.getQT();
+        RealMatrix t  = transformer.getT();
+        double norm = q.multiply(t).multiply(qT).subtract(MatrixUtils.createRealMatrix(data)).getNorm();
+        assertEquals(0, norm, 4.0e-15);
+    }
+
+    public void testQOrthogonal() {
+        checkOrthogonal(new TriDiagonalTransformer(MatrixUtils.createRealMatrix(testSquare5)).getQ());
+        checkOrthogonal(new TriDiagonalTransformer(MatrixUtils.createRealMatrix(testSquare3)).getQ());
+    }
+
+    public void testQTOrthogonal() {
+        checkOrthogonal(new TriDiagonalTransformer(MatrixUtils.createRealMatrix(testSquare5)).getQT());
+        checkOrthogonal(new TriDiagonalTransformer(MatrixUtils.createRealMatrix(testSquare3)).getQT());
+    }
+
+    private void checkOrthogonal(RealMatrix m) {
+        RealMatrix mTm = m.transpose().multiply(m);
+        RealMatrix id  = MatrixUtils.createRealIdentityMatrix(mTm.getRowDimension());
+        assertEquals(0, mTm.subtract(id).getNorm(), 1.0e-15);
+    }
+
+    public void testTTriDiagonal() {
+        checkTriDiagonal(new TriDiagonalTransformer(MatrixUtils.createRealMatrix(testSquare5)).getT());
+        checkTriDiagonal(new TriDiagonalTransformer(MatrixUtils.createRealMatrix(testSquare3)).getT());
+    }
+
+    private void checkTriDiagonal(RealMatrix m) {
+        final int rows = m.getRowDimension();
+        final int cols = m.getColumnDimension();
+        for (int i = 0; i < rows; ++i) {
+            for (int j = 0; j < cols; ++j) {
+                if ((i < j - 1) || (i > j + 1)) {
+                    assertEquals(0, m.getEntry(i, j), 1.0e-16);
+                }
+            }
+        }
+    }
+
+    public void testMatricesValues5() {
+        checkMatricesValues(testSquare5,
+                            new double[][] {
+                                { 1.0,  0.0,                 0.0,                  0.0,                   0.0 },
+                                { 0.0, -0.5163977794943222,  0.016748280772542083, 0.839800693771262,     0.16669620021405473 },
+                                { 0.0, -0.7745966692414833, -0.4354553000860955,  -0.44989322880603355,  -0.08930153582895772 },
+                                { 0.0, -0.2581988897471611,  0.6364346693566014,  -0.30263204032131164,   0.6608313651342882 },
+                                { 0.0, -0.2581988897471611,  0.6364346693566009,  -0.027289660803112598, -0.7263191580755246 }
+                            },
+                            new double[] { 1, 4.4, 1.433099579242636, -0.89537362758743, 2.062274048344794 },
+                            new double[] { -FastMath.sqrt(15), -3.0832882879592476, 0.6082710842351517, 1.1786086405912128 });
+    }
+
+    public void testMatricesValues3() {
+        checkMatricesValues(testSquare3,
+                            new double[][] {
+                                {  1.0,  0.0,  0.0 },
+                                {  0.0, -0.6,  0.8 },
+                                {  0.0, -0.8, -0.6 },
+                            },
+                            new double[] { 1, 2.64, -0.64 },
+                            new double[] { -5, -1.52 });
+    }
+
+    private void checkMatricesValues(double[][] matrix, double[][] qRef,
+                                     double[] mainDiagnonal,
+                                     double[] secondaryDiagonal) {
+        TriDiagonalTransformer transformer =
+            new TriDiagonalTransformer(MatrixUtils.createRealMatrix(matrix));
+
+        // check values against known references
+        RealMatrix q = transformer.getQ();
+        assertEquals(0, q.subtract(MatrixUtils.createRealMatrix(qRef)).getNorm(), 1.0e-14);
+
+        RealMatrix t = transformer.getT();
+        double[][] tData = new double[mainDiagnonal.length][mainDiagnonal.length];
+        for (int i = 0; i < mainDiagnonal.length; ++i) {
+            tData[i][i] = mainDiagnonal[i];
+            if (i > 0) {
+                tData[i][i - 1] = secondaryDiagonal[i - 1];
+            }
+            if (i < secondaryDiagonal.length) {
+                tData[i][i + 1] = secondaryDiagonal[i];
+            }
+        }
+        assertEquals(0, t.subtract(MatrixUtils.createRealMatrix(tData)).getNorm(), 1.0e-14);
+
+        // check the same cached instance is returned the second time
+        assertTrue(q == transformer.getQ());
+        assertTrue(t == transformer.getT());
+
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/ode/ContinuousOutputModelTest.java b/src/test/java/org/apache/commons/math/ode/ContinuousOutputModelTest.java
new file mode 100644
index 0000000..dbaf2d0
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/ContinuousOutputModelTest.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;
+
+import junit.framework.*;
+import java.util.Random;
+
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.ode.ContinuousOutputModel;
+import org.apache.commons.math.ode.FirstOrderIntegrator;
+import org.apache.commons.math.ode.IntegratorException;
+import org.apache.commons.math.ode.nonstiff.DormandPrince54Integrator;
+import org.apache.commons.math.ode.nonstiff.DormandPrince853Integrator;
+import org.apache.commons.math.ode.sampling.DummyStepInterpolator;
+import org.apache.commons.math.ode.sampling.StepInterpolator;
+import org.apache.commons.math.util.FastMath;
+
+public class ContinuousOutputModelTest
+  extends TestCase {
+
+  public ContinuousOutputModelTest(String name) {
+    super(name);
+    pb    = null;
+    integ = null;
+  }
+
+  public void testBoundaries()
+    throws DerivativeException, IntegratorException {
+    integ.addStepHandler(new ContinuousOutputModel());
+    integ.integrate(pb,
+                    pb.getInitialTime(), pb.getInitialState(),
+                    pb.getFinalTime(), new double[pb.getDimension()]);
+    ContinuousOutputModel cm = (ContinuousOutputModel) integ.getStepHandlers().iterator().next();
+    cm.setInterpolatedTime(2.0 * pb.getInitialTime() - pb.getFinalTime());
+    cm.setInterpolatedTime(2.0 * pb.getFinalTime() - pb.getInitialTime());
+    cm.setInterpolatedTime(0.5 * (pb.getFinalTime() + pb.getInitialTime()));
+  }
+
+  public void testRandomAccess()
+    throws DerivativeException, IntegratorException {
+
+    ContinuousOutputModel cm = new ContinuousOutputModel();
+    integ.addStepHandler(cm);
+    integ.integrate(pb,
+                    pb.getInitialTime(), pb.getInitialState(),
+                    pb.getFinalTime(), new double[pb.getDimension()]);
+
+    Random random = new Random(347588535632l);
+    double maxError = 0.0;
+    for (int i = 0; i < 1000; ++i) {
+      double r = random.nextDouble();
+      double time = r * pb.getInitialTime() + (1.0 - r) * pb.getFinalTime();
+      cm.setInterpolatedTime(time);
+      double[] interpolatedY = cm.getInterpolatedState ();
+      double[] theoreticalY  = pb.computeTheoreticalState(time);
+      double dx = interpolatedY[0] - theoreticalY[0];
+      double dy = interpolatedY[1] - theoreticalY[1];
+      double error = dx * dx + dy * dy;
+      if (error > maxError) {
+        maxError = error;
+      }
+    }
+
+    assertTrue(maxError < 1.0e-9);
+
+  }
+
+  public void testModelsMerging()
+    throws DerivativeException, IntegratorException {
+
+      // theoretical solution: y[0] = cos(t), y[1] = sin(t)
+      FirstOrderDifferentialEquations problem =
+          new FirstOrderDifferentialEquations() {
+              private static final long serialVersionUID = 2472449657345878299L;
+              public void computeDerivatives(double t, double[] y, double[] dot)
+                  throws DerivativeException {
+                  dot[0] = -y[1];
+                  dot[1] =  y[0];
+              }
+              public int getDimension() {
+                  return 2;
+              }
+          };
+
+      // integrate backward from π to 0;
+      ContinuousOutputModel cm1 = new ContinuousOutputModel();
+      FirstOrderIntegrator integ1 =
+          new DormandPrince853Integrator(0, 1.0, 1.0e-8, 1.0e-8);
+      integ1.addStepHandler(cm1);
+      integ1.integrate(problem, FastMath.PI, new double[] { -1.0, 0.0 },
+                       0, new double[2]);
+
+      // integrate backward from 2π to π
+      ContinuousOutputModel cm2 = new ContinuousOutputModel();
+      FirstOrderIntegrator integ2 =
+          new DormandPrince853Integrator(0, 0.1, 1.0e-12, 1.0e-12);
+      integ2.addStepHandler(cm2);
+      integ2.integrate(problem, 2.0 * FastMath.PI, new double[] { 1.0, 0.0 },
+                       FastMath.PI, new double[2]);
+
+      // merge the two half circles
+      ContinuousOutputModel cm = new ContinuousOutputModel();
+      cm.append(cm2);
+      cm.append(new ContinuousOutputModel());
+      cm.append(cm1);
+
+      // check circle
+      assertEquals(2.0 * FastMath.PI, cm.getInitialTime(), 1.0e-12);
+      assertEquals(0, cm.getFinalTime(), 1.0e-12);
+      assertEquals(cm.getFinalTime(), cm.getInterpolatedTime(), 1.0e-12);
+      for (double t = 0; t < 2.0 * FastMath.PI; t += 0.1) {
+          cm.setInterpolatedTime(t);
+          double[] y = cm.getInterpolatedState();
+          assertEquals(FastMath.cos(t), y[0], 1.0e-7);
+          assertEquals(FastMath.sin(t), y[1], 1.0e-7);
+      }
+
+  }
+
+  public void testErrorConditions()
+    throws DerivativeException {
+
+      ContinuousOutputModel cm = new ContinuousOutputModel();
+      cm.handleStep(buildInterpolator(0, new double[] { 0.0, 1.0, -2.0 }, 1), true);
+
+      // dimension mismatch
+      assertTrue(checkAppendError(cm, 1.0, new double[] { 0.0, 1.0 }, 2.0));
+
+      // hole between time ranges
+      assertTrue(checkAppendError(cm, 10.0, new double[] { 0.0, 1.0, -2.0 }, 20.0));
+
+      // propagation direction mismatch
+      assertTrue(checkAppendError(cm, 1.0, new double[] { 0.0, 1.0, -2.0 }, 0.0));
+
+      // no errors
+      assertFalse(checkAppendError(cm, 1.0, new double[] { 0.0, 1.0, -2.0 }, 2.0));
+
+  }
+
+  private boolean checkAppendError(ContinuousOutputModel cm,
+                                   double t0, double[] y0, double t1)
+  throws DerivativeException {
+      try {
+          ContinuousOutputModel otherCm = new ContinuousOutputModel();
+          otherCm.handleStep(buildInterpolator(t0, y0, t1), true);
+          cm.append(otherCm);
+      } catch(IllegalArgumentException iae) {
+          return true; // there was an allowable error
+      }
+      return false; // no allowable error
+  }
+
+  private StepInterpolator buildInterpolator(double t0, double[] y0, double t1) {
+      DummyStepInterpolator interpolator  = new DummyStepInterpolator(y0, new double[y0.length], t1 >= t0);
+      interpolator.storeTime(t0);
+      interpolator.shift();
+      interpolator.storeTime(t1);
+      return interpolator;
+  }
+
+  public void checkValue(double value, double reference) {
+    assertTrue(FastMath.abs(value - reference) < 1.0e-10);
+  }
+
+  @Override
+  public void setUp() {
+    pb = new TestProblem3(0.9);
+    double minStep = 0;
+    double maxStep = pb.getFinalTime() - pb.getInitialTime();
+    integ = new DormandPrince54Integrator(minStep, maxStep, 1.0e-8, 1.0e-8);
+  }
+
+  @Override
+  public void tearDown() {
+    pb    = null;
+    integ = null;
+  }
+
+  TestProblem3 pb;
+  FirstOrderIntegrator integ;
+
+}
diff --git a/src/test/java/org/apache/commons/math/ode/FirstOrderConverterTest.java b/src/test/java/org/apache/commons/math/ode/FirstOrderConverterTest.java
new file mode 100644
index 0000000..819e1b0
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/FirstOrderConverterTest.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.ode;
+
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.ode.FirstOrderConverter;
+import org.apache.commons.math.ode.IntegratorException;
+import org.apache.commons.math.ode.SecondOrderDifferentialEquations;
+import org.apache.commons.math.ode.nonstiff.ClassicalRungeKuttaIntegrator;
+import org.apache.commons.math.util.FastMath;
+
+import junit.framework.*;
+
+public class FirstOrderConverterTest
+  extends TestCase {
+
+  public FirstOrderConverterTest(String name) {
+    super(name);
+  }
+
+  public void testDoubleDimension() {
+    for (int i = 1; i < 10; ++i) {
+      SecondOrderDifferentialEquations eqn2 = new Equations(i, 0.2);
+      FirstOrderConverter eqn1 = new FirstOrderConverter(eqn2);
+      assertTrue(eqn1.getDimension() == (2 * eqn2.getDimension()));
+    }
+  }
+
+  public void testDecreasingSteps()
+    throws DerivativeException, IntegratorException {
+
+    double previousError = Double.NaN;
+    for (int i = 0; i < 10; ++i) {
+
+      double step  = FastMath.pow(2.0, -(i + 1));
+      double error = integrateWithSpecifiedStep(4.0, 0.0, 1.0, step)
+                   - FastMath.sin(4.0);
+      if (i > 0) {
+        assertTrue(FastMath.abs(error) < FastMath.abs(previousError));
+      }
+      previousError = error;
+
+    }
+  }
+
+  public void testSmallStep()
+    throws DerivativeException, IntegratorException {
+    double error = integrateWithSpecifiedStep(4.0, 0.0, 1.0, 1.0e-4)
+                   - FastMath.sin(4.0);
+    assertTrue(FastMath.abs(error) < 1.0e-10);
+  }
+
+  public void testBigStep()
+    throws DerivativeException, IntegratorException {
+    double error = integrateWithSpecifiedStep(4.0, 0.0, 1.0, 0.5)
+                   - FastMath.sin(4.0);
+    assertTrue(FastMath.abs(error) > 0.1);
+  }
+
+  private static class Equations
+    implements SecondOrderDifferentialEquations {
+
+     private int n;
+
+      private double omega2;
+
+      public Equations(int n, double omega) {
+        this.n = n;
+        omega2 = omega * omega;
+      }
+
+      public int getDimension() {
+        return n;
+      }
+
+      public void computeSecondDerivatives(double t, double[] y, double[] yDot,
+                                           double[] yDDot) {
+        for (int i = 0; i < n; ++i) {
+          yDDot[i] = -omega2 * y[i];
+        }
+    }
+
+  }
+
+  private double integrateWithSpecifiedStep(double omega,
+                                            double t0, double t,
+                                            double step)
+  throws DerivativeException, IntegratorException {
+    double[] y0 = new double[2];
+    y0[0] = FastMath.sin(omega * t0);
+    y0[1] = omega * FastMath.cos(omega * t0);
+    ClassicalRungeKuttaIntegrator i = new ClassicalRungeKuttaIntegrator(step);
+    double[] y = new double[2];
+    i.integrate(new FirstOrderConverter(new Equations(1, omega)), t0, y0, t, y);
+    return y[0];
+  }
+
+}
diff --git a/src/test/java/org/apache/commons/math/ode/TestProblem1.java b/src/test/java/org/apache/commons/math/ode/TestProblem1.java
new file mode 100644
index 0000000..fc8a381
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/TestProblem1.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;
+
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * This class is used in the junit tests for the ODE integrators.
+
+ * <p>This specific problem is the following differential equation :
+ * <pre>
+ *    y' = -y
+ * </pre>
+ * the solution of this equation is a simple exponential function :
+ * <pre>
+ *   y (t) = y (t0) exp (t0-t)
+ * </pre>
+ * </p>
+
+ */
+public class TestProblem1
+  extends TestProblemAbstract {
+
+  /** Serializable version identifier. */
+  private static final long serialVersionUID = 1977870815289373164L;
+
+  /** theoretical state */
+  private double[] y;
+
+  /**
+   * Simple constructor.
+   */
+  public TestProblem1() {
+    super();
+    double[] y0 = { 1.0, 0.1 };
+    setInitialConditions(0.0, y0);
+    setFinalConditions(4.0);
+    double[] errorScale = { 1.0, 1.0 };
+    setErrorScale(errorScale);
+    y = new double[y0.length];
+  }
+
+  /**
+   * Copy constructor.
+   * @param problem problem to copy
+   */
+  public TestProblem1(TestProblem1 problem) {
+    super(problem);
+    y = problem.y.clone();
+  }
+
+  /** {@inheritDoc} */
+  @Override
+public TestProblem1 copy() {
+    return new TestProblem1(this);
+  }
+
+  @Override
+  public void doComputeDerivatives(double t, double[] y, double[] yDot) {
+
+    // compute the derivatives
+    for (int i = 0; i < n; ++i)
+      yDot[i] = -y[i];
+
+  }
+
+  @Override
+  public double[] computeTheoreticalState(double t) {
+    double c = FastMath.exp (t0 - t);
+    for (int i = 0; i < n; ++i) {
+      y[i] = c * y0[i];
+    }
+    return y;
+  }
+
+}
diff --git a/src/test/java/org/apache/commons/math/ode/TestProblem2.java b/src/test/java/org/apache/commons/math/ode/TestProblem2.java
new file mode 100644
index 0000000..c506005
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/TestProblem2.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.ode;
+
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * This class is used in the junit tests for the ODE integrators.
+
+ * <p>This specific problem is the following differential equation :
+ * <pre>
+ *    y' = t^3 - t y
+ * </pre>
+ * with the initial condition y (0) = 0. The solution of this equation
+ * is the following function :
+ * <pre>
+ *   y (t) = t^2 + 2 (exp (- t^2 / 2) - 1)
+ * </pre>
+ * </p>
+
+ */
+public class TestProblem2
+  extends TestProblemAbstract {
+
+  /** Serializable version identifier. */
+  private static final long serialVersionUID = 8330741783213512366L;
+
+  /** theoretical state */
+  private double[] y;
+
+  /**
+   * Simple constructor.
+   */
+  public TestProblem2() {
+    super();
+    double[] y0 = { 0.0 };
+    setInitialConditions(0.0, y0);
+    setFinalConditions(1.0);
+    double[] errorScale = { 1.0 };
+    setErrorScale(errorScale);
+    y = new double[y0.length];
+  }
+
+  /**
+   * Copy constructor.
+   * @param problem problem to copy
+   */
+  public TestProblem2(TestProblem2 problem) {
+    super(problem);
+    y = problem.y.clone();
+  }
+
+  /** {@inheritDoc} */
+  @Override
+public TestProblem2 copy() {
+    return new TestProblem2(this);
+  }
+
+  @Override
+  public void doComputeDerivatives(double t, double[] y, double[] yDot) {
+
+    // compute the derivatives
+    for (int i = 0; i < n; ++i)
+      yDot[i] = t * (t * t - y[i]);
+
+  }
+
+  @Override
+  public double[] computeTheoreticalState(double t) {
+    double t2 = t * t;
+    double c = t2 + 2 * (FastMath.exp (-0.5 * t2) - 1);
+    for (int i = 0; i < n; ++i) {
+      y[i] = c;
+    }
+    return y;
+  }
+
+}
diff --git a/src/test/java/org/apache/commons/math/ode/TestProblem3.java b/src/test/java/org/apache/commons/math/ode/TestProblem3.java
new file mode 100644
index 0000000..cebdf9d
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/TestProblem3.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;
+
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * This class is used in the junit tests for the ODE integrators.
+
+ * <p>This specific problem is the following differential equation :
+ * <pre>
+ *    y1'' = -y1/r^3  y1 (0) = 1-e  y1' (0) = 0
+ *    y2'' = -y2/r^3  y2 (0) = 0    y2' (0) =sqrt((1+e)/(1-e))
+ *    r = sqrt (y1^2 + y2^2), e = 0.9
+ * </pre>
+ * This is a two-body problem in the plane which can be solved by
+ * Kepler's equation
+ * <pre>
+ *   y1 (t) = ...
+ * </pre>
+ * </p>
+
+ */
+public class TestProblem3
+  extends TestProblemAbstract {
+
+  /** Serializable version identifier. */
+  private static final long serialVersionUID = 8567328542728919999L;
+
+  /** Eccentricity */
+  double e;
+
+  /** theoretical state */
+  private double[] y;
+
+  /**
+   * Simple constructor.
+   * @param e eccentricity
+   */
+  public TestProblem3(double e) {
+    super();
+    this.e = e;
+    double[] y0 = { 1 - e, 0, 0, FastMath.sqrt((1+e)/(1-e)) };
+    setInitialConditions(0.0, y0);
+    setFinalConditions(20.0);
+    double[] errorScale = { 1.0, 1.0, 1.0, 1.0 };
+    setErrorScale(errorScale);
+    y = new double[y0.length];
+  }
+
+  /**
+   * Simple constructor.
+   */
+  public TestProblem3() {
+    this(0.1);
+  }
+
+  /**
+   * Copy constructor.
+   * @param problem problem to copy
+   */
+  public TestProblem3(TestProblem3 problem) {
+    super(problem);
+    e = problem.e;
+    y = problem.y.clone();
+  }
+
+  /** {@inheritDoc} */
+  @Override
+public TestProblem3 copy() {
+    return new TestProblem3(this);
+  }
+
+  @Override
+  public void doComputeDerivatives(double t, double[] y, double[] yDot) {
+
+    // current radius
+    double r2 = y[0] * y[0] + y[1] * y[1];
+    double invR3 = 1 / (r2 * FastMath.sqrt(r2));
+
+    // compute the derivatives
+    yDot[0] = y[2];
+    yDot[1] = y[3];
+    yDot[2] = -invR3  * y[0];
+    yDot[3] = -invR3  * y[1];
+
+  }
+
+  @Override
+  public double[] computeTheoreticalState(double t) {
+
+    // solve Kepler's equation
+    double E = t;
+    double d = 0;
+    double corr = 999.0;
+    for (int i = 0; (i < 50) && (FastMath.abs(corr) > 1.0e-12); ++i) {
+      double f2  = e * FastMath.sin(E);
+      double f0  = d - f2;
+      double f1  = 1 - e * FastMath.cos(E);
+      double f12 = f1 + f1;
+      corr  = f0 * f12 / (f1 * f12 - f0 * f2);
+      d -= corr;
+      E = t + d;
+    }
+
+    double cosE = FastMath.cos(E);
+    double sinE = FastMath.sin(E);
+
+    y[0] = cosE - e;
+    y[1] = FastMath.sqrt(1 - e * e) * sinE;
+    y[2] = -sinE / (1 - e * cosE);
+    y[3] = FastMath.sqrt(1 - e * e) * cosE / (1 - e * cosE);
+
+    return y;
+  }
+
+}
diff --git a/src/test/java/org/apache/commons/math/ode/TestProblem4.java b/src/test/java/org/apache/commons/math/ode/TestProblem4.java
new file mode 100644
index 0000000..b2f5e56
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/TestProblem4.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.ode;
+
+import org.apache.commons.math.ode.events.EventHandler;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * This class is used in the junit tests for the ODE integrators.
+
+ * <p>This specific problem is the following differential equation :
+ * <pre>
+ *    x'' = -x
+ * </pre>
+ * And when x decreases down to 0, the state should be changed as follows :
+ * <pre>
+ *   x' -> -x'
+ * </pre>
+ * The theoretical solution of this problem is x = |sin(t+a)|
+ * </p>
+
+ */
+public class TestProblem4
+  extends TestProblemAbstract {
+
+  /** Serializable version identifier. */
+  private static final long serialVersionUID = -5910438521889015745L;
+
+  /** Time offset. */
+  private double a;
+
+  /** theoretical state */
+  private double[] y;
+
+  /** Simple constructor. */
+  public TestProblem4() {
+    super();
+    a = 1.2;
+    double[] y0 = { FastMath.sin(a), FastMath.cos(a) };
+    setInitialConditions(0.0, y0);
+    setFinalConditions(15);
+    double[] errorScale = { 1.0, 0.0 };
+    setErrorScale(errorScale);
+    y = new double[y0.length];
+  }
+
+  /**
+   * Copy constructor.
+   * @param problem problem to copy
+   */
+  public TestProblem4(TestProblem4 problem) {
+    super(problem);
+    a = problem.a;
+    y = problem.y.clone();
+  }
+
+  /** {@inheritDoc} */
+  @Override
+public TestProblem4 copy() {
+    return new TestProblem4(this);
+  }
+
+  @Override
+  public EventHandler[] getEventsHandlers() {
+    return new EventHandler[] { new Bounce(), new Stop() };
+  }
+
+  /**
+   * Get the theoretical events times.
+   * @return theoretical events times
+   */
+  @Override
+  public double[] getTheoreticalEventsTimes() {
+      return new double[] {
+          1 * FastMath.PI - a,
+          2 * FastMath.PI - a,
+          3 * FastMath.PI - a,
+          4 * FastMath.PI - a,
+          12.0
+      };
+  }
+
+  @Override
+  public void doComputeDerivatives(double t, double[] y, double[] yDot) {
+    yDot[0] =  y[1];
+    yDot[1] = -y[0];
+  }
+
+  @Override
+  public double[] computeTheoreticalState(double t) {
+    double sin = FastMath.sin(t + a);
+    double cos = FastMath.cos(t + a);
+    y[0] = FastMath.abs(sin);
+    y[1] = (sin >= 0) ? cos : -cos;
+    return y;
+  }
+
+  private static class Bounce implements EventHandler {
+
+    private static final long serialVersionUID = 1356097180027801200L;
+    private int sign;
+
+    public Bounce() {
+      sign = +1;
+    }
+
+    public double g(double t, double[] y) {
+      return sign * y[0];
+    }
+
+    public int eventOccurred(double t, double[] y, boolean increasing) {
+      // this sign change is needed because the state will be reset soon
+      sign = -sign;
+      return EventHandler.RESET_STATE;
+    }
+
+    public void resetState(double t, double[] y) {
+      y[0] = -y[0];
+      y[1] = -y[1];
+    }
+
+  }
+
+  private static class Stop implements EventHandler {
+
+    private static final long serialVersionUID = 6975050568227951931L;
+
+    public Stop() {
+    }
+
+    public double g(double t, double[] y) {
+      return t - 12.0;
+    }
+
+    public int eventOccurred(double t, double[] y, boolean increasing) {
+      return EventHandler.STOP;
+    }
+
+    public void resetState(double t, double[] y) {
+    }
+
+  }
+
+}
diff --git a/src/test/java/org/apache/commons/math/ode/TestProblem5.java b/src/test/java/org/apache/commons/math/ode/TestProblem5.java
new file mode 100644
index 0000000..8a53941
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/TestProblem5.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.ode;
+
+/**
+ * This class is used in the junit tests for the ODE integrators.
+ * <p>This is the same as problem 1 except integration is done
+ * backward in time</p>
+ */
+public class TestProblem5
+  extends TestProblem1 {
+
+  /** Serializable version identifier. */
+  private static final long serialVersionUID = 7579233102411804237L;
+
+  /**
+   * Simple constructor.
+   */
+  public TestProblem5() {
+    super();
+    setFinalConditions(2 * t0 - t1);
+  }
+
+  /** {@inheritDoc} */
+  @Override
+  public TestProblem5 copy() {
+    return new TestProblem5();
+  }
+}
diff --git a/src/test/java/org/apache/commons/math/ode/TestProblem6.java b/src/test/java/org/apache/commons/math/ode/TestProblem6.java
new file mode 100644
index 0000000..32fcfda
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/TestProblem6.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.ode;
+
+/**
+ * This class is used in the junit tests for the ODE integrators.
+
+ * <p>This specific problem is the following differential equation :
+ * <pre>
+ *    y' = 3x^5 - y
+ * </pre>
+ * when the initial condition is y(0) = -360, the solution of this
+ * equation degenerates to a simple quintic polynomial function :
+ * <pre>
+ *   y (t) = 3x^5 - 15x^4 + 60x^3 - 180x^2 + 360x - 360
+ * </pre>
+ * </p>
+
+ */
+public class TestProblem6
+  extends TestProblemAbstract {
+
+    /** Serializable version identifier. */
+    private static final long serialVersionUID = 1353409119804352378L;
+
+    /** theoretical state */
+    private double[] y;
+
+    /**
+     * Simple constructor.
+     */
+    public TestProblem6() {
+        super();
+        double[] y0 = { -360.0 };
+        setInitialConditions(0.0, y0);
+        setFinalConditions(1.0);
+        double[] errorScale = { 1.0 };
+        setErrorScale(errorScale);
+        y = new double[y0.length];
+    }
+
+    /**
+     * Copy constructor.
+     * @param problem problem to copy
+     */
+    public TestProblem6(TestProblem6 problem) {
+        super(problem);
+        y = problem.y.clone();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public TestProblem6 copy() {
+      return new TestProblem6(this);
+    }
+
+    @Override
+    public void doComputeDerivatives(double t, double[] y, double[] yDot) {
+
+        // compute the derivatives
+        double t2 = t  * t;
+        double t4 = t2 * t2;
+        double t5 = t4 * t;
+        for (int i = 0; i < n; ++i) {
+            yDot[i] = 3 * t5 - y[i];
+        }
+
+    }
+
+    @Override
+    public double[] computeTheoreticalState(double t) {
+        for (int i = 0; i < n; ++i) {
+            y[i] = ((((3 * t - 15) * t + 60) * t - 180) * t + 360) * t - 360;
+        }
+        return y;
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/ode/TestProblemAbstract.java b/src/test/java/org/apache/commons/math/ode/TestProblemAbstract.java
new file mode 100644
index 0000000..f6492ce
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/TestProblemAbstract.java
@@ -0,0 +1,191 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.ode;
+
+import org.apache.commons.math.ode.FirstOrderDifferentialEquations;
+import org.apache.commons.math.ode.events.EventHandler;
+
+/**
+ * This class is used as the base class of the problems that are
+ * integrated during the junit tests for the ODE integrators.
+ */
+public abstract class TestProblemAbstract
+  implements FirstOrderDifferentialEquations {
+
+  /** Serializable version identifier. */
+  private static final long serialVersionUID = -8521928974502839379L;
+
+  /** Dimension of the problem. */
+  protected int n;
+
+  /** Number of functions calls. */
+  protected int calls;
+
+  /** Initial time */
+  protected double t0;
+
+  /** Initial state */
+  protected double[] y0;
+
+  /** Final time */
+  protected double t1;
+
+  /** Error scale */
+  protected double[] errorScale;
+
+  /**
+   * Simple constructor.
+   */
+  protected TestProblemAbstract() {
+    n          = 0;
+    calls      = 0;
+    t0         = 0;
+    y0         = null;
+    t1         = 0;
+    errorScale = null;
+  }
+
+  /**
+   * Copy constructor.
+   * @param problem problem to copy
+   */
+  protected TestProblemAbstract(TestProblemAbstract problem) {
+    n     = problem.n;
+    calls = problem.calls;
+    t0    = problem.t0;
+    if (problem.y0 == null) {
+      y0 = null;
+    } else {
+      y0 = problem.y0.clone();
+    }
+    if (problem.errorScale == null) {
+      errorScale = null;
+    } else {
+      errorScale = problem.errorScale.clone();
+    }
+    t1 = problem.t1;
+  }
+
+  /**
+   * Copy operation.
+   * @return a copy of the instance
+   */
+  public abstract TestProblemAbstract copy();
+
+  /**
+   * Set the initial conditions
+   * @param t0 initial time
+   * @param y0 initial state vector
+   */
+  protected void setInitialConditions(double t0, double[] y0) {
+    calls     = 0;
+    n         = y0.length;
+    this.t0   = t0;
+    this.y0   = y0.clone();
+   }
+
+  /**
+   * Set the final conditions.
+   * @param t1 final time
+   */
+  protected void setFinalConditions(double t1) {
+    this.t1 = t1;
+  }
+
+  /**
+   * Set the error scale
+   * @param errorScale error scale
+   */
+  protected void setErrorScale(double[] errorScale) {
+    this.errorScale = errorScale.clone();
+  }
+
+  public int getDimension() {
+    return n;
+  }
+
+  /**
+   * Get the initial time.
+   * @return initial time
+   */
+  public double getInitialTime() {
+    return t0;
+  }
+
+  /**
+   * Get the initial state vector.
+   * @return initial state vector
+   */
+  public double[] getInitialState() {
+    return y0;
+  }
+
+  /**
+   * Get the final time.
+   * @return final time
+   */
+  public double getFinalTime() {
+    return t1;
+  }
+
+  /**
+   * Get the error scale.
+   * @return error scale
+   */
+  public double[] getErrorScale() {
+    return errorScale;
+  }
+
+  /**
+   * Get the events handlers.
+   * @return events handlers   */
+  public EventHandler[] getEventsHandlers() {
+    return new EventHandler[0];
+  }
+
+  /**
+   * Get the theoretical events times.
+   * @return theoretical events times
+   */
+  public double[] getTheoreticalEventsTimes() {
+      return new double[0];
+  }
+
+  /**
+   * Get the number of calls.
+   * @return nuber of calls
+   */
+  public int getCalls() {
+    return calls;
+  }
+
+  public void computeDerivatives(double t, double[] y, double[] yDot) {
+    ++calls;
+    doComputeDerivatives(t, y, yDot);
+  }
+
+  abstract public void doComputeDerivatives(double t, double[] y, double[] yDot);
+
+  /**
+   * Compute the theoretical state at the specified time.
+   * @param t time at which the state is required
+   * @return state vector at time t
+   */
+  abstract public double[] computeTheoreticalState(double t);
+
+}
diff --git a/src/test/java/org/apache/commons/math/ode/TestProblemFactory.java b/src/test/java/org/apache/commons/math/ode/TestProblemFactory.java
new file mode 100644
index 0000000..c9584c8
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/TestProblemFactory.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.ode;
+
+/**
+ * This class is used in the junit tests for the ODE integrators.
+ */
+public class TestProblemFactory {
+
+  /** Problems pool. */
+  private static final TestProblemAbstract[] pool = {
+    new TestProblem1(),
+    new TestProblem2(),
+    new TestProblem3(),
+    new TestProblem4(),
+    new TestProblem5(),
+    new TestProblem6()
+  };
+
+  /**
+   * Private constructor.
+   * This is a utility class, so there are no instance at all.
+   */
+  private TestProblemFactory() {
+  }
+
+  /**
+   * Get the problems.
+   * @return array of problems to solve
+   */
+  public static TestProblemAbstract[] getProblems() {
+    return pool;
+  }
+
+}
diff --git a/src/test/java/org/apache/commons/math/ode/TestProblemHandler.java b/src/test/java/org/apache/commons/math/ode/TestProblemHandler.java
new file mode 100644
index 0000000..0618b89
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/TestProblemHandler.java
@@ -0,0 +1,157 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.ode;
+
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.ode.ODEIntegrator;
+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 used to handle steps for the test problems
+ * integrated during the junit tests for the ODE integrators.
+ */
+public class TestProblemHandler
+  implements StepHandler {
+
+  /** Associated problem. */
+  private TestProblemAbstract problem;
+
+  /** Maximal errors encountered during the integration. */
+  private double maxValueError;
+  private double maxTimeError;
+
+  /** Error at the end of the integration. */
+  private double lastError;
+
+  /** Time at the end of integration. */
+  private double lastTime;
+
+  /** ODE solver used. */
+  private ODEIntegrator integrator;
+
+  /** Expected start for step. */
+  private double expectedStepStart;
+
+  /**
+   * Simple constructor.
+   * @param problem problem for which steps should be handled
+   * @param integrator ODE solver used
+   */
+  public TestProblemHandler(TestProblemAbstract problem, ODEIntegrator integrator) {
+    this.problem = problem;
+    this.integrator = integrator;
+    reset();
+  }
+
+  public boolean requiresDenseOutput() {
+    return true;
+  }
+
+  public void reset() {
+    maxValueError = 0;
+    maxTimeError  = 0;
+    lastError     = 0;
+    expectedStepStart = Double.NaN;
+  }
+
+  public void handleStep(StepInterpolator interpolator,
+                         boolean isLast)
+    throws DerivativeException {
+
+    double start = integrator.getCurrentStepStart();
+    if (FastMath.abs((start - problem.getInitialTime()) / integrator.getCurrentSignedStepsize()) > 0.001) {
+        // multistep integrators do not handle the first steps themselves
+        // so we have to make sure the integrator we look at has really started its work
+        if (!Double.isNaN(expectedStepStart)) {
+            // the step should either start at the end of the integrator step
+            // or at an event if the step is split into several substeps
+            double stepError = FastMath.max(maxTimeError, FastMath.abs(start - expectedStepStart));
+            for (double eventTime : problem.getTheoreticalEventsTimes()) {
+                stepError = FastMath.min(stepError, FastMath.abs(start - eventTime));
+            }
+            maxTimeError = FastMath.max(maxTimeError, stepError);
+        }
+        expectedStepStart = start + integrator.getCurrentSignedStepsize();
+    }
+
+    double pT = interpolator.getPreviousTime();
+    double cT = interpolator.getCurrentTime();
+    double[] errorScale = problem.getErrorScale();
+
+    // store the error at the last step
+    if (isLast) {
+      double[] interpolatedY = interpolator.getInterpolatedState();
+      double[] theoreticalY  = problem.computeTheoreticalState(cT);
+      for (int i = 0; i < interpolatedY.length; ++i) {
+        double error = FastMath.abs(interpolatedY[i] - theoreticalY[i]);
+        lastError = FastMath.max(error, lastError);
+      }
+      lastTime = cT;
+    }
+
+    // walk through the step
+    for (int k = 0; k <= 20; ++k) {
+
+      double time = pT + (k * (cT - pT)) / 20;
+      interpolator.setInterpolatedTime(time);
+      double[] interpolatedY = interpolator.getInterpolatedState();
+      double[] theoreticalY  = problem.computeTheoreticalState(interpolator.getInterpolatedTime());
+
+      // update the errors
+      for (int i = 0; i < interpolatedY.length; ++i) {
+        double error = errorScale[i] * FastMath.abs(interpolatedY[i] - theoreticalY[i]);
+        maxValueError = FastMath.max(error, maxValueError);
+      }
+    }
+  }
+
+  /**
+   * Get the maximal value error encountered during integration.
+   * @return maximal value error
+   */
+  public double getMaximalValueError() {
+    return maxValueError;
+  }
+
+  /**
+   * Get the maximal time error encountered during integration.
+   * @return maximal time error
+   */
+  public double getMaximalTimeError() {
+    return maxTimeError;
+  }
+
+  /**
+   * Get the error at the end of the integration.
+   * @return error at the end of the integration
+   */
+  public double getLastError() {
+    return lastError;
+  }
+
+  /**
+   * Get the time at the end of the integration.
+   * @return time at the end of the integration.
+   */
+  public double getLastTime() {
+    return lastTime;
+  }
+
+}
diff --git a/src/test/java/org/apache/commons/math/ode/events/EventStateTest.java b/src/test/java/org/apache/commons/math/ode/events/EventStateTest.java
new file mode 100644
index 0000000..dff6d6b
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/events/EventStateTest.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.ode.events;
+
+import junit.framework.Assert;
+
+import org.apache.commons.math.ConvergenceException;
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.ode.sampling.AbstractStepInterpolator;
+import org.apache.commons.math.ode.sampling.DummyStepInterpolator;
+import org.junit.Test;
+
+public class EventStateTest {
+
+    // JIRA: MATH-322
+    @Test
+    public void closeEvents()
+        throws EventException, ConvergenceException, DerivativeException {
+
+        final double r1  = 90.0;
+        final double r2  = 135.0;
+        final double gap = r2 - r1;
+        EventHandler closeEventsGenerator = new EventHandler() {
+            public void resetState(double t, double[] y) {
+            }
+            public double g(double t, double[] y) {
+                return (t - r1) * (r2 - t);
+            }
+            public int eventOccurred(double t, double[] y, boolean increasing) {
+                return CONTINUE;
+            }
+        };
+
+        final double tolerance = 0.1;
+        EventState es = new EventState(closeEventsGenerator, 1.5 * gap, tolerance, 10);
+
+        AbstractStepInterpolator interpolator =
+            new DummyStepInterpolator(new double[0], new double[0], true);
+        interpolator.storeTime(r1 - 2.5 * gap);
+        interpolator.shift();
+        interpolator.storeTime(r1 - 1.5 * gap);
+        es.reinitializeBegin(interpolator);
+
+        interpolator.shift();
+        interpolator.storeTime(r1 - 0.5 * gap);
+        Assert.assertFalse(es.evaluateStep(interpolator));
+
+        interpolator.shift();
+        interpolator.storeTime(0.5 * (r1 + r2));
+        Assert.assertTrue(es.evaluateStep(interpolator));
+        Assert.assertEquals(r1, es.getEventTime(), tolerance);
+        es.stepAccepted(es.getEventTime(), new double[0]);
+
+        interpolator.shift();
+        interpolator.storeTime(r2 + 0.4 * gap);
+        Assert.assertTrue(es.evaluateStep(interpolator));
+        Assert.assertEquals(r2, es.getEventTime(), tolerance);
+
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/ode/jacobians/FirstOrderIntegratorWithJacobiansTest.java b/src/test/java/org/apache/commons/math/ode/jacobians/FirstOrderIntegratorWithJacobiansTest.java
new file mode 100644
index 0000000..0a7ed7a
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/jacobians/FirstOrderIntegratorWithJacobiansTest.java
@@ -0,0 +1,437 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.FirstOrderIntegrator;
+import org.apache.commons.math.ode.IntegratorException;
+import org.apache.commons.math.ode.nonstiff.DormandPrince54Integrator;
+import org.apache.commons.math.stat.descriptive.SummaryStatistics;
+import org.apache.commons.math.util.FastMath;
+import org.junit.Assert;
+import org.junit.Test;
+
+ at Deprecated
+public class FirstOrderIntegratorWithJacobiansTest {
+
+    @Test
+    public void testLowAccuracyExternalDifferentiation()
+        throws IntegratorException, DerivativeException {
+        // this test does not really test FirstOrderIntegratorWithJacobians,
+        // it only shows that WITHOUT this class, attempting to recover
+        // the jacobians from external differentiation on simple integration
+        // results with low accuracy gives very poor results. In fact,
+        // the curves dy/dp = g(b) when b varies from 2.88 to 3.08 are
+        // essentially noise.
+        // This test is taken from Hairer, Norsett and Wanner book
+        // Solving Ordinary Differential Equations I (Nonstiff problems),
+        // the curves dy/dp = g(b) are in figure 6.5
+        FirstOrderIntegrator integ =
+            new DormandPrince54Integrator(1.0e-8, 100.0, new double[] { 1.0e-4, 1.0e-4 }, new double[] { 1.0e-4, 1.0e-4 });
+        double hP = 1.0e-12;
+        SummaryStatistics residualsP0 = new SummaryStatistics();
+        SummaryStatistics residualsP1 = new SummaryStatistics();
+        for (double b = 2.88; b < 3.08; b += 0.001) {
+            Brusselator brusselator = new Brusselator(b);
+            double[] y = { 1.3, b };
+            integ.integrate(brusselator, 0, y, 20.0, y);
+            double[] yP = { 1.3, b + hP };
+            brusselator.setParameter(0, b + hP);
+            integ.integrate(brusselator, 0, yP, 20.0, yP);
+            residualsP0.addValue((yP[0] - y[0]) / hP - brusselator.dYdP0());
+            residualsP1.addValue((yP[1] - y[1]) / hP - brusselator.dYdP1());
+        }
+        Assert.assertTrue((residualsP0.getMax() - residualsP0.getMin()) > 600);
+        Assert.assertTrue(residualsP0.getStandardDeviation() > 30);
+        Assert.assertTrue((residualsP1.getMax() - residualsP1.getMin()) > 800);
+        Assert.assertTrue(residualsP1.getStandardDeviation() > 50);
+    }
+
+    @Test
+    public void testHighAccuracyExternalDifferentiation()
+        throws IntegratorException, DerivativeException {
+        FirstOrderIntegrator integ =
+            new DormandPrince54Integrator(1.0e-8, 100.0, new double[] { 1.0e-10, 1.0e-10 }, new double[] { 1.0e-10, 1.0e-10 });
+        double hP = 1.0e-12;
+        SummaryStatistics residualsP0 = new SummaryStatistics();
+        SummaryStatistics residualsP1 = new SummaryStatistics();
+        for (double b = 2.88; b < 3.08; b += 0.001) {
+            Brusselator brusselator = new Brusselator(b);
+            double[] y = { 1.3, b };
+            integ.integrate(brusselator, 0, y, 20.0, y);
+            double[] yP = { 1.3, b + hP };
+            brusselator.setParameter(0, b + hP);
+            integ.integrate(brusselator, 0, yP, 20.0, yP);
+            residualsP0.addValue((yP[0] - y[0]) / hP - brusselator.dYdP0());
+            residualsP1.addValue((yP[1] - y[1]) / hP - brusselator.dYdP1());
+        }
+        Assert.assertTrue((residualsP0.getMax() - residualsP0.getMin()) > 0.02);
+        Assert.assertTrue((residualsP0.getMax() - residualsP0.getMin()) < 0.03);
+        Assert.assertTrue(residualsP0.getStandardDeviation() > 0.003);
+        Assert.assertTrue(residualsP0.getStandardDeviation() < 0.004);
+        Assert.assertTrue((residualsP1.getMax() - residualsP1.getMin()) > 0.04);
+        Assert.assertTrue((residualsP1.getMax() - residualsP1.getMin()) < 0.05);
+        Assert.assertTrue(residualsP1.getStandardDeviation() > 0.007);
+        Assert.assertTrue(residualsP1.getStandardDeviation() < 0.008);
+    }
+
+    @Test
+    public void testInternalDifferentiation()
+        throws IntegratorException, DerivativeException {
+        FirstOrderIntegrator integ =
+            new DormandPrince54Integrator(1.0e-8, 100.0, new double[] { 1.0e-4, 1.0e-4 }, new double[] { 1.0e-4, 1.0e-4 });
+        double hP = 1.0e-12;
+        SummaryStatistics residualsP0 = new SummaryStatistics();
+        SummaryStatistics residualsP1 = new SummaryStatistics();
+        for (double b = 2.88; b < 3.08; b += 0.001) {
+            Brusselator brusselator = new Brusselator(b);
+            brusselator.setParameter(0, b);
+            double[] z = { 1.3, b };
+            double[][] dZdZ0 = new double[2][2];
+            double[][] dZdP  = new double[2][1];
+            double hY = 1.0e-12;
+            FirstOrderIntegratorWithJacobians extInt =
+                new FirstOrderIntegratorWithJacobians(integ, brusselator, new double[] { b },
+                                                      new double[] { hY, hY }, new double[] { hP });
+            extInt.setMaxEvaluations(5000);
+            extInt.integrate(0, z, new double[][] { { 0.0 }, { 1.0 } }, 20.0, z, dZdZ0, dZdP);
+            Assert.assertEquals(5000, extInt.getMaxEvaluations());
+            Assert.assertTrue(extInt.getEvaluations() > 1400);
+            Assert.assertTrue(extInt.getEvaluations() < 2000);
+            Assert.assertEquals(4 * integ.getEvaluations(), extInt.getEvaluations());
+            residualsP0.addValue(dZdP[0][0] - brusselator.dYdP0());
+            residualsP1.addValue(dZdP[1][0] - brusselator.dYdP1());
+        }
+        Assert.assertTrue((residualsP0.getMax() - residualsP0.getMin()) < 0.02);
+        Assert.assertTrue(residualsP0.getStandardDeviation() < 0.003);
+        Assert.assertTrue((residualsP1.getMax() - residualsP1.getMin()) < 0.05);
+        Assert.assertTrue(residualsP1.getStandardDeviation() < 0.01);
+    }
+
+    @Test
+    public void testAnalyticalDifferentiation()
+        throws IntegratorException, DerivativeException {
+        FirstOrderIntegrator integ =
+            new DormandPrince54Integrator(1.0e-8, 100.0, new double[] { 1.0e-4, 1.0e-4 }, new double[] { 1.0e-4, 1.0e-4 });
+        SummaryStatistics residualsP0 = new SummaryStatistics();
+        SummaryStatistics residualsP1 = new SummaryStatistics();
+        for (double b = 2.88; b < 3.08; b += 0.001) {
+            Brusselator brusselator = new Brusselator(b);
+            brusselator.setParameter(0, b);
+            double[] z = { 1.3, b };
+            double[][] dZdZ0 = new double[2][2];
+            double[][] dZdP  = new double[2][1];
+            FirstOrderIntegratorWithJacobians extInt =
+                new FirstOrderIntegratorWithJacobians(integ, brusselator);
+            extInt.setMaxEvaluations(5000);
+            extInt.integrate(0, z, new double[][] { { 0.0 }, { 1.0 } }, 20.0, z, dZdZ0, dZdP);
+            Assert.assertEquals(5000, extInt.getMaxEvaluations());
+            Assert.assertTrue(extInt.getEvaluations() > 350);
+            Assert.assertTrue(extInt.getEvaluations() < 510);
+            Assert.assertEquals(integ.getEvaluations(), extInt.getEvaluations());
+            residualsP0.addValue(dZdP[0][0] - brusselator.dYdP0());
+            residualsP1.addValue(dZdP[1][0] - brusselator.dYdP1());
+        }
+        Assert.assertTrue((residualsP0.getMax() - residualsP0.getMin()) < 0.014);
+        Assert.assertTrue(residualsP0.getStandardDeviation() < 0.003);
+        Assert.assertTrue((residualsP1.getMax() - residualsP1.getMin()) < 0.05);
+        Assert.assertTrue(residualsP1.getStandardDeviation() < 0.01);
+    }
+
+    @Test
+    public void testFinalResult() throws IntegratorException, DerivativeException {
+        FirstOrderIntegrator integ =
+            new DormandPrince54Integrator(1.0e-8, 100.0, new double[] { 1.0e-10, 1.0e-10 }, new double[] { 1.0e-10, 1.0e-10 });
+        double[] y = new double[] { 0.0, 1.0 };
+        Circle circle = new Circle(y, 1.0, 1.0, 0.1);
+        double[][] dydy0 = new double[2][2];
+        double[][] dydp  = new double[2][3];
+        double t = 18 * FastMath.PI;
+        FirstOrderIntegratorWithJacobians extInt =
+            new FirstOrderIntegratorWithJacobians(integ, circle);
+        extInt.integrate(0, y, circle.exactDyDp(0), t, y, dydy0, dydp);
+        for (int i = 0; i < y.length; ++i) {
+            Assert.assertEquals(circle.exactY(t)[i], y[i], 1.0e-9);
+        }
+        for (int i = 0; i < dydy0.length; ++i) {
+            for (int j = 0; j < dydy0[i].length; ++j) {
+                Assert.assertEquals(circle.exactDyDy0(t)[i][j], dydy0[i][j], 1.0e-9);
+            }
+        }
+        for (int i = 0; i < dydp.length; ++i) {
+            for (int j = 0; j < dydp[i].length; ++j) {
+                Assert.assertEquals(circle.exactDyDp(t)[i][j], dydp[i][j], 1.0e-7);
+            }
+        }
+    }
+
+    @Test
+    public void testStepHandlerResult() throws IntegratorException, DerivativeException {
+        FirstOrderIntegrator integ =
+            new DormandPrince54Integrator(1.0e-8, 100.0, new double[] { 1.0e-10, 1.0e-10 }, new double[] { 1.0e-10, 1.0e-10 });
+        double[] y = new double[] { 0.0, 1.0 };
+        final Circle circle = new Circle(y, 1.0, 1.0, 0.1);
+        double[][] dydy0 = new double[2][2];
+        double[][] dydp  = new double[2][3];
+        double t = 18 * FastMath.PI;
+        final FirstOrderIntegratorWithJacobians extInt =
+            new FirstOrderIntegratorWithJacobians(integ, circle);
+        extInt.addStepHandler(new StepHandlerWithJacobians() {
+
+            public void reset() {
+            }
+
+            public boolean requiresDenseOutput() {
+                return false;
+            }
+
+            public void handleStep(StepInterpolatorWithJacobians interpolator, boolean isLast)
+                throws DerivativeException {
+                double     t     = interpolator.getCurrentTime();
+                double[]   y     = interpolator.getInterpolatedY();
+                double[][] dydy0 = interpolator.getInterpolatedDyDy0();
+                double[][] dydp  = interpolator.getInterpolatedDyDp();
+                Assert.assertEquals(interpolator.getPreviousTime(), extInt.getCurrentStepStart(), 1.0e-10);
+                Assert.assertTrue(extInt.getCurrentSignedStepsize() < 0.5);
+                for (int i = 0; i < y.length; ++i) {
+                    Assert.assertEquals(circle.exactY(t)[i], y[i], 1.0e-9);
+                }
+                for (int i = 0; i < dydy0.length; ++i) {
+                    for (int j = 0; j < dydy0[i].length; ++j) {
+                        Assert.assertEquals(circle.exactDyDy0(t)[i][j], dydy0[i][j], 1.0e-9);
+                    }
+                }
+                for (int i = 0; i < dydp.length; ++i) {
+                    for (int j = 0; j < dydp[i].length; ++j) {
+                        Assert.assertEquals(circle.exactDyDp(t)[i][j], dydp[i][j], 3.0e-8);
+                    }
+                }
+
+                double[]   yDot     = interpolator.getInterpolatedYDot();
+                double[][] dydy0Dot = interpolator.getInterpolatedDyDy0Dot();
+                double[][] dydpDot  = interpolator.getInterpolatedDyDpDot();
+
+                for (int i = 0; i < yDot.length; ++i) {
+                    Assert.assertEquals(circle.exactYDot(t)[i], yDot[i], 1.0e-10);
+                }
+                for (int i = 0; i < dydy0Dot.length; ++i) {
+                    for (int j = 0; j < dydy0Dot[i].length; ++j) {
+                        Assert.assertEquals(circle.exactDyDy0Dot(t)[i][j], dydy0Dot[i][j], 1.0e-10);
+                    }
+                }
+                for (int i = 0; i < dydpDot.length; ++i) {
+                    for (int j = 0; j < dydpDot[i].length; ++j) {
+                        Assert.assertEquals(circle.exactDyDpDot(t)[i][j], dydpDot[i][j], 3.0e-9);
+                    }
+                }
+            }
+        });
+        extInt.integrate(0, y, circle.exactDyDp(0), t, y, dydy0, dydp);
+    }
+
+    @Test
+    public void testEventHandler() throws IntegratorException, DerivativeException {
+        FirstOrderIntegrator integ =
+            new DormandPrince54Integrator(1.0e-8, 100.0, new double[] { 1.0e-10, 1.0e-10 }, new double[] { 1.0e-10, 1.0e-10 });
+        double[] y = new double[] { 0.0, 1.0 };
+        final Circle circle = new Circle(y, 1.0, 1.0, 0.1);
+        double[][] dydy0 = new double[2][2];
+        double[][] dydp  = new double[2][3];
+        double t = 18 * FastMath.PI;
+        final FirstOrderIntegratorWithJacobians extInt =
+            new FirstOrderIntegratorWithJacobians(integ, circle);
+        extInt.addEventHandler(new EventHandlerWithJacobians() {
+
+            public int eventOccurred(double t, double[] y, double[][] dydy0,
+                                     double[][] dydp, boolean increasing) {
+                Assert.assertEquals(0.1, y[1], 1.0e-11);
+                Assert.assertTrue(!increasing);
+                return STOP;
+            }
+
+            public double g(double t, double[] y, double[][] dydy0,
+                            double[][] dydp) {
+                return y[1] - 0.1;
+            }
+
+            public void resetState(double t, double[] y, double[][] dydy0,
+                                   double[][] dydp) {
+            }
+        }, 10.0, 1.0e-10, 1000);
+        double stopTime = extInt.integrate(0, y, circle.exactDyDp(0), t, y, dydy0, dydp);
+        Assert.assertTrue(stopTime < 5.0 * FastMath.PI);
+    }
+
+    private static class Brusselator implements ParameterizedODE, ODEWithJacobians {
+
+        private double b;
+
+        public Brusselator(double b) {
+            this.b = b;
+        }
+
+        public int getDimension() {
+            return 2;
+        }
+
+        public void setParameter(int i, double p) {
+            b = p;
+        }
+
+        public int getParametersDimension() {
+            return 1;
+        }
+
+        public void computeDerivatives(double t, double[] y, double[] yDot) {
+            double prod = y[0] * y[0] * y[1];
+            yDot[0] = 1 + prod - (b + 1) * y[0];
+            yDot[1] = b * y[0] - prod;
+        }
+
+        public void computeJacobians(double t, double[] y, double[] yDot, double[][] dFdY, double[][] dFdP) {
+            double p = 2 * y[0] * y[1];
+            double y02 = y[0] * y[0];
+            dFdY[0][0] = p - (1 + b);
+            dFdY[0][1] = y02;
+            dFdY[1][0] = b - p;
+            dFdY[1][1] = -y02;
+            dFdP[0][0] = -y[0];
+            dFdP[1][0] = y[0];
+        }
+
+        public double dYdP0() {
+            return -1088.232716447743 + (1050.775747149553 + (-339.012934631828 + 36.52917025056327 * b) * b) * b;
+        }
+
+        public double dYdP1() {
+            return 1502.824469929139 + (-1438.6974831849952 + (460.959476642384 - 49.43847385647082 * b) * b) * b;
+        }
+
+    }
+
+    /** ODE representing a point moving on a circle with provided center and angular rate. */
+    private static class Circle implements ODEWithJacobians {
+
+        private final double[] y0;
+        private double cx;
+        private double cy;
+        private double omega;
+
+        public Circle(double[] y0, double cx, double cy, double omega) {
+            this.y0    = y0.clone();
+            this.cx    = cx;
+            this.cy    = cy;
+            this.omega = omega;
+        }
+
+        public int getDimension() {
+            return 2;
+        }
+
+        public int getParametersDimension() {
+            return 3;
+        }
+
+        public void computeDerivatives(double t, double[] y, double[] yDot) {
+            yDot[0] = omega * (cy - y[1]);
+            yDot[1] = omega * (y[0] - cx);
+        }
+
+        public void computeJacobians(double t, double[] y, double[] yDot, double[][] dFdY, double[][] dFdP) {
+
+            dFdY[0][0] = 0;
+            dFdY[0][1] = -omega;
+            dFdY[1][0] = omega;
+            dFdY[1][1] = 0;
+
+            dFdP[0][0] = 0;
+            dFdP[0][1] = omega;
+            dFdP[0][2] = cy - y[1];
+            dFdP[1][0] = -omega;
+            dFdP[1][1] = 0;
+            dFdP[1][2] = y[0] - cx;
+
+        }
+
+        public double[] exactY(double t) {
+            double cos = FastMath.cos(omega * t);
+            double sin = FastMath.sin(omega * t);
+            double dx0 = y0[0] - cx;
+            double dy0 = y0[1] - cy;
+            return new double[] {
+                cx + cos * dx0 - sin * dy0,
+                cy + sin * dx0 + cos * dy0
+            };
+        }
+
+        public double[][] exactDyDy0(double t) {
+            double cos = FastMath.cos(omega * t);
+            double sin = FastMath.sin(omega * t);
+            return new double[][] {
+                { cos, -sin },
+                { sin,  cos }
+            };
+        }
+
+        public double[][] exactDyDp(double t) {
+            double cos = FastMath.cos(omega * t);
+            double sin = FastMath.sin(omega * t);
+            double dx0 = y0[0] - cx;
+            double dy0 = y0[1] - cy;
+            return new double[][] {
+                { 1 - cos, sin,    -t * (sin * dx0 + cos * dy0) },
+                { -sin,    1 - cos, t * (cos * dx0 - sin * dy0) }
+            };
+        }
+
+        public double[] exactYDot(double t) {
+            double oCos = omega * FastMath.cos(omega * t);
+            double oSin = omega * FastMath.sin(omega * t);
+            double dx0 = y0[0] - cx;
+            double dy0 = y0[1] - cy;
+            return new double[] {
+                -oSin * dx0 - oCos * dy0,
+                 oCos * dx0 - oSin * dy0
+            };
+        }
+
+        public double[][] exactDyDy0Dot(double t) {
+            double oCos = omega * FastMath.cos(omega * t);
+            double oSin = omega * FastMath.sin(omega * t);
+            return new double[][] {
+                { -oSin, -oCos },
+                {  oCos, -oSin }
+            };
+        }
+
+        public double[][] exactDyDpDot(double t) {
+            double cos  = FastMath.cos(omega * t);
+            double sin  = FastMath.sin(omega * t);
+            double oCos = omega * cos;
+            double oSin = omega * sin;
+            double dx0  = y0[0] - cx;
+            double dy0  = y0[1] - cy;
+            return new double[][] {
+                {  oSin, oCos, -sin * dx0 - cos * dy0 - t * ( oCos * dx0 - oSin * dy0) },
+                { -oCos, oSin,  cos * dx0 - sin * dy0 + t * (-oSin * dx0 - oCos * dy0) }
+            };
+        }
+
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/ode/nonstiff/AdamsBashforthIntegratorTest.java b/src/test/java/org/apache/commons/math/ode/nonstiff/AdamsBashforthIntegratorTest.java
new file mode 100644
index 0000000..7eeac66
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/nonstiff/AdamsBashforthIntegratorTest.java
@@ -0,0 +1,157 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.ode.nonstiff;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.ode.FirstOrderIntegrator;
+import org.apache.commons.math.ode.IntegratorException;
+import org.apache.commons.math.ode.TestProblem1;
+import org.apache.commons.math.ode.TestProblem5;
+import org.apache.commons.math.ode.TestProblem6;
+import org.apache.commons.math.ode.TestProblemHandler;
+import org.apache.commons.math.util.FastMath;
+import org.junit.Test;
+
+public class AdamsBashforthIntegratorTest {
+
+    @Test(expected=IntegratorException.class)
+    public void dimensionCheck() throws DerivativeException, IntegratorException {
+        TestProblem1 pb = new TestProblem1();
+        FirstOrderIntegrator integ =
+            new AdamsBashforthIntegrator(2, 0.0, 1.0, 1.0e-10, 1.0e-10);
+        integ.integrate(pb,
+                        0.0, new double[pb.getDimension()+10],
+                        1.0, new double[pb.getDimension()+10]);
+    }
+
+    @Test(expected=IntegratorException.class)
+    public void testMinStep() throws DerivativeException, IntegratorException {
+
+          TestProblem1 pb = new TestProblem1();
+          double minStep = 0.1 * (pb.getFinalTime() - pb.getInitialTime());
+          double maxStep = pb.getFinalTime() - pb.getInitialTime();
+          double[] vecAbsoluteTolerance = { 1.0e-15, 1.0e-16 };
+          double[] vecRelativeTolerance = { 1.0e-15, 1.0e-16 };
+
+          FirstOrderIntegrator integ = new AdamsBashforthIntegrator(4, minStep, maxStep,
+                                                                    vecAbsoluteTolerance,
+                                                                    vecRelativeTolerance);
+          TestProblemHandler handler = new TestProblemHandler(pb, integ);
+          integ.addStepHandler(handler);
+          integ.integrate(pb,
+                          pb.getInitialTime(), pb.getInitialState(),
+                          pb.getFinalTime(), new double[pb.getDimension()]);
+
+    }
+
+    @Test
+    public void testIncreasingTolerance()
+        throws DerivativeException, IntegratorException {
+
+        int previousCalls = Integer.MAX_VALUE;
+        for (int i = -12; i < -5; ++i) {
+            TestProblem1 pb = new TestProblem1();
+            double minStep = 0;
+            double maxStep = pb.getFinalTime() - pb.getInitialTime();
+            double scalAbsoluteTolerance = FastMath.pow(10.0, i);
+            double scalRelativeTolerance = 0.01 * scalAbsoluteTolerance;
+
+            FirstOrderIntegrator integ = new AdamsBashforthIntegrator(4, minStep, maxStep,
+                                                                      scalAbsoluteTolerance,
+                                                                      scalRelativeTolerance);
+            TestProblemHandler handler = new TestProblemHandler(pb, integ);
+            integ.addStepHandler(handler);
+            integ.integrate(pb,
+                            pb.getInitialTime(), pb.getInitialState(),
+                            pb.getFinalTime(), new double[pb.getDimension()]);
+
+            // the 31 and 36 factors are only valid for this test
+            // and has been obtained from trial and error
+            // there is no general relation between local and global errors
+            assertTrue(handler.getMaximalValueError() > (31.0 * scalAbsoluteTolerance));
+            assertTrue(handler.getMaximalValueError() < (36.0 * scalAbsoluteTolerance));
+            assertEquals(0, handler.getMaximalTimeError(), 1.0e-16);
+
+            int calls = pb.getCalls();
+            assertEquals(integ.getEvaluations(), calls);
+            assertTrue(calls <= previousCalls);
+            previousCalls = calls;
+
+        }
+
+    }
+
+    @Test(expected = DerivativeException.class)
+    public void exceedMaxEvaluations() throws DerivativeException, IntegratorException {
+
+        TestProblem1 pb  = new TestProblem1();
+        double range = pb.getFinalTime() - pb.getInitialTime();
+
+        AdamsBashforthIntegrator integ = new AdamsBashforthIntegrator(2, 0, range, 1.0e-12, 1.0e-12);
+        TestProblemHandler handler = new TestProblemHandler(pb, integ);
+        integ.addStepHandler(handler);
+        integ.setMaxEvaluations(650);
+        integ.integrate(pb,
+                        pb.getInitialTime(), pb.getInitialState(),
+                        pb.getFinalTime(), new double[pb.getDimension()]);
+
+    }
+
+    @Test
+    public void backward() throws DerivativeException, IntegratorException {
+
+        TestProblem5 pb = new TestProblem5();
+        double range = FastMath.abs(pb.getFinalTime() - pb.getInitialTime());
+
+        FirstOrderIntegrator integ = new AdamsBashforthIntegrator(4, 0, range, 1.0e-12, 1.0e-12);
+        TestProblemHandler handler = new TestProblemHandler(pb, integ);
+        integ.addStepHandler(handler);
+        integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(),
+                        pb.getFinalTime(), new double[pb.getDimension()]);
+
+        assertTrue(handler.getLastError() < 1.0e-8);
+        assertTrue(handler.getMaximalValueError() < 1.0e-8);
+        assertEquals(0, handler.getMaximalTimeError(), 1.0e-16);
+        assertEquals("Adams-Bashforth", integ.getName());
+    }
+
+    @Test
+    public void polynomial() throws DerivativeException, IntegratorException {
+        TestProblem6 pb = new TestProblem6();
+        double range = FastMath.abs(pb.getFinalTime() - pb.getInitialTime());
+
+        for (int nSteps = 1; nSteps < 8; ++nSteps) {
+            AdamsBashforthIntegrator integ =
+                new AdamsBashforthIntegrator(nSteps, 1.0e-6 * range, 0.1 * range, 1.0e-10, 1.0e-10);
+            TestProblemHandler handler = new TestProblemHandler(pb, integ);
+            integ.addStepHandler(handler);
+            integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(),
+                            pb.getFinalTime(), new double[pb.getDimension()]);
+            if (nSteps < 4) {
+                assertTrue(integ.getEvaluations() > 150);
+            } else {
+                assertTrue(integ.getEvaluations() < 70);
+            }
+        }
+
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/ode/nonstiff/AdamsMoultonIntegratorTest.java b/src/test/java/org/apache/commons/math/ode/nonstiff/AdamsMoultonIntegratorTest.java
new file mode 100644
index 0000000..4c9f711
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/nonstiff/AdamsMoultonIntegratorTest.java
@@ -0,0 +1,157 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.ode.nonstiff;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.ode.FirstOrderIntegrator;
+import org.apache.commons.math.ode.IntegratorException;
+import org.apache.commons.math.ode.TestProblem1;
+import org.apache.commons.math.ode.TestProblem5;
+import org.apache.commons.math.ode.TestProblem6;
+import org.apache.commons.math.ode.TestProblemHandler;
+import org.apache.commons.math.util.FastMath;
+import org.junit.Test;
+
+public class AdamsMoultonIntegratorTest {
+
+    @Test(expected=IntegratorException.class)
+    public void dimensionCheck() throws DerivativeException, IntegratorException {
+        TestProblem1 pb = new TestProblem1();
+        FirstOrderIntegrator integ =
+            new AdamsMoultonIntegrator(2, 0.0, 1.0, 1.0e-10, 1.0e-10);
+        integ.integrate(pb,
+                        0.0, new double[pb.getDimension()+10],
+                        1.0, new double[pb.getDimension()+10]);
+    }
+
+    @Test(expected=IntegratorException.class)
+    public void testMinStep() throws DerivativeException, IntegratorException {
+
+          TestProblem1 pb = new TestProblem1();
+          double minStep = 0.1 * (pb.getFinalTime() - pb.getInitialTime());
+          double maxStep = pb.getFinalTime() - pb.getInitialTime();
+          double[] vecAbsoluteTolerance = { 1.0e-15, 1.0e-16 };
+          double[] vecRelativeTolerance = { 1.0e-15, 1.0e-16 };
+
+          FirstOrderIntegrator integ = new AdamsMoultonIntegrator(4, minStep, maxStep,
+                                                                  vecAbsoluteTolerance,
+                                                                  vecRelativeTolerance);
+          TestProblemHandler handler = new TestProblemHandler(pb, integ);
+          integ.addStepHandler(handler);
+          integ.integrate(pb,
+                          pb.getInitialTime(), pb.getInitialState(),
+                          pb.getFinalTime(), new double[pb.getDimension()]);
+
+    }
+
+    @Test
+    public void testIncreasingTolerance()
+        throws DerivativeException, IntegratorException {
+
+        int previousCalls = Integer.MAX_VALUE;
+        for (int i = -12; i < -2; ++i) {
+            TestProblem1 pb = new TestProblem1();
+            double minStep = 0;
+            double maxStep = pb.getFinalTime() - pb.getInitialTime();
+            double scalAbsoluteTolerance = FastMath.pow(10.0, i);
+            double scalRelativeTolerance = 0.01 * scalAbsoluteTolerance;
+
+            FirstOrderIntegrator integ = new AdamsMoultonIntegrator(4, minStep, maxStep,
+                                                                    scalAbsoluteTolerance,
+                                                                    scalRelativeTolerance);
+            TestProblemHandler handler = new TestProblemHandler(pb, integ);
+            integ.addStepHandler(handler);
+            integ.integrate(pb,
+                            pb.getInitialTime(), pb.getInitialState(),
+                            pb.getFinalTime(), new double[pb.getDimension()]);
+
+            // the 0.15 and 3.0 factors are only valid for this test
+            // and has been obtained from trial and error
+            // there is no general relation between local and global errors
+            assertTrue(handler.getMaximalValueError() > (0.15 * scalAbsoluteTolerance));
+            assertTrue(handler.getMaximalValueError() < (3.0 * scalAbsoluteTolerance));
+            assertEquals(0, handler.getMaximalTimeError(), 1.0e-16);
+
+            int calls = pb.getCalls();
+            assertEquals(integ.getEvaluations(), calls);
+            assertTrue(calls <= previousCalls);
+            previousCalls = calls;
+
+        }
+
+    }
+
+    @Test(expected = DerivativeException.class)
+    public void exceedMaxEvaluations() throws DerivativeException, IntegratorException {
+
+        TestProblem1 pb  = new TestProblem1();
+        double range = pb.getFinalTime() - pb.getInitialTime();
+
+        AdamsMoultonIntegrator integ = new AdamsMoultonIntegrator(2, 0, range, 1.0e-12, 1.0e-12);
+        TestProblemHandler handler = new TestProblemHandler(pb, integ);
+        integ.addStepHandler(handler);
+        integ.setMaxEvaluations(650);
+        integ.integrate(pb,
+                        pb.getInitialTime(), pb.getInitialState(),
+                        pb.getFinalTime(), new double[pb.getDimension()]);
+
+    }
+
+    @Test
+    public void backward() throws DerivativeException, IntegratorException {
+
+        TestProblem5 pb = new TestProblem5();
+        double range = FastMath.abs(pb.getFinalTime() - pb.getInitialTime());
+
+        FirstOrderIntegrator integ = new AdamsMoultonIntegrator(4, 0, range, 1.0e-12, 1.0e-12);
+        TestProblemHandler handler = new TestProblemHandler(pb, integ);
+        integ.addStepHandler(handler);
+        integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(),
+                        pb.getFinalTime(), new double[pb.getDimension()]);
+
+        assertTrue(handler.getLastError() < 1.0e-9);
+        assertTrue(handler.getMaximalValueError() < 1.0e-9);
+        assertEquals(0, handler.getMaximalTimeError(), 1.0e-16);
+        assertEquals("Adams-Moulton", integ.getName());
+    }
+
+    @Test
+    public void polynomial() throws DerivativeException, IntegratorException {
+        TestProblem6 pb = new TestProblem6();
+        double range = FastMath.abs(pb.getFinalTime() - pb.getInitialTime());
+
+        for (int nSteps = 1; nSteps < 7; ++nSteps) {
+            AdamsMoultonIntegrator integ =
+                new AdamsMoultonIntegrator(nSteps, 1.0e-6 * range, 0.1 * range, 1.0e-9, 1.0e-9);
+            TestProblemHandler handler = new TestProblemHandler(pb, integ);
+            integ.addStepHandler(handler);
+            integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(),
+                            pb.getFinalTime(), new double[pb.getDimension()]);
+            if (nSteps < 4) {
+                assertTrue(integ.getEvaluations() > 140);
+            } else {
+                assertTrue(integ.getEvaluations() < 90);
+            }
+        }
+
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/ode/nonstiff/ClassicalRungeKuttaIntegratorTest.java b/src/test/java/org/apache/commons/math/ode/nonstiff/ClassicalRungeKuttaIntegratorTest.java
new file mode 100644
index 0000000..6c44c9d
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/nonstiff/ClassicalRungeKuttaIntegratorTest.java
@@ -0,0 +1,308 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.ode.nonstiff;
+
+import junit.framework.*;
+
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.ode.FirstOrderDifferentialEquations;
+import org.apache.commons.math.ode.FirstOrderIntegrator;
+import org.apache.commons.math.ode.IntegratorException;
+import org.apache.commons.math.ode.TestProblem1;
+import org.apache.commons.math.ode.TestProblem3;
+import org.apache.commons.math.ode.TestProblem5;
+import org.apache.commons.math.ode.TestProblemAbstract;
+import org.apache.commons.math.ode.TestProblemFactory;
+import org.apache.commons.math.ode.TestProblemHandler;
+import org.apache.commons.math.ode.events.EventHandler;
+import org.apache.commons.math.ode.nonstiff.ClassicalRungeKuttaIntegrator;
+import org.apache.commons.math.ode.sampling.StepHandler;
+import org.apache.commons.math.ode.sampling.StepInterpolator;
+import org.apache.commons.math.util.FastMath;
+
+public class ClassicalRungeKuttaIntegratorTest
+  extends TestCase {
+
+  public ClassicalRungeKuttaIntegratorTest(String name) {
+    super(name);
+  }
+
+  public void testMissedEndEvent() throws IntegratorException, DerivativeException {
+      final double   t0     = 1878250320.0000029;
+      final double   tEvent = 1878250379.9999986;
+      final double[] k      = { 1.0e-4, 1.0e-5, 1.0e-6 };
+      FirstOrderDifferentialEquations ode = new FirstOrderDifferentialEquations() {
+
+          public int getDimension() {
+              return k.length;
+          }
+
+          public void computeDerivatives(double t, double[] y, double[] yDot) {
+              for (int i = 0; i < y.length; ++i) {
+                  yDot[i] = k[i] * y[i];
+              }
+          }
+      };
+
+      ClassicalRungeKuttaIntegrator integrator = new ClassicalRungeKuttaIntegrator(60.0);
+
+      double[] y0   = new double[k.length];
+      for (int i = 0; i < y0.length; ++i) {
+          y0[i] = i + 1;
+      }
+      double[] y    = new double[k.length];
+
+      double finalT = integrator.integrate(ode, t0, y0, tEvent, y);
+      Assert.assertEquals(tEvent, finalT, 5.0e-6);
+      for (int i = 0; i < y.length; ++i) {
+          Assert.assertEquals(y0[i] * FastMath.exp(k[i] * (finalT - t0)), y[i], 1.0e-9);
+      }
+
+      integrator.addEventHandler(new EventHandler() {
+
+          public void resetState(double t, double[] y) {
+          }
+
+          public double g(double t, double[] y) {
+              return t - tEvent;
+          }
+
+          public int eventOccurred(double t, double[] y, boolean increasing) {
+              Assert.assertEquals(tEvent, t, 5.0e-6);
+              return CONTINUE;
+          }
+      }, Double.POSITIVE_INFINITY, 1.0e-20, 100);
+      finalT = integrator.integrate(ode, t0, y0, tEvent + 120, y);
+      Assert.assertEquals(tEvent + 120, finalT, 5.0e-6);
+      for (int i = 0; i < y.length; ++i) {
+          Assert.assertEquals(y0[i] * FastMath.exp(k[i] * (finalT - t0)), y[i], 1.0e-9);
+      }
+
+  }
+
+  public void testSanityChecks() {
+    try  {
+      TestProblem1 pb = new TestProblem1();
+      new ClassicalRungeKuttaIntegrator(0.01).integrate(pb,
+                                                        0.0, new double[pb.getDimension()+10],
+                                                        1.0, new double[pb.getDimension()]);
+        fail("an exception should have been thrown");
+    } catch(DerivativeException de) {
+      fail("wrong exception caught");
+    } catch(IntegratorException ie) {
+    }
+    try  {
+        TestProblem1 pb = new TestProblem1();
+        new ClassicalRungeKuttaIntegrator(0.01).integrate(pb,
+                                                          0.0, new double[pb.getDimension()],
+                                                          1.0, new double[pb.getDimension()+10]);
+          fail("an exception should have been thrown");
+      } catch(DerivativeException de) {
+        fail("wrong exception caught");
+      } catch(IntegratorException ie) {
+      }
+    try  {
+      TestProblem1 pb = new TestProblem1();
+      new ClassicalRungeKuttaIntegrator(0.01).integrate(pb,
+                                                        0.0, new double[pb.getDimension()],
+                                                        0.0, new double[pb.getDimension()]);
+        fail("an exception should have been thrown");
+    } catch(DerivativeException de) {
+      fail("wrong exception caught");
+    } catch(IntegratorException ie) {
+    }
+  }
+
+  public void testDecreasingSteps()
+    throws DerivativeException, IntegratorException  {
+
+    TestProblemAbstract[] problems = TestProblemFactory.getProblems();
+    for (int k = 0; k < problems.length; ++k) {
+
+      double previousValueError = Double.NaN;
+      double previousTimeError = Double.NaN;
+      for (int i = 4; i < 10; ++i) {
+
+        TestProblemAbstract pb = problems[k].copy();
+        double step = (pb.getFinalTime() - pb.getInitialTime()) * FastMath.pow(2.0, -i);
+
+        FirstOrderIntegrator integ = new ClassicalRungeKuttaIntegrator(step);
+        TestProblemHandler handler = new TestProblemHandler(pb, integ);
+        integ.addStepHandler(handler);
+        EventHandler[] functions = pb.getEventsHandlers();
+        for (int l = 0; l < functions.length; ++l) {
+          integ.addEventHandler(functions[l],
+                                     Double.POSITIVE_INFINITY, 1.0e-6 * step, 1000);
+        }
+        assertEquals(functions.length, integ.getEventHandlers().size());
+        double stopTime = integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(),
+                                          pb.getFinalTime(), new double[pb.getDimension()]);
+        if (functions.length == 0) {
+            assertEquals(pb.getFinalTime(), stopTime, 1.0e-10);
+        }
+
+        double error = handler.getMaximalValueError();
+        if (i > 4) {
+          assertTrue(error < FastMath.abs(previousValueError));
+        }
+        previousValueError = error;
+
+        double timeError = handler.getMaximalTimeError();
+        if (i > 4) {
+          assertTrue(timeError <= FastMath.abs(previousTimeError));
+        }
+        previousTimeError = timeError;
+
+        integ.clearEventHandlers();
+        assertEquals(0, integ.getEventHandlers().size());
+      }
+
+    }
+
+  }
+
+  public void testSmallStep()
+    throws DerivativeException, IntegratorException {
+
+    TestProblem1 pb = new TestProblem1();
+    double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.001;
+
+    FirstOrderIntegrator integ = new ClassicalRungeKuttaIntegrator(step);
+    TestProblemHandler handler = new TestProblemHandler(pb, integ);
+    integ.addStepHandler(handler);
+    integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(),
+                    pb.getFinalTime(), new double[pb.getDimension()]);
+
+    assertTrue(handler.getLastError() < 2.0e-13);
+    assertTrue(handler.getMaximalValueError() < 4.0e-12);
+    assertEquals(0, handler.getMaximalTimeError(), 1.0e-12);
+    assertEquals("classical Runge-Kutta", integ.getName());
+  }
+
+  public void testBigStep()
+    throws DerivativeException, IntegratorException {
+
+    TestProblem1 pb = new TestProblem1();
+    double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.2;
+
+    FirstOrderIntegrator integ = new ClassicalRungeKuttaIntegrator(step);
+    TestProblemHandler handler = new TestProblemHandler(pb, integ);
+    integ.addStepHandler(handler);
+    integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(),
+                    pb.getFinalTime(), new double[pb.getDimension()]);
+
+    assertTrue(handler.getLastError() > 0.0004);
+    assertTrue(handler.getMaximalValueError() > 0.005);
+    assertEquals(0, handler.getMaximalTimeError(), 1.0e-12);
+
+  }
+
+  public void testBackward()
+    throws DerivativeException, IntegratorException {
+
+    TestProblem5 pb = new TestProblem5();
+    double step = FastMath.abs(pb.getFinalTime() - pb.getInitialTime()) * 0.001;
+
+    FirstOrderIntegrator integ = new ClassicalRungeKuttaIntegrator(step);
+    TestProblemHandler handler = new TestProblemHandler(pb, integ);
+    integ.addStepHandler(handler);
+    integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(),
+                    pb.getFinalTime(), new double[pb.getDimension()]);
+
+    assertTrue(handler.getLastError() < 5.0e-10);
+    assertTrue(handler.getMaximalValueError() < 7.0e-10);
+    assertEquals(0, handler.getMaximalTimeError(), 1.0e-12);
+    assertEquals("classical Runge-Kutta", integ.getName());
+  }
+
+  public void testKepler()
+    throws DerivativeException, IntegratorException {
+
+    final TestProblem3 pb  = new TestProblem3(0.9);
+    double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.0003;
+
+    FirstOrderIntegrator integ = new ClassicalRungeKuttaIntegrator(step);
+    integ.addStepHandler(new KeplerHandler(pb));
+    integ.integrate(pb,
+                    pb.getInitialTime(), pb.getInitialState(),
+                    pb.getFinalTime(), new double[pb.getDimension()]);
+  }
+
+  private static class KeplerHandler implements StepHandler {
+    public KeplerHandler(TestProblem3 pb) {
+      this.pb = pb;
+      reset();
+    }
+    public boolean requiresDenseOutput() {
+      return false;
+    }
+    public void reset() {
+      maxError = 0;
+    }
+    public void handleStep(StepInterpolator interpolator,
+                           boolean isLast) throws DerivativeException {
+
+      double[] interpolatedY = interpolator.getInterpolatedState ();
+      double[] theoreticalY  = pb.computeTheoreticalState(interpolator.getCurrentTime());
+      double dx = interpolatedY[0] - theoreticalY[0];
+      double dy = interpolatedY[1] - theoreticalY[1];
+      double error = dx * dx + dy * dy;
+      if (error > maxError) {
+        maxError = error;
+      }
+      if (isLast) {
+        // even with more than 1000 evaluations per period,
+        // RK4 is not able to integrate such an eccentric
+        // orbit with a good accuracy
+        assertTrue(maxError > 0.005);
+      }
+    }
+    private double maxError = 0;
+    private TestProblem3 pb;
+  }
+
+  public void testStepSize()
+    throws DerivativeException, IntegratorException {
+      final double step = 1.23456;
+      FirstOrderIntegrator integ = new ClassicalRungeKuttaIntegrator(step);
+      integ.addStepHandler(new StepHandler() {
+          public void handleStep(StepInterpolator interpolator, boolean isLast) {
+              if (! isLast) {
+                  assertEquals(step,
+                               interpolator.getCurrentTime() - interpolator.getPreviousTime(),
+                               1.0e-12);
+              }
+          }
+          public boolean requiresDenseOutput() {
+              return false;
+          }
+          public void reset() {
+          }
+      });
+      integ.integrate(new FirstOrderDifferentialEquations() {
+          private static final long serialVersionUID = 0L;
+          public void computeDerivatives(double t, double[] y, double[] dot) {
+              dot[0] = 1.0;
+          }
+          public int getDimension() {
+              return 1;
+          }
+      }, 0.0, new double[] { 0.0 }, 5.0, new double[1]);
+  }
+
+}
diff --git a/src/test/java/org/apache/commons/math/ode/nonstiff/ClassicalRungeKuttaStepInterpolatorTest.java b/src/test/java/org/apache/commons/math/ode/nonstiff/ClassicalRungeKuttaStepInterpolatorTest.java
new file mode 100644
index 0000000..93f0805
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/nonstiff/ClassicalRungeKuttaStepInterpolatorTest.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.ode.nonstiff;
+
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Random;
+
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.ode.ContinuousOutputModel;
+import org.apache.commons.math.ode.IntegratorException;
+import org.apache.commons.math.ode.TestProblem3;
+import org.apache.commons.math.ode.sampling.StepHandler;
+import org.apache.commons.math.ode.sampling.StepInterpolatorTestUtils;
+import org.junit.Test;
+
+public class ClassicalRungeKuttaStepInterpolatorTest {
+
+  @Test
+  public void derivativesConsistency()
+  throws DerivativeException, IntegratorException {
+    TestProblem3 pb = new TestProblem3();
+    double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.001;
+    ClassicalRungeKuttaIntegrator integ = new ClassicalRungeKuttaIntegrator(step);
+    StepInterpolatorTestUtils.checkDerivativesConsistency(integ, pb, 1.0e-10);
+  }
+
+  @Test
+  public void serialization()
+    throws DerivativeException, IntegratorException,
+           IOException, ClassNotFoundException {
+
+    TestProblem3 pb = new TestProblem3(0.9);
+    double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.0003;
+    ClassicalRungeKuttaIntegrator integ = new ClassicalRungeKuttaIntegrator(step);
+    integ.addStepHandler(new ContinuousOutputModel());
+    integ.integrate(pb,
+                    pb.getInitialTime(), pb.getInitialState(),
+                    pb.getFinalTime(), new double[pb.getDimension()]);
+
+    ByteArrayOutputStream bos = new ByteArrayOutputStream();
+    ObjectOutputStream    oos = new ObjectOutputStream(bos);
+    for (StepHandler handler : integ.getStepHandlers()) {
+        oos.writeObject(handler);
+    }
+
+    assertTrue(bos.size () > 753000);
+    assertTrue(bos.size () < 754000);
+
+    ByteArrayInputStream  bis = new ByteArrayInputStream(bos.toByteArray());
+    ObjectInputStream     ois = new ObjectInputStream(bis);
+    ContinuousOutputModel cm  = (ContinuousOutputModel) ois.readObject();
+
+    Random random = new Random(347588535632l);
+    double maxError = 0.0;
+    for (int i = 0; i < 1000; ++i) {
+      double r = random.nextDouble();
+      double time = r * pb.getInitialTime() + (1.0 - r) * pb.getFinalTime();
+      cm.setInterpolatedTime(time);
+      double[] interpolatedY = cm.getInterpolatedState ();
+      double[] theoreticalY  = pb.computeTheoreticalState(time);
+      double dx = interpolatedY[0] - theoreticalY[0];
+      double dy = interpolatedY[1] - theoreticalY[1];
+      double error = dx * dx + dy * dy;
+      if (error > maxError) {
+        maxError = error;
+      }
+    }
+
+    assertTrue(maxError > 0.005);
+
+  }
+
+}
diff --git a/src/test/java/org/apache/commons/math/ode/nonstiff/DormandPrince54IntegratorTest.java b/src/test/java/org/apache/commons/math/ode/nonstiff/DormandPrince54IntegratorTest.java
new file mode 100644
index 0000000..c7be578
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/nonstiff/DormandPrince54IntegratorTest.java
@@ -0,0 +1,363 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.ode.nonstiff;
+
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.ode.FirstOrderIntegrator;
+import org.apache.commons.math.ode.IntegratorException;
+import org.apache.commons.math.ode.TestProblem1;
+import org.apache.commons.math.ode.TestProblem3;
+import org.apache.commons.math.ode.TestProblem4;
+import org.apache.commons.math.ode.TestProblem5;
+import org.apache.commons.math.ode.TestProblemAbstract;
+import org.apache.commons.math.ode.TestProblemHandler;
+import org.apache.commons.math.ode.events.EventHandler;
+import org.apache.commons.math.ode.nonstiff.DormandPrince54Integrator;
+import org.apache.commons.math.ode.nonstiff.EmbeddedRungeKuttaIntegrator;
+import org.apache.commons.math.ode.sampling.StepHandler;
+import org.apache.commons.math.ode.sampling.StepInterpolator;
+import org.apache.commons.math.util.FastMath;
+
+import junit.framework.*;
+
+public class DormandPrince54IntegratorTest
+  extends TestCase {
+
+  public DormandPrince54IntegratorTest(String name) {
+    super(name);
+  }
+
+  public void testDimensionCheck() {
+    try  {
+      TestProblem1 pb = new TestProblem1();
+      DormandPrince54Integrator integrator = new DormandPrince54Integrator(0.0, 1.0,
+                                                                           1.0e-10, 1.0e-10);
+      integrator.integrate(pb,
+                           0.0, new double[pb.getDimension()+10],
+                           1.0, new double[pb.getDimension()+10]);
+      fail("an exception should have been thrown");
+    } catch(DerivativeException de) {
+      fail("wrong exception caught");
+    } catch(IntegratorException ie) {
+    }
+  }
+
+  public void testMinStep() {
+
+    try {
+      TestProblem1 pb = new TestProblem1();
+      double minStep = 0.1 * (pb.getFinalTime() - pb.getInitialTime());
+      double maxStep = pb.getFinalTime() - pb.getInitialTime();
+      double[] vecAbsoluteTolerance = { 1.0e-15, 1.0e-16 };
+      double[] vecRelativeTolerance = { 1.0e-15, 1.0e-16 };
+
+      FirstOrderIntegrator integ = new DormandPrince54Integrator(minStep, maxStep,
+                                                                 vecAbsoluteTolerance,
+                                                                 vecRelativeTolerance);
+      TestProblemHandler handler = new TestProblemHandler(pb, integ);
+      integ.addStepHandler(handler);
+      integ.integrate(pb,
+                      pb.getInitialTime(), pb.getInitialState(),
+                      pb.getFinalTime(), new double[pb.getDimension()]);
+      fail("an exception should have been thrown");
+    } catch(DerivativeException de) {
+      fail("wrong exception caught");
+    } catch(IntegratorException ie) {
+    }
+
+  }
+
+  public void testSmallLastStep()
+    throws DerivativeException, IntegratorException {
+
+    TestProblemAbstract pb = new TestProblem5();
+    double minStep = 1.25;
+    double maxStep = FastMath.abs(pb.getFinalTime() - pb.getInitialTime());
+    double scalAbsoluteTolerance = 6.0e-4;
+    double scalRelativeTolerance = 6.0e-4;
+
+    AdaptiveStepsizeIntegrator integ =
+      new DormandPrince54Integrator(minStep, maxStep,
+                                    scalAbsoluteTolerance,
+                                    scalRelativeTolerance);
+
+    DP54SmallLastHandler handler = new DP54SmallLastHandler(minStep);
+    integ.addStepHandler(handler);
+    integ.setInitialStepSize(1.7);
+    integ.integrate(pb,
+                    pb.getInitialTime(), pb.getInitialState(),
+                    pb.getFinalTime(), new double[pb.getDimension()]);
+    assertTrue(handler.wasLastSeen());
+    assertEquals("Dormand-Prince 5(4)", integ.getName());
+
+  }
+
+  public void testBackward()
+      throws DerivativeException, IntegratorException {
+
+      TestProblem5 pb = new TestProblem5();
+      double minStep = 0;
+      double maxStep = pb.getFinalTime() - pb.getInitialTime();
+      double scalAbsoluteTolerance = 1.0e-8;
+      double scalRelativeTolerance = 0.01 * scalAbsoluteTolerance;
+
+      FirstOrderIntegrator integ = new DormandPrince54Integrator(minStep, maxStep,
+                                                                 scalAbsoluteTolerance,
+                                                                 scalRelativeTolerance);
+      TestProblemHandler handler = new TestProblemHandler(pb, integ);
+      integ.addStepHandler(handler);
+      integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(),
+                      pb.getFinalTime(), new double[pb.getDimension()]);
+
+      assertTrue(handler.getLastError() < 2.0e-7);
+      assertTrue(handler.getMaximalValueError() < 2.0e-7);
+      assertEquals(0, handler.getMaximalTimeError(), 1.0e-12);
+      assertEquals("Dormand-Prince 5(4)", integ.getName());
+  }
+
+  private static class DP54SmallLastHandler implements StepHandler {
+
+    public DP54SmallLastHandler(double minStep) {
+      lastSeen = false;
+      this.minStep = minStep;
+    }
+
+    public boolean requiresDenseOutput() {
+      return false;
+    }
+
+    public void reset() {
+    }
+
+    public void handleStep(StepInterpolator interpolator, boolean isLast) {
+      if (isLast) {
+        lastSeen = true;
+        double h = interpolator.getCurrentTime() - interpolator.getPreviousTime();
+        assertTrue(FastMath.abs(h) < minStep);
+      }
+    }
+
+    public boolean wasLastSeen() {
+      return lastSeen;
+    }
+
+    private boolean lastSeen;
+    private double  minStep;
+
+  }
+
+  public void testIncreasingTolerance()
+    throws DerivativeException, IntegratorException {
+
+    int previousCalls = Integer.MAX_VALUE;
+    for (int i = -12; i < -2; ++i) {
+      TestProblem1 pb = new TestProblem1();
+      double minStep = 0;
+      double maxStep = pb.getFinalTime() - pb.getInitialTime();
+      double scalAbsoluteTolerance = FastMath.pow(10.0, i);
+      double scalRelativeTolerance = 0.01 * scalAbsoluteTolerance;
+
+      EmbeddedRungeKuttaIntegrator integ =
+          new DormandPrince54Integrator(minStep, maxStep,
+                                        scalAbsoluteTolerance, scalRelativeTolerance);
+      TestProblemHandler handler = new TestProblemHandler(pb, integ);
+      integ.setSafety(0.8);
+      integ.setMaxGrowth(5.0);
+      integ.setMinReduction(0.3);
+      integ.addStepHandler(handler);
+      integ.integrate(pb,
+                      pb.getInitialTime(), pb.getInitialState(),
+                      pb.getFinalTime(), new double[pb.getDimension()]);
+      assertEquals(0.8, integ.getSafety(), 1.0e-12);
+      assertEquals(5.0, integ.getMaxGrowth(), 1.0e-12);
+      assertEquals(0.3, integ.getMinReduction(), 1.0e-12);
+
+      // the 0.7 factor is only valid for this test
+      // and has been obtained from trial and error
+      // there is no general relation between local and global errors
+      assertTrue(handler.getMaximalValueError() < (0.7 * scalAbsoluteTolerance));
+      assertEquals(0, handler.getMaximalTimeError(), 1.0e-12);
+
+      int calls = pb.getCalls();
+      assertEquals(integ.getEvaluations(), calls);
+      assertTrue(calls <= previousCalls);
+      previousCalls = calls;
+
+    }
+
+  }
+
+  public void testEvents()
+    throws DerivativeException, IntegratorException {
+
+    TestProblem4 pb = new TestProblem4();
+    double minStep = 0;
+    double maxStep = pb.getFinalTime() - pb.getInitialTime();
+    double scalAbsoluteTolerance = 1.0e-8;
+    double scalRelativeTolerance = 0.01 * scalAbsoluteTolerance;
+
+    FirstOrderIntegrator integ = new DormandPrince54Integrator(minStep, maxStep,
+                                                               scalAbsoluteTolerance,
+                                                               scalRelativeTolerance);
+    TestProblemHandler handler = new TestProblemHandler(pb, integ);
+    integ.addStepHandler(handler);
+    EventHandler[] functions = pb.getEventsHandlers();
+    double convergence = 1.0e-8 * maxStep;
+    for (int l = 0; l < functions.length; ++l) {
+      integ.addEventHandler(functions[l],
+                                 Double.POSITIVE_INFINITY, convergence, 1000);
+    }
+    assertEquals(functions.length, integ.getEventHandlers().size());
+    integ.integrate(pb,
+                    pb.getInitialTime(), pb.getInitialState(),
+                    pb.getFinalTime(), new double[pb.getDimension()]);
+
+    assertTrue(handler.getMaximalValueError() < 5.0e-6);
+    assertEquals(0, handler.getMaximalTimeError(), convergence);
+    assertEquals(12.0, handler.getLastTime(), convergence);
+    integ.clearEventHandlers();
+    assertEquals(0, integ.getEventHandlers().size());
+
+  }
+
+  public void testKepler()
+    throws DerivativeException, IntegratorException {
+
+    final TestProblem3 pb  = new TestProblem3(0.9);
+    double minStep = 0;
+    double maxStep = pb.getFinalTime() - pb.getInitialTime();
+    double scalAbsoluteTolerance = 1.0e-8;
+    double scalRelativeTolerance = scalAbsoluteTolerance;
+
+    FirstOrderIntegrator integ = new DormandPrince54Integrator(minStep, maxStep,
+                                                               scalAbsoluteTolerance,
+                                                               scalRelativeTolerance);
+    integ.addStepHandler(new KeplerHandler(pb));
+    integ.integrate(pb,
+                    pb.getInitialTime(), pb.getInitialState(),
+                    pb.getFinalTime(), new double[pb.getDimension()]);
+
+    assertEquals(integ.getEvaluations(), pb.getCalls());
+    assertTrue(pb.getCalls() < 2800);
+
+  }
+
+  public void testVariableSteps()
+    throws DerivativeException, IntegratorException {
+
+    final TestProblem3 pb  = new TestProblem3(0.9);
+    double minStep = 0;
+    double maxStep = pb.getFinalTime() - pb.getInitialTime();
+    double scalAbsoluteTolerance = 1.0e-8;
+    double scalRelativeTolerance = scalAbsoluteTolerance;
+
+    FirstOrderIntegrator integ = new DormandPrince54Integrator(minStep, maxStep,
+                                                               scalAbsoluteTolerance,
+                                                               scalRelativeTolerance);
+    integ.addStepHandler(new VariableHandler());
+    double stopTime = integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(),
+                                      pb.getFinalTime(), new double[pb.getDimension()]);
+    assertEquals(pb.getFinalTime(), stopTime, 1.0e-10);
+  }
+
+  private static class KeplerHandler implements StepHandler {
+    public KeplerHandler(TestProblem3 pb) {
+      this.pb = pb;
+      reset();
+    }
+    public boolean requiresDenseOutput() {
+      return true;
+    }
+    public void reset() {
+      nbSteps = 0;
+      maxError = 0;
+    }
+    public void handleStep(StepInterpolator interpolator,
+                           boolean isLast)
+    throws DerivativeException {
+
+      ++nbSteps;
+      for (int a = 1; a < 10; ++a) {
+
+        double prev   = interpolator.getPreviousTime();
+        double curr   = interpolator.getCurrentTime();
+        double interp = ((10 - a) * prev + a * curr) / 10;
+        interpolator.setInterpolatedTime(interp);
+
+        double[] interpolatedY = interpolator.getInterpolatedState ();
+        double[] theoreticalY  = pb.computeTheoreticalState(interpolator.getInterpolatedTime());
+        double dx = interpolatedY[0] - theoreticalY[0];
+        double dy = interpolatedY[1] - theoreticalY[1];
+        double error = dx * dx + dy * dy;
+        if (error > maxError) {
+          maxError = error;
+        }
+      }
+      if (isLast) {
+        assertTrue(maxError < 7.0e-10);
+        assertTrue(nbSteps < 400);
+      }
+    }
+    private int nbSteps;
+    private double maxError;
+    private TestProblem3 pb;
+  }
+
+  private static class VariableHandler implements StepHandler {
+    public VariableHandler() {
+      firstTime = true;
+      minStep = 0;
+      maxStep = 0;
+    }
+    public boolean requiresDenseOutput() {
+      return false;
+    }
+    public void reset() {
+      firstTime = true;
+      minStep = 0;
+      maxStep = 0;
+    }
+    public void handleStep(StepInterpolator interpolator,
+                           boolean isLast) {
+
+      double step = FastMath.abs(interpolator.getCurrentTime()
+                             - interpolator.getPreviousTime());
+      if (firstTime) {
+        minStep   = FastMath.abs(step);
+        maxStep   = minStep;
+        firstTime = false;
+      } else {
+        if (step < minStep) {
+          minStep = step;
+        }
+        if (step > maxStep) {
+          maxStep = step;
+        }
+      }
+
+      if (isLast) {
+        assertTrue(minStep < (1.0 / 450.0));
+        assertTrue(maxStep > (1.0 / 4.2));
+      }
+    }
+    private boolean firstTime;
+    private double  minStep;
+    private double  maxStep;
+  }
+
+}
diff --git a/src/test/java/org/apache/commons/math/ode/nonstiff/DormandPrince54StepInterpolatorTest.java b/src/test/java/org/apache/commons/math/ode/nonstiff/DormandPrince54StepInterpolatorTest.java
new file mode 100644
index 0000000..5e3f57f
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/nonstiff/DormandPrince54StepInterpolatorTest.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.ode.nonstiff;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Random;
+
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.ode.ContinuousOutputModel;
+import org.apache.commons.math.ode.IntegratorException;
+import org.apache.commons.math.ode.TestProblem3;
+import org.apache.commons.math.ode.sampling.StepHandler;
+import org.apache.commons.math.ode.sampling.StepInterpolator;
+import org.apache.commons.math.ode.sampling.StepInterpolatorTestUtils;
+import org.apache.commons.math.util.FastMath;
+import org.junit.Test;
+
+public class DormandPrince54StepInterpolatorTest {
+
+  @Test
+  public void derivativesConsistency()
+  throws DerivativeException, IntegratorException {
+    TestProblem3 pb = new TestProblem3(0.1);
+    double minStep = 0;
+    double maxStep = pb.getFinalTime() - pb.getInitialTime();
+    double scalAbsoluteTolerance = 1.0e-8;
+    double scalRelativeTolerance = scalAbsoluteTolerance;
+    DormandPrince54Integrator integ = new DormandPrince54Integrator(minStep, maxStep,
+                                                                    scalAbsoluteTolerance,
+                                                                    scalRelativeTolerance);
+    StepInterpolatorTestUtils.checkDerivativesConsistency(integ, pb, 1.0e-10);
+  }
+
+  @Test
+  public void serialization()
+    throws DerivativeException, IntegratorException,
+           IOException, ClassNotFoundException {
+
+    TestProblem3 pb = new TestProblem3(0.9);
+    double minStep = 0;
+    double maxStep = pb.getFinalTime() - pb.getInitialTime();
+    double scalAbsoluteTolerance = 1.0e-8;
+    double scalRelativeTolerance = scalAbsoluteTolerance;
+    DormandPrince54Integrator integ = new DormandPrince54Integrator(minStep, maxStep,
+                                                                    scalAbsoluteTolerance,
+                                                                    scalRelativeTolerance);
+    integ.addStepHandler(new ContinuousOutputModel());
+    integ.integrate(pb,
+                    pb.getInitialTime(), pb.getInitialState(),
+                    pb.getFinalTime(), new double[pb.getDimension()]);
+
+    ByteArrayOutputStream bos = new ByteArrayOutputStream();
+    ObjectOutputStream    oos = new ObjectOutputStream(bos);
+    for (StepHandler handler : integ.getStepHandlers()) {
+        oos.writeObject(handler);
+    }
+
+    assertTrue(bos.size () > 126000);
+    assertTrue(bos.size () < 127000);
+
+    ByteArrayInputStream  bis = new ByteArrayInputStream(bos.toByteArray());
+    ObjectInputStream     ois = new ObjectInputStream(bis);
+    ContinuousOutputModel cm  = (ContinuousOutputModel) ois.readObject();
+
+    Random random = new Random(347588535632l);
+    double maxError = 0.0;
+    for (int i = 0; i < 1000; ++i) {
+      double r = random.nextDouble();
+      double time = r * pb.getInitialTime() + (1.0 - r) * pb.getFinalTime();
+      cm.setInterpolatedTime(time);
+      double[] interpolatedY = cm.getInterpolatedState ();
+      double[] theoreticalY  = pb.computeTheoreticalState(time);
+      double dx = interpolatedY[0] - theoreticalY[0];
+      double dy = interpolatedY[1] - theoreticalY[1];
+      double error = dx * dx + dy * dy;
+      if (error > maxError) {
+        maxError = error;
+      }
+    }
+
+    assertTrue(maxError < 7.0e-10);
+
+  }
+
+  @Test
+  public void checkClone()
+    throws DerivativeException, IntegratorException {
+      TestProblem3 pb = new TestProblem3(0.9);
+      double minStep = 0;
+      double maxStep = pb.getFinalTime() - pb.getInitialTime();
+      double scalAbsoluteTolerance = 1.0e-8;
+      double scalRelativeTolerance = scalAbsoluteTolerance;
+      DormandPrince54Integrator integ = new DormandPrince54Integrator(minStep, maxStep,
+                                                                      scalAbsoluteTolerance,
+                                                                      scalRelativeTolerance);
+      integ.addStepHandler(new StepHandler() {
+        public void handleStep(StepInterpolator interpolator, boolean isLast)
+          throws DerivativeException {
+              StepInterpolator cloned = interpolator.copy();
+              double tA = cloned.getPreviousTime();
+              double tB = cloned.getCurrentTime();
+              double halfStep = FastMath.abs(tB - tA) / 2;
+              assertEquals(interpolator.getPreviousTime(), tA, 1.0e-12);
+              assertEquals(interpolator.getCurrentTime(), tB, 1.0e-12);
+              for (int i = 0; i < 10; ++i) {
+                  double t = (i * tB + (9 - i) * tA) / 9;
+                  interpolator.setInterpolatedTime(t);
+                  assertTrue(FastMath.abs(cloned.getInterpolatedTime() - t) > (halfStep / 10));
+                  cloned.setInterpolatedTime(t);
+                  assertEquals(t, cloned.getInterpolatedTime(), 1.0e-12);
+                  double[] referenceState = interpolator.getInterpolatedState();
+                  double[] cloneState     = cloned.getInterpolatedState();
+                  for (int j = 0; j < referenceState.length; ++j) {
+                      assertEquals(referenceState[j], cloneState[j], 1.0e-12);
+                  }
+              }
+          }
+          public boolean requiresDenseOutput() {
+              return true;
+          }
+          public void reset() {
+          }
+      });
+      integ.integrate(pb,
+              pb.getInitialTime(), pb.getInitialState(),
+              pb.getFinalTime(), new double[pb.getDimension()]);
+
+  }
+
+}
diff --git a/src/test/java/org/apache/commons/math/ode/nonstiff/DormandPrince853IntegratorTest.java b/src/test/java/org/apache/commons/math/ode/nonstiff/DormandPrince853IntegratorTest.java
new file mode 100644
index 0000000..c161235
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/nonstiff/DormandPrince853IntegratorTest.java
@@ -0,0 +1,428 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.FirstOrderIntegrator;
+import org.apache.commons.math.ode.IntegratorException;
+import org.apache.commons.math.ode.TestProblem1;
+import org.apache.commons.math.ode.TestProblem3;
+import org.apache.commons.math.ode.TestProblem4;
+import org.apache.commons.math.ode.TestProblem5;
+import org.apache.commons.math.ode.TestProblemHandler;
+import org.apache.commons.math.ode.events.EventHandler;
+import org.apache.commons.math.ode.nonstiff.DormandPrince853Integrator;
+import org.apache.commons.math.ode.sampling.DummyStepHandler;
+import org.apache.commons.math.ode.sampling.StepHandler;
+import org.apache.commons.math.ode.sampling.StepInterpolator;
+import org.apache.commons.math.util.FastMath;
+
+import junit.framework.*;
+
+public class DormandPrince853IntegratorTest
+  extends TestCase {
+
+  public DormandPrince853IntegratorTest(String name) {
+    super(name);
+  }
+
+  public void testMissedEndEvent() throws IntegratorException, DerivativeException {
+      final double   t0     = 1878250320.0000029;
+      final double   tEvent = 1878250379.9999986;
+      final double[] k  = { 1.0e-4, 1.0e-5, 1.0e-6 };
+      FirstOrderDifferentialEquations ode = new FirstOrderDifferentialEquations() {
+
+          public int getDimension() {
+              return k.length;
+          }
+
+          public void computeDerivatives(double t, double[] y, double[] yDot) {
+              for (int i = 0; i < y.length; ++i) {
+                  yDot[i] = k[i] * y[i];
+              }
+          }
+      };
+
+      DormandPrince853Integrator integrator = new DormandPrince853Integrator(0.0, 100.0,
+                                                                             1.0e-10, 1.0e-10);
+
+      double[] y0   = new double[k.length];
+      for (int i = 0; i < y0.length; ++i) {
+          y0[i] = i + 1;
+      }
+      double[] y    = new double[k.length];
+
+      integrator.setInitialStepSize(60.0);
+      double finalT = integrator.integrate(ode, t0, y0, tEvent, y);
+      Assert.assertEquals(tEvent, finalT, 5.0e-6);
+      for (int i = 0; i < y.length; ++i) {
+          Assert.assertEquals(y0[i] * FastMath.exp(k[i] * (finalT - t0)), y[i], 1.0e-9);
+      }
+
+      integrator.setInitialStepSize(60.0);
+      integrator.addEventHandler(new EventHandler() {
+
+          public void resetState(double t, double[] y) {
+          }
+
+          public double g(double t, double[] y) {
+              return t - tEvent;
+          }
+
+          public int eventOccurred(double t, double[] y, boolean increasing) {
+              Assert.assertEquals(tEvent, t, 5.0e-6);
+              return CONTINUE;
+          }
+      }, Double.POSITIVE_INFINITY, 1.0e-20, 100);
+      finalT = integrator.integrate(ode, t0, y0, tEvent + 120, y);
+      Assert.assertEquals(tEvent + 120, finalT, 5.0e-6);
+      for (int i = 0; i < y.length; ++i) {
+          Assert.assertEquals(y0[i] * FastMath.exp(k[i] * (finalT - t0)), y[i], 1.0e-9);
+      }
+
+  }
+
+  public void testDimensionCheck() {
+    try  {
+      TestProblem1 pb = new TestProblem1();
+      DormandPrince853Integrator integrator = new DormandPrince853Integrator(0.0, 1.0,
+                                                                             1.0e-10, 1.0e-10);
+      integrator.integrate(pb,
+                           0.0, new double[pb.getDimension()+10],
+                           1.0, new double[pb.getDimension()+10]);
+      fail("an exception should have been thrown");
+    } catch(DerivativeException de) {
+      fail("wrong exception caught");
+    } catch(IntegratorException ie) {
+    }
+  }
+
+  public void testNullIntervalCheck() {
+    try  {
+      TestProblem1 pb = new TestProblem1();
+      DormandPrince853Integrator integrator = new DormandPrince853Integrator(0.0, 1.0,
+                                                                             1.0e-10, 1.0e-10);
+      integrator.integrate(pb,
+                           0.0, new double[pb.getDimension()],
+                           0.0, new double[pb.getDimension()]);
+      fail("an exception should have been thrown");
+    } catch(DerivativeException de) {
+      fail("wrong exception caught");
+    } catch(IntegratorException ie) {
+    }
+  }
+
+  public void testMinStep() {
+
+    try {
+      TestProblem1 pb = new TestProblem1();
+      double minStep = 0.1 * (pb.getFinalTime() - pb.getInitialTime());
+      double maxStep = pb.getFinalTime() - pb.getInitialTime();
+      double[] vecAbsoluteTolerance = { 1.0e-15, 1.0e-16 };
+      double[] vecRelativeTolerance = { 1.0e-15, 1.0e-16 };
+
+      FirstOrderIntegrator integ = new DormandPrince853Integrator(minStep, maxStep,
+                                                                  vecAbsoluteTolerance,
+                                                                  vecRelativeTolerance);
+      TestProblemHandler handler = new TestProblemHandler(pb, integ);
+      integ.addStepHandler(handler);
+      integ.integrate(pb,
+                      pb.getInitialTime(), pb.getInitialState(),
+                      pb.getFinalTime(), new double[pb.getDimension()]);
+      fail("an exception should have been thrown");
+    } catch(DerivativeException de) {
+      fail("wrong exception caught");
+    } catch(IntegratorException ie) {
+    }
+
+  }
+
+  public void testIncreasingTolerance()
+    throws DerivativeException, IntegratorException {
+
+    int previousCalls = Integer.MAX_VALUE;
+    for (int i = -12; i < -2; ++i) {
+      TestProblem1 pb = new TestProblem1();
+      double minStep = 0;
+      double maxStep = pb.getFinalTime() - pb.getInitialTime();
+      double scalAbsoluteTolerance = FastMath.pow(10.0, i);
+      double scalRelativeTolerance = 0.01 * scalAbsoluteTolerance;
+
+      FirstOrderIntegrator integ = new DormandPrince853Integrator(minStep, maxStep,
+                                                                  scalAbsoluteTolerance,
+                                                                  scalRelativeTolerance);
+      TestProblemHandler handler = new TestProblemHandler(pb, integ);
+      integ.addStepHandler(handler);
+      integ.integrate(pb,
+                      pb.getInitialTime(), pb.getInitialState(),
+                      pb.getFinalTime(), new double[pb.getDimension()]);
+
+      // the 1.3 factor is only valid for this test
+      // and has been obtained from trial and error
+      // there is no general relation between local and global errors
+      assertTrue(handler.getMaximalValueError() < (1.3 * scalAbsoluteTolerance));
+      assertEquals(0, handler.getMaximalTimeError(), 1.0e-12);
+
+      int calls = pb.getCalls();
+      assertEquals(integ.getEvaluations(), calls);
+      assertTrue(calls <= previousCalls);
+      previousCalls = calls;
+
+    }
+
+  }
+
+  public void testBackward()
+      throws DerivativeException, IntegratorException {
+
+      TestProblem5 pb = new TestProblem5();
+      double minStep = 0;
+      double maxStep = pb.getFinalTime() - pb.getInitialTime();
+      double scalAbsoluteTolerance = 1.0e-8;
+      double scalRelativeTolerance = 0.01 * scalAbsoluteTolerance;
+
+      FirstOrderIntegrator integ = new DormandPrince853Integrator(minStep, maxStep,
+                                                                  scalAbsoluteTolerance,
+                                                                  scalRelativeTolerance);
+      TestProblemHandler handler = new TestProblemHandler(pb, integ);
+      integ.addStepHandler(handler);
+      integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(),
+                      pb.getFinalTime(), new double[pb.getDimension()]);
+
+      assertTrue(handler.getLastError() < 8.1e-8);
+      assertTrue(handler.getMaximalValueError() < 1.1e-7);
+      assertEquals(0, handler.getMaximalTimeError(), 1.0e-12);
+      assertEquals("Dormand-Prince 8 (5, 3)", integ.getName());
+  }
+
+  public void testEvents()
+    throws DerivativeException, IntegratorException {
+
+    TestProblem4 pb = new TestProblem4();
+    double minStep = 0;
+    double maxStep = pb.getFinalTime() - pb.getInitialTime();
+    double scalAbsoluteTolerance = 1.0e-9;
+    double scalRelativeTolerance = 0.01 * scalAbsoluteTolerance;
+
+    FirstOrderIntegrator integ = new DormandPrince853Integrator(minStep, maxStep,
+                                                                scalAbsoluteTolerance,
+                                                                scalRelativeTolerance);
+    TestProblemHandler handler = new TestProblemHandler(pb, integ);
+    integ.addStepHandler(handler);
+    EventHandler[] functions = pb.getEventsHandlers();
+    double convergence = 1.0e-8 * maxStep;
+    for (int l = 0; l < functions.length; ++l) {
+      integ.addEventHandler(functions[l],
+                                 Double.POSITIVE_INFINITY, convergence, 1000);
+    }
+    assertEquals(functions.length, integ.getEventHandlers().size());
+    integ.integrate(pb,
+                    pb.getInitialTime(), pb.getInitialState(),
+                    pb.getFinalTime(), new double[pb.getDimension()]);
+
+    assertTrue(handler.getMaximalValueError() < 5.0e-8);
+    assertEquals(0, handler.getMaximalTimeError(), convergence);
+    assertEquals(12.0, handler.getLastTime(), convergence);
+    integ.clearEventHandlers();
+    assertEquals(0, integ.getEventHandlers().size());
+
+  }
+
+  public void testKepler()
+    throws DerivativeException, IntegratorException {
+
+    final TestProblem3 pb  = new TestProblem3(0.9);
+    double minStep = 0;
+    double maxStep = pb.getFinalTime() - pb.getInitialTime();
+    double scalAbsoluteTolerance = 1.0e-8;
+    double scalRelativeTolerance = scalAbsoluteTolerance;
+
+    FirstOrderIntegrator integ = new DormandPrince853Integrator(minStep, maxStep,
+                                                                scalAbsoluteTolerance,
+                                                                scalRelativeTolerance);
+    integ.addStepHandler(new KeplerHandler(pb));
+    integ.integrate(pb,
+                    pb.getInitialTime(), pb.getInitialState(),
+                    pb.getFinalTime(), new double[pb.getDimension()]);
+
+    assertEquals(integ.getEvaluations(), pb.getCalls());
+    assertTrue(pb.getCalls() < 3300);
+
+  }
+
+  public void testVariableSteps()
+    throws DerivativeException, IntegratorException {
+
+    final TestProblem3 pb  = new TestProblem3(0.9);
+    double minStep = 0;
+    double maxStep = pb.getFinalTime() - pb.getInitialTime();
+    double scalAbsoluteTolerance = 1.0e-8;
+    double scalRelativeTolerance = scalAbsoluteTolerance;
+
+    FirstOrderIntegrator integ = new DormandPrince853Integrator(minStep, maxStep,
+                                                               scalAbsoluteTolerance,
+                                                               scalRelativeTolerance);
+    integ.addStepHandler(new VariableHandler());
+    double stopTime = integ.integrate(pb,
+                                      pb.getInitialTime(), pb.getInitialState(),
+                                      pb.getFinalTime(), new double[pb.getDimension()]);
+    assertEquals(pb.getFinalTime(), stopTime, 1.0e-10);
+    assertEquals("Dormand-Prince 8 (5, 3)", integ.getName());
+  }
+
+  public void testNoDenseOutput()
+    throws DerivativeException, IntegratorException {
+    TestProblem1 pb1 = new TestProblem1();
+    TestProblem1 pb2 = pb1.copy();
+    double minStep = 0.1 * (pb1.getFinalTime() - pb1.getInitialTime());
+    double maxStep = pb1.getFinalTime() - pb1.getInitialTime();
+    double scalAbsoluteTolerance = 1.0e-4;
+    double scalRelativeTolerance = 1.0e-4;
+
+    FirstOrderIntegrator integ = new DormandPrince853Integrator(minStep, maxStep,
+                                                                scalAbsoluteTolerance,
+                                                                scalRelativeTolerance);
+    integ.addStepHandler(DummyStepHandler.getInstance());
+    integ.integrate(pb1,
+                    pb1.getInitialTime(), pb1.getInitialState(),
+                    pb1.getFinalTime(), new double[pb1.getDimension()]);
+    int callsWithoutDenseOutput = pb1.getCalls();
+    assertEquals(integ.getEvaluations(), callsWithoutDenseOutput);
+
+    integ.addStepHandler(new InterpolatingStepHandler());
+    integ.integrate(pb2,
+                    pb2.getInitialTime(), pb2.getInitialState(),
+                    pb2.getFinalTime(), new double[pb2.getDimension()]);
+    int callsWithDenseOutput = pb2.getCalls();
+    assertEquals(integ.getEvaluations(), callsWithDenseOutput);
+
+    assertTrue(callsWithDenseOutput > callsWithoutDenseOutput);
+
+  }
+
+  public void testUnstableDerivative()
+  throws DerivativeException, IntegratorException {
+    final StepProblem stepProblem = new StepProblem(0.0, 1.0, 2.0);
+    FirstOrderIntegrator integ =
+      new DormandPrince853Integrator(0.1, 10, 1.0e-12, 0.0);
+    integ.addEventHandler(stepProblem, 1.0, 1.0e-12, 1000);
+    double[] y = { Double.NaN };
+    integ.integrate(stepProblem, 0.0, new double[] { 0.0 }, 10.0, y);
+    assertEquals(8.0, y[0], 1.0e-12);
+  }
+
+  private static class KeplerHandler implements StepHandler {
+    public KeplerHandler(TestProblem3 pb) {
+      this.pb = pb;
+      reset();
+    }
+    public boolean requiresDenseOutput() {
+      return true;
+    }
+    public void reset() {
+      nbSteps = 0;
+      maxError = 0;
+    }
+    public void handleStep(StepInterpolator interpolator,
+                           boolean isLast)
+    throws DerivativeException {
+
+      ++nbSteps;
+      for (int a = 1; a < 10; ++a) {
+
+        double prev   = interpolator.getPreviousTime();
+        double curr   = interpolator.getCurrentTime();
+        double interp = ((10 - a) * prev + a * curr) / 10;
+        interpolator.setInterpolatedTime(interp);
+
+        double[] interpolatedY = interpolator.getInterpolatedState ();
+        double[] theoreticalY  = pb.computeTheoreticalState(interpolator.getInterpolatedTime());
+        double dx = interpolatedY[0] - theoreticalY[0];
+        double dy = interpolatedY[1] - theoreticalY[1];
+        double error = dx * dx + dy * dy;
+        if (error > maxError) {
+          maxError = error;
+        }
+      }
+      if (isLast) {
+        assertTrue(maxError < 2.4e-10);
+        assertTrue(nbSteps < 150);
+      }
+    }
+    private int nbSteps;
+    private double maxError;
+    private TestProblem3 pb;
+  }
+
+  private static class VariableHandler implements StepHandler {
+    public VariableHandler() {
+      reset();
+    }
+    public boolean requiresDenseOutput() {
+      return false;
+    }
+    public void reset() {
+      firstTime = true;
+      minStep = 0;
+      maxStep = 0;
+    }
+    public void handleStep(StepInterpolator interpolator,
+                           boolean isLast) {
+
+      double step = FastMath.abs(interpolator.getCurrentTime()
+                             - interpolator.getPreviousTime());
+      if (firstTime) {
+        minStep   = FastMath.abs(step);
+        maxStep   = minStep;
+        firstTime = false;
+      } else {
+        if (step < minStep) {
+          minStep = step;
+        }
+        if (step > maxStep) {
+          maxStep = step;
+        }
+      }
+
+      if (isLast) {
+        assertTrue(minStep < (1.0 / 100.0));
+        assertTrue(maxStep > (1.0 / 2.0));
+      }
+    }
+    private boolean firstTime = true;
+    private double  minStep = 0;
+    private double  maxStep = 0;
+  }
+
+  private static class InterpolatingStepHandler implements StepHandler {
+    public boolean requiresDenseOutput() {
+      return true;
+    }
+    public void reset() {
+    }
+    public void handleStep(StepInterpolator interpolator,
+                           boolean isLast)
+    throws DerivativeException {
+      double prev = interpolator.getPreviousTime();
+      double curr = interpolator.getCurrentTime();
+      interpolator.setInterpolatedTime(0.5*(prev + curr));
+    }
+  }
+
+}
diff --git a/src/test/java/org/apache/commons/math/ode/nonstiff/DormandPrince853StepInterpolatorTest.java b/src/test/java/org/apache/commons/math/ode/nonstiff/DormandPrince853StepInterpolatorTest.java
new file mode 100644
index 0000000..fac6580
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/nonstiff/DormandPrince853StepInterpolatorTest.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.ode.nonstiff;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Random;
+
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.ode.ContinuousOutputModel;
+import org.apache.commons.math.ode.IntegratorException;
+import org.apache.commons.math.ode.TestProblem3;
+import org.apache.commons.math.ode.sampling.StepHandler;
+import org.apache.commons.math.ode.sampling.StepInterpolator;
+import org.apache.commons.math.ode.sampling.StepInterpolatorTestUtils;
+import org.apache.commons.math.util.FastMath;
+import org.junit.Test;
+
+public class DormandPrince853StepInterpolatorTest {
+
+  @Test
+  public void derivativesConsistency()
+  throws DerivativeException, IntegratorException {
+    TestProblem3 pb = new TestProblem3(0.1);
+    double minStep = 0;
+    double maxStep = pb.getFinalTime() - pb.getInitialTime();
+    double scalAbsoluteTolerance = 1.0e-8;
+    double scalRelativeTolerance = scalAbsoluteTolerance;
+    DormandPrince853Integrator integ = new DormandPrince853Integrator(minStep, maxStep,
+                                                                      scalAbsoluteTolerance,
+                                                                      scalRelativeTolerance);
+    StepInterpolatorTestUtils.checkDerivativesConsistency(integ, pb, 1.0e-10);
+  }
+
+  @Test
+  public void serialization()
+    throws DerivativeException, IntegratorException,
+           IOException, ClassNotFoundException {
+
+    TestProblem3 pb = new TestProblem3(0.9);
+    double minStep = 0;
+    double maxStep = pb.getFinalTime() - pb.getInitialTime();
+    double scalAbsoluteTolerance = 1.0e-8;
+    double scalRelativeTolerance = scalAbsoluteTolerance;
+    DormandPrince853Integrator integ = new DormandPrince853Integrator(minStep, maxStep,
+                                                                      scalAbsoluteTolerance,
+                                                                      scalRelativeTolerance);
+    integ.addStepHandler(new ContinuousOutputModel());
+    integ.integrate(pb,
+                    pb.getInitialTime(), pb.getInitialState(),
+                    pb.getFinalTime(), new double[pb.getDimension()]);
+
+    ByteArrayOutputStream bos = new ByteArrayOutputStream();
+    ObjectOutputStream    oos = new ObjectOutputStream(bos);
+    for (StepHandler handler : integ.getStepHandlers()) {
+        oos.writeObject(handler);
+    }
+
+    assertTrue(bos.size () > 88000);
+    assertTrue(bos.size () < 89000);
+
+    ByteArrayInputStream  bis = new ByteArrayInputStream(bos.toByteArray());
+    ObjectInputStream     ois = new ObjectInputStream(bis);
+    ContinuousOutputModel cm  = (ContinuousOutputModel) ois.readObject();
+
+    Random random = new Random(347588535632l);
+    double maxError = 0.0;
+    for (int i = 0; i < 1000; ++i) {
+      double r = random.nextDouble();
+      double time = r * pb.getInitialTime() + (1.0 - r) * pb.getFinalTime();
+      cm.setInterpolatedTime(time);
+      double[] interpolatedY = cm.getInterpolatedState ();
+      double[] theoreticalY  = pb.computeTheoreticalState(time);
+      double dx = interpolatedY[0] - theoreticalY[0];
+      double dy = interpolatedY[1] - theoreticalY[1];
+      double error = dx * dx + dy * dy;
+      if (error > maxError) {
+        maxError = error;
+      }
+    }
+
+    assertTrue(maxError < 2.4e-10);
+
+  }
+
+  @Test
+  public void checklone()
+  throws DerivativeException, IntegratorException {
+    TestProblem3 pb = new TestProblem3(0.9);
+    double minStep = 0;
+    double maxStep = pb.getFinalTime() - pb.getInitialTime();
+    double scalAbsoluteTolerance = 1.0e-8;
+    double scalRelativeTolerance = scalAbsoluteTolerance;
+    DormandPrince853Integrator integ = new DormandPrince853Integrator(minStep, maxStep,
+                                                                      scalAbsoluteTolerance,
+                                                                      scalRelativeTolerance);
+    integ.addStepHandler(new StepHandler() {
+        public void handleStep(StepInterpolator interpolator, boolean isLast)
+        throws DerivativeException {
+            StepInterpolator cloned = interpolator.copy();
+            double tA = cloned.getPreviousTime();
+            double tB = cloned.getCurrentTime();
+            double halfStep = FastMath.abs(tB - tA) / 2;
+            assertEquals(interpolator.getPreviousTime(), tA, 1.0e-12);
+            assertEquals(interpolator.getCurrentTime(), tB, 1.0e-12);
+            for (int i = 0; i < 10; ++i) {
+                double t = (i * tB + (9 - i) * tA) / 9;
+                interpolator.setInterpolatedTime(t);
+                assertTrue(FastMath.abs(cloned.getInterpolatedTime() - t) > (halfStep / 10));
+                cloned.setInterpolatedTime(t);
+                assertEquals(t, cloned.getInterpolatedTime(), 1.0e-12);
+                double[] referenceState = interpolator.getInterpolatedState();
+                double[] cloneState     = cloned.getInterpolatedState();
+                for (int j = 0; j < referenceState.length; ++j) {
+                    assertEquals(referenceState[j], cloneState[j], 1.0e-12);
+                }
+            }
+        }
+        public boolean requiresDenseOutput() {
+            return true;
+        }
+        public void reset() {
+        }
+    });
+    integ.integrate(pb,
+            pb.getInitialTime(), pb.getInitialState(),
+            pb.getFinalTime(), new double[pb.getDimension()]);
+
+  }
+
+}
diff --git a/src/test/java/org/apache/commons/math/ode/nonstiff/EulerIntegratorTest.java b/src/test/java/org/apache/commons/math/ode/nonstiff/EulerIntegratorTest.java
new file mode 100644
index 0000000..9b83dff
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/nonstiff/EulerIntegratorTest.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.nonstiff;
+
+import junit.framework.*;
+
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.ode.FirstOrderDifferentialEquations;
+import org.apache.commons.math.ode.FirstOrderIntegrator;
+import org.apache.commons.math.ode.IntegratorException;
+import org.apache.commons.math.ode.TestProblem1;
+import org.apache.commons.math.ode.TestProblem5;
+import org.apache.commons.math.ode.TestProblemAbstract;
+import org.apache.commons.math.ode.TestProblemFactory;
+import org.apache.commons.math.ode.TestProblemHandler;
+import org.apache.commons.math.ode.events.EventHandler;
+import org.apache.commons.math.ode.nonstiff.EulerIntegrator;
+import org.apache.commons.math.ode.sampling.StepHandler;
+import org.apache.commons.math.ode.sampling.StepInterpolator;
+import org.apache.commons.math.util.FastMath;
+
+public class EulerIntegratorTest
+  extends TestCase {
+
+  public EulerIntegratorTest(String name) {
+    super(name);
+  }
+
+  public void testDimensionCheck() {
+    try  {
+      TestProblem1 pb = new TestProblem1();
+      new EulerIntegrator(0.01).integrate(pb,
+                                          0.0, new double[pb.getDimension()+10],
+                                          1.0, new double[pb.getDimension()+10]);
+        fail("an exception should have been thrown");
+    } catch(DerivativeException de) {
+      fail("wrong exception caught");
+    } catch(IntegratorException ie) {
+    }
+  }
+
+  public void testDecreasingSteps()
+    throws DerivativeException, IntegratorException {
+
+    TestProblemAbstract[] problems = TestProblemFactory.getProblems();
+    for (int k = 0; k < problems.length; ++k) {
+
+      double previousValueError = Double.NaN;
+      double previousTimeError = Double.NaN;
+      for (int i = 4; i < 8; ++i) {
+
+        TestProblemAbstract pb  = problems[k].copy();
+        double step = (pb.getFinalTime() - pb.getInitialTime()) * FastMath.pow(2.0, -i);
+
+        FirstOrderIntegrator integ = new EulerIntegrator(step);
+        TestProblemHandler handler = new TestProblemHandler(pb, integ);
+        integ.addStepHandler(handler);
+        EventHandler[] functions = pb.getEventsHandlers();
+        for (int l = 0; l < functions.length; ++l) {
+          integ.addEventHandler(functions[l],
+                                     Double.POSITIVE_INFINITY, 1.0e-6 * step, 1000);
+        }
+        double stopTime = integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(),
+                                          pb.getFinalTime(), new double[pb.getDimension()]);
+        if (functions.length == 0) {
+            assertEquals(pb.getFinalTime(), stopTime, 1.0e-10);
+        }
+
+        double valueError = handler.getMaximalValueError();
+        if (i > 4) {
+          assertTrue(valueError < FastMath.abs(previousValueError));
+        }
+        previousValueError = valueError;
+
+        double timeError = handler.getMaximalTimeError();
+        if (i > 4) {
+          assertTrue(timeError <= FastMath.abs(previousTimeError));
+        }
+        previousTimeError = timeError;
+
+      }
+
+    }
+
+  }
+
+  public void testSmallStep()
+    throws DerivativeException, IntegratorException {
+
+    TestProblem1 pb  = new TestProblem1();
+    double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.001;
+
+    FirstOrderIntegrator integ = new EulerIntegrator(step);
+    TestProblemHandler handler = new TestProblemHandler(pb, integ);
+    integ.addStepHandler(handler);
+    integ.integrate(pb,
+                    pb.getInitialTime(), pb.getInitialState(),
+                    pb.getFinalTime(), new double[pb.getDimension()]);
+
+   assertTrue(handler.getLastError() < 2.0e-4);
+   assertTrue(handler.getMaximalValueError() < 1.0e-3);
+   assertEquals(0, handler.getMaximalTimeError(), 1.0e-12);
+   assertEquals("Euler", integ.getName());
+
+  }
+
+  public void testBigStep()
+    throws DerivativeException, IntegratorException {
+
+    TestProblem1 pb  = new TestProblem1();
+    double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.2;
+
+    FirstOrderIntegrator integ = new EulerIntegrator(step);
+    TestProblemHandler handler = new TestProblemHandler(pb, integ);
+    integ.addStepHandler(handler);
+    integ.integrate(pb,
+                    pb.getInitialTime(), pb.getInitialState(),
+                    pb.getFinalTime(), new double[pb.getDimension()]);
+
+    assertTrue(handler.getLastError() > 0.01);
+    assertTrue(handler.getMaximalValueError() > 0.2);
+    assertEquals(0, handler.getMaximalTimeError(), 1.0e-12);
+
+  }
+
+  public void testBackward()
+      throws DerivativeException, IntegratorException {
+
+      TestProblem5 pb = new TestProblem5();
+      double step = FastMath.abs(pb.getFinalTime() - pb.getInitialTime()) * 0.001;
+
+      FirstOrderIntegrator integ = new EulerIntegrator(step);
+      TestProblemHandler handler = new TestProblemHandler(pb, integ);
+      integ.addStepHandler(handler);
+      integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(),
+                      pb.getFinalTime(), new double[pb.getDimension()]);
+
+      assertTrue(handler.getLastError() < 0.45);
+      assertTrue(handler.getMaximalValueError() < 0.45);
+      assertEquals(0, handler.getMaximalTimeError(), 1.0e-12);
+      assertEquals("Euler", integ.getName());
+  }
+
+  public void testStepSize()
+    throws DerivativeException, IntegratorException {
+      final double step = 1.23456;
+      FirstOrderIntegrator integ = new EulerIntegrator(step);
+      integ.addStepHandler(new StepHandler() {
+        public void handleStep(StepInterpolator interpolator, boolean isLast) {
+            if (! isLast) {
+                assertEquals(step,
+                             interpolator.getCurrentTime() - interpolator.getPreviousTime(),
+                             1.0e-12);
+            }
+        }
+        public boolean requiresDenseOutput() {
+            return false;
+        }
+        public void reset() {
+        }
+      });
+      integ.integrate(new FirstOrderDifferentialEquations() {
+                          private static final long serialVersionUID = 0L;
+                          public void computeDerivatives(double t, double[] y, double[] dot) {
+                              dot[0] = 1.0;
+                          }
+                          public int getDimension() {
+                              return 1;
+                          }
+                      }, 0.0, new double[] { 0.0 }, 5.0, new double[1]);
+  }
+
+}
diff --git a/src/test/java/org/apache/commons/math/ode/nonstiff/EulerStepInterpolatorTest.java b/src/test/java/org/apache/commons/math/ode/nonstiff/EulerStepInterpolatorTest.java
new file mode 100644
index 0000000..7f3324d
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/nonstiff/EulerStepInterpolatorTest.java
@@ -0,0 +1,182 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.ode.nonstiff;
+
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Random;
+
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.ode.ContinuousOutputModel;
+import org.apache.commons.math.ode.IntegratorException;
+import org.apache.commons.math.ode.TestProblem1;
+import org.apache.commons.math.ode.TestProblem3;
+import org.apache.commons.math.ode.sampling.StepHandler;
+import org.apache.commons.math.ode.sampling.StepInterpolatorTestUtils;
+import org.apache.commons.math.util.FastMath;
+import org.junit.Test;
+
+public class EulerStepInterpolatorTest {
+
+  @Test
+  public void noReset() throws DerivativeException {
+
+    double[]   y    =   { 0.0, 1.0, -2.0 };
+    double[][] yDot = { { 1.0, 2.0, -2.0 } };
+    EulerStepInterpolator interpolator = new EulerStepInterpolator();
+    interpolator.reinitialize(new DummyIntegrator(interpolator), y, yDot, true);
+    interpolator.storeTime(0);
+    interpolator.shift();
+    interpolator.storeTime(1);
+
+    double[] result = interpolator.getInterpolatedState();
+    for (int i = 0; i < result.length; ++i) {
+      assertTrue(FastMath.abs(result[i] - y[i]) < 1.0e-10);
+    }
+
+  }
+
+  @Test
+  public void interpolationAtBounds()
+    throws DerivativeException {
+
+    double   t0 = 0;
+    double[] y0 = {0.0, 1.0, -2.0};
+
+    double[] y = y0.clone();
+    double[][] yDot = { new double[y0.length] };
+    EulerStepInterpolator interpolator = new EulerStepInterpolator();
+    interpolator.reinitialize(new DummyIntegrator(interpolator), y, yDot, true);
+    interpolator.storeTime(t0);
+
+    double dt = 1.0;
+    y[0] =  1.0;
+    y[1] =  3.0;
+    y[2] = -4.0;
+    yDot[0][0] = (y[0] - y0[0]) / dt;
+    yDot[0][1] = (y[1] - y0[1]) / dt;
+    yDot[0][2] = (y[2] - y0[2]) / dt;
+    interpolator.shift();
+    interpolator.storeTime(t0 + dt);
+
+    interpolator.setInterpolatedTime(interpolator.getPreviousTime());
+    double[] result = interpolator.getInterpolatedState();
+    for (int i = 0; i < result.length; ++i) {
+      assertTrue(FastMath.abs(result[i] - y0[i]) < 1.0e-10);
+    }
+
+    interpolator.setInterpolatedTime(interpolator.getCurrentTime());
+    result = interpolator.getInterpolatedState();
+    for (int i = 0; i < result.length; ++i) {
+      assertTrue(FastMath.abs(result[i] - y[i]) < 1.0e-10);
+    }
+
+  }
+
+  @Test
+  public void interpolationInside()
+  throws DerivativeException {
+
+    double[]   y    =   { 1.0, 3.0, -4.0 };
+    double[][] yDot = { { 1.0, 2.0, -2.0 } };
+    EulerStepInterpolator interpolator = new EulerStepInterpolator();
+    interpolator.reinitialize(new DummyIntegrator(interpolator), y, yDot, true);
+    interpolator.storeTime(0);
+    interpolator.shift();
+    interpolator.storeTime(1);
+
+    interpolator.setInterpolatedTime(0.1);
+    double[] result = interpolator.getInterpolatedState();
+    assertTrue(FastMath.abs(result[0] - 0.1) < 1.0e-10);
+    assertTrue(FastMath.abs(result[1] - 1.2) < 1.0e-10);
+    assertTrue(FastMath.abs(result[2] + 2.2) < 1.0e-10);
+
+    interpolator.setInterpolatedTime(0.5);
+    result = interpolator.getInterpolatedState();
+    assertTrue(FastMath.abs(result[0] - 0.5) < 1.0e-10);
+    assertTrue(FastMath.abs(result[1] - 2.0) < 1.0e-10);
+    assertTrue(FastMath.abs(result[2] + 3.0) < 1.0e-10);
+
+  }
+
+  @Test
+  public void derivativesConsistency()
+  throws DerivativeException, IntegratorException {
+    TestProblem3 pb = new TestProblem3();
+    double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.001;
+    EulerIntegrator integ = new EulerIntegrator(step);
+    StepInterpolatorTestUtils.checkDerivativesConsistency(integ, pb, 1.0e-10);
+  }
+
+  @Test
+  public void serialization()
+    throws DerivativeException, IntegratorException,
+           IOException, ClassNotFoundException {
+
+    TestProblem1 pb = new TestProblem1();
+    double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.001;
+    EulerIntegrator integ = new EulerIntegrator(step);
+    integ.addStepHandler(new ContinuousOutputModel());
+    integ.integrate(pb,
+                    pb.getInitialTime(), pb.getInitialState(),
+                    pb.getFinalTime(), new double[pb.getDimension()]);
+
+    ByteArrayOutputStream bos = new ByteArrayOutputStream();
+    ObjectOutputStream    oos = new ObjectOutputStream(bos);
+    for (StepHandler handler : integ.getStepHandlers()) {
+        oos.writeObject(handler);
+    }
+
+    ByteArrayInputStream  bis = new ByteArrayInputStream(bos.toByteArray());
+    ObjectInputStream     ois = new ObjectInputStream(bis);
+    ContinuousOutputModel cm  = (ContinuousOutputModel) ois.readObject();
+
+    Random random = new Random(347588535632l);
+    double maxError = 0.0;
+    for (int i = 0; i < 1000; ++i) {
+      double r = random.nextDouble();
+      double time = r * pb.getInitialTime() + (1.0 - r) * pb.getFinalTime();
+      cm.setInterpolatedTime(time);
+      double[] interpolatedY = cm.getInterpolatedState ();
+      double[] theoreticalY  = pb.computeTheoreticalState(time);
+      double dx = interpolatedY[0] - theoreticalY[0];
+      double dy = interpolatedY[1] - theoreticalY[1];
+      double error = dx * dx + dy * dy;
+      if (error > maxError) {
+        maxError = error;
+      }
+    }
+    assertTrue(maxError < 0.001);
+
+  }
+
+  private static class DummyIntegrator extends RungeKuttaIntegrator {
+
+
+      protected DummyIntegrator(RungeKuttaStepInterpolator prototype) {
+          super("dummy", new double[0], new double[0][0], new double[0], prototype, Double.NaN);
+      }
+
+  }
+
+}
diff --git a/src/test/java/org/apache/commons/math/ode/nonstiff/GillIntegratorTest.java b/src/test/java/org/apache/commons/math/ode/nonstiff/GillIntegratorTest.java
new file mode 100644
index 0000000..0814a3f
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/nonstiff/GillIntegratorTest.java
@@ -0,0 +1,243 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.ode.nonstiff;
+
+import junit.framework.*;
+
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.ode.FirstOrderDifferentialEquations;
+import org.apache.commons.math.ode.FirstOrderIntegrator;
+import org.apache.commons.math.ode.IntegratorException;
+import org.apache.commons.math.ode.TestProblem1;
+import org.apache.commons.math.ode.TestProblem3;
+import org.apache.commons.math.ode.TestProblem5;
+import org.apache.commons.math.ode.TestProblemAbstract;
+import org.apache.commons.math.ode.TestProblemFactory;
+import org.apache.commons.math.ode.TestProblemHandler;
+import org.apache.commons.math.ode.events.EventHandler;
+import org.apache.commons.math.ode.nonstiff.GillIntegrator;
+import org.apache.commons.math.ode.sampling.StepHandler;
+import org.apache.commons.math.ode.sampling.StepInterpolator;
+import org.apache.commons.math.util.FastMath;
+
+public class GillIntegratorTest
+  extends TestCase {
+
+  public GillIntegratorTest(String name) {
+    super(name);
+  }
+
+  public void testDimensionCheck() {
+    try  {
+      TestProblem1 pb = new TestProblem1();
+      new GillIntegrator(0.01).integrate(pb,
+                                         0.0, new double[pb.getDimension()+10],
+                                         1.0, new double[pb.getDimension()+10]);
+        fail("an exception should have been thrown");
+    } catch(DerivativeException de) {
+      fail("wrong exception caught");
+    } catch(IntegratorException ie) {
+    }
+  }
+
+  public void testDecreasingSteps()
+    throws DerivativeException, IntegratorException  {
+
+    TestProblemAbstract[] problems = TestProblemFactory.getProblems();
+    for (int k = 0; k < problems.length; ++k) {
+
+      double previousValueError = Double.NaN;
+      double previousTimeError = Double.NaN;
+      for (int i = 5; i < 10; ++i) {
+
+        TestProblemAbstract pb = problems[k].copy();
+        double step = (pb.getFinalTime() - pb.getInitialTime()) * FastMath.pow(2.0, -i);
+
+        FirstOrderIntegrator integ = new GillIntegrator(step);
+        TestProblemHandler handler = new TestProblemHandler(pb, integ);
+        integ.addStepHandler(handler);
+        EventHandler[] functions = pb.getEventsHandlers();
+        for (int l = 0; l < functions.length; ++l) {
+          integ.addEventHandler(functions[l],
+                                     Double.POSITIVE_INFINITY, 1.0e-6 * step, 1000);
+        }
+        double stopTime = integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(),
+                                          pb.getFinalTime(), new double[pb.getDimension()]);
+        if (functions.length == 0) {
+            assertEquals(pb.getFinalTime(), stopTime, 1.0e-10);
+        }
+
+        double valueError = handler.getMaximalValueError();
+        if (i > 5) {
+          assertTrue(valueError < FastMath.abs(previousValueError));
+        }
+        previousValueError = valueError;
+
+        double timeError = handler.getMaximalTimeError();
+        if (i > 5) {
+          assertTrue(timeError <= FastMath.abs(previousTimeError));
+        }
+        previousTimeError = timeError;
+
+      }
+
+    }
+
+  }
+
+  public void testSmallStep()
+    throws DerivativeException, IntegratorException {
+
+    TestProblem1 pb = new TestProblem1();
+    double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.001;
+
+    FirstOrderIntegrator integ = new GillIntegrator(step);
+    TestProblemHandler handler = new TestProblemHandler(pb, integ);
+    integ.addStepHandler(handler);
+    integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(),
+                    pb.getFinalTime(), new double[pb.getDimension()]);
+
+    assertTrue(handler.getLastError() < 2.0e-13);
+    assertTrue(handler.getMaximalValueError() < 4.0e-12);
+    assertEquals(0, handler.getMaximalTimeError(), 1.0e-12);
+    assertEquals("Gill", integ.getName());
+
+  }
+
+  public void testBigStep()
+    throws DerivativeException, IntegratorException {
+
+    TestProblem1 pb = new TestProblem1();
+    double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.2;
+
+    FirstOrderIntegrator integ = new GillIntegrator(step);
+    TestProblemHandler handler = new TestProblemHandler(pb, integ);
+    integ.addStepHandler(handler);
+    integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(),
+                    pb.getFinalTime(), new double[pb.getDimension()]);
+
+    assertTrue(handler.getLastError() > 0.0004);
+    assertTrue(handler.getMaximalValueError() > 0.005);
+    assertEquals(0, handler.getMaximalTimeError(), 1.0e-12);
+
+  }
+
+  public void testBackward()
+      throws DerivativeException, IntegratorException {
+
+      TestProblem5 pb = new TestProblem5();
+      double step = FastMath.abs(pb.getFinalTime() - pb.getInitialTime()) * 0.001;
+
+      FirstOrderIntegrator integ = new GillIntegrator(step);
+      TestProblemHandler handler = new TestProblemHandler(pb, integ);
+      integ.addStepHandler(handler);
+      integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(),
+                      pb.getFinalTime(), new double[pb.getDimension()]);
+
+      assertTrue(handler.getLastError() < 5.0e-10);
+      assertTrue(handler.getMaximalValueError() < 7.0e-10);
+      assertEquals(0, handler.getMaximalTimeError(), 1.0e-12);
+      assertEquals("Gill", integ.getName());
+  }
+
+  public void testKepler()
+    throws DerivativeException, IntegratorException {
+
+    final TestProblem3 pb  = new TestProblem3(0.9);
+    double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.0003;
+
+    FirstOrderIntegrator integ = new GillIntegrator(step);
+    integ.addStepHandler(new KeplerStepHandler(pb));
+    integ.integrate(pb,
+                    pb.getInitialTime(), pb.getInitialState(),
+                    pb.getFinalTime(), new double[pb.getDimension()]);
+  }
+
+  public void testUnstableDerivative()
+  throws DerivativeException, IntegratorException {
+    final StepProblem stepProblem = new StepProblem(0.0, 1.0, 2.0);
+    FirstOrderIntegrator integ = new GillIntegrator(0.3);
+    integ.addEventHandler(stepProblem, 1.0, 1.0e-12, 1000);
+    double[] y = { Double.NaN };
+    integ.integrate(stepProblem, 0.0, new double[] { 0.0 }, 10.0, y);
+    assertEquals(8.0, y[0], 1.0e-12);
+  }
+
+  private static class KeplerStepHandler implements StepHandler {
+    public KeplerStepHandler(TestProblem3 pb) {
+      this.pb = pb;
+      reset();
+    }
+    public boolean requiresDenseOutput() {
+      return false;
+    }
+    public void reset() {
+      maxError = 0;
+    }
+    public void handleStep(StepInterpolator interpolator,
+                           boolean isLast) throws DerivativeException {
+
+      double[] interpolatedY = interpolator.getInterpolatedState();
+      double[] theoreticalY  = pb.computeTheoreticalState(interpolator.getCurrentTime());
+      double dx = interpolatedY[0] - theoreticalY[0];
+      double dy = interpolatedY[1] - theoreticalY[1];
+      double error = dx * dx + dy * dy;
+      if (error > maxError) {
+        maxError = error;
+      }
+      if (isLast) {
+        // even with more than 1000 evaluations per period,
+        // RK4 is not able to integrate such an eccentric
+        // orbit with a good accuracy
+        assertTrue(maxError > 0.001);
+      }
+    }
+    private double maxError;
+    private TestProblem3 pb;
+  }
+
+  public void testStepSize()
+    throws DerivativeException, IntegratorException {
+      final double step = 1.23456;
+      FirstOrderIntegrator integ = new GillIntegrator(step);
+      integ.addStepHandler(new StepHandler() {
+          public void handleStep(StepInterpolator interpolator, boolean isLast) {
+              if (! isLast) {
+                  assertEquals(step,
+                               interpolator.getCurrentTime() - interpolator.getPreviousTime(),
+                               1.0e-12);
+              }
+          }
+          public boolean requiresDenseOutput() {
+              return false;
+          }
+          public void reset() {
+          }
+      });
+      integ.integrate(new FirstOrderDifferentialEquations() {
+          private static final long serialVersionUID = 0L;
+          public void computeDerivatives(double t, double[] y, double[] dot) {
+              dot[0] = 1.0;
+          }
+          public int getDimension() {
+              return 1;
+          }
+      }, 0.0, new double[] { 0.0 }, 5.0, new double[1]);
+  }
+
+}
diff --git a/src/test/java/org/apache/commons/math/ode/nonstiff/GillStepInterpolatorTest.java b/src/test/java/org/apache/commons/math/ode/nonstiff/GillStepInterpolatorTest.java
new file mode 100644
index 0000000..7267850
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/nonstiff/GillStepInterpolatorTest.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.ode.nonstiff;
+
+import static org.junit.Assert.assertTrue;
+
+import java.util.Random;
+import java.io.ByteArrayOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectInputStream;
+import java.io.IOException;
+
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.ode.ContinuousOutputModel;
+import org.apache.commons.math.ode.IntegratorException;
+import org.apache.commons.math.ode.TestProblem3;
+import org.apache.commons.math.ode.nonstiff.GillIntegrator;
+import org.apache.commons.math.ode.sampling.StepHandler;
+import org.apache.commons.math.ode.sampling.StepInterpolatorTestUtils;
+import org.junit.Test;
+
+public class GillStepInterpolatorTest {
+
+  @Test
+  public void testDerivativesConsistency()
+  throws DerivativeException, IntegratorException {
+    TestProblem3 pb = new TestProblem3();
+    double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.001;
+    GillIntegrator integ = new GillIntegrator(step);
+    StepInterpolatorTestUtils.checkDerivativesConsistency(integ, pb, 1.0e-10);
+  }
+
+  @Test
+  public void serialization()
+    throws DerivativeException, IntegratorException,
+           IOException, ClassNotFoundException {
+
+    TestProblem3 pb = new TestProblem3(0.9);
+    double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.0003;
+    GillIntegrator integ = new GillIntegrator(step);
+    integ.addStepHandler(new ContinuousOutputModel());
+    integ.integrate(pb,
+                    pb.getInitialTime(), pb.getInitialState(),
+                    pb.getFinalTime(), new double[pb.getDimension()]);
+
+    ByteArrayOutputStream bos = new ByteArrayOutputStream();
+    ObjectOutputStream    oos = new ObjectOutputStream(bos);
+    for (StepHandler handler : integ.getStepHandlers()) {
+        oos.writeObject(handler);
+    }
+
+    assertTrue(bos.size () > 753000);
+    assertTrue(bos.size () < 754000);
+
+    ByteArrayInputStream  bis = new ByteArrayInputStream(bos.toByteArray());
+    ObjectInputStream     ois = new ObjectInputStream(bis);
+    ContinuousOutputModel cm  = (ContinuousOutputModel) ois.readObject();
+
+    Random random = new Random(347588535632l);
+    double maxError = 0.0;
+    for (int i = 0; i < 1000; ++i) {
+      double r = random.nextDouble();
+      double time = r * pb.getInitialTime() + (1.0 - r) * pb.getFinalTime();
+      cm.setInterpolatedTime(time);
+      double[] interpolatedY = cm.getInterpolatedState ();
+      double[] theoreticalY  = pb.computeTheoreticalState(time);
+      double dx = interpolatedY[0] - theoreticalY[0];
+      double dy = interpolatedY[1] - theoreticalY[1];
+      double error = dx * dx + dy * dy;
+      if (error > maxError) {
+        maxError = error;
+      }
+    }
+
+    assertTrue(maxError < 0.003);
+
+  }
+
+}
diff --git a/src/test/java/org/apache/commons/math/ode/nonstiff/GraggBulirschStoerIntegratorTest.java b/src/test/java/org/apache/commons/math/ode/nonstiff/GraggBulirschStoerIntegratorTest.java
new file mode 100644
index 0000000..1f949a4
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/nonstiff/GraggBulirschStoerIntegratorTest.java
@@ -0,0 +1,365 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.FirstOrderIntegrator;
+import org.apache.commons.math.ode.IntegratorException;
+import org.apache.commons.math.ode.TestProblem1;
+import org.apache.commons.math.ode.TestProblem3;
+import org.apache.commons.math.ode.TestProblem4;
+import org.apache.commons.math.ode.TestProblem5;
+import org.apache.commons.math.ode.TestProblemAbstract;
+import org.apache.commons.math.ode.TestProblemHandler;
+import org.apache.commons.math.ode.events.EventHandler;
+import org.apache.commons.math.ode.nonstiff.GraggBulirschStoerIntegrator;
+import org.apache.commons.math.ode.sampling.StepHandler;
+import org.apache.commons.math.ode.sampling.StepInterpolator;
+import org.apache.commons.math.util.FastMath;
+
+import junit.framework.*;
+
+public class GraggBulirschStoerIntegratorTest
+  extends TestCase {
+
+  public GraggBulirschStoerIntegratorTest(String name) {
+    super(name);
+  }
+
+  public void testDimensionCheck() {
+    try  {
+      TestProblem1 pb = new TestProblem1();
+      AdaptiveStepsizeIntegrator integrator =
+        new GraggBulirschStoerIntegrator(0.0, 1.0, 1.0e-10, 1.0e-10);
+      integrator.integrate(pb,
+                           0.0, new double[pb.getDimension()+10],
+                           1.0, new double[pb.getDimension()+10]);
+      fail("an exception should have been thrown");
+    } catch(DerivativeException de) {
+      fail("wrong exception caught");
+    } catch(IntegratorException ie) {
+    }
+  }
+
+  public void testNullIntervalCheck() {
+    try  {
+      TestProblem1 pb = new TestProblem1();
+      GraggBulirschStoerIntegrator integrator =
+        new GraggBulirschStoerIntegrator(0.0, 1.0, 1.0e-10, 1.0e-10);
+      integrator.integrate(pb,
+                           0.0, new double[pb.getDimension()],
+                           0.0, new double[pb.getDimension()]);
+      fail("an exception should have been thrown");
+    } catch(DerivativeException de) {
+      fail("wrong exception caught");
+    } catch(IntegratorException ie) {
+    }
+  }
+
+  public void testMinStep() {
+
+    try {
+      TestProblem5 pb  = new TestProblem5();
+      double minStep   = 0.1 * FastMath.abs(pb.getFinalTime() - pb.getInitialTime());
+      double maxStep   = FastMath.abs(pb.getFinalTime() - pb.getInitialTime());
+      double[] vecAbsoluteTolerance = { 1.0e-20, 1.0e-21 };
+      double[] vecRelativeTolerance = { 1.0e-20, 1.0e-21 };
+
+      FirstOrderIntegrator integ =
+        new GraggBulirschStoerIntegrator(minStep, maxStep,
+                                         vecAbsoluteTolerance, vecRelativeTolerance);
+      TestProblemHandler handler = new TestProblemHandler(pb, integ);
+      integ.addStepHandler(handler);
+      integ.integrate(pb,
+                      pb.getInitialTime(), pb.getInitialState(),
+                      pb.getFinalTime(), new double[pb.getDimension()]);
+      fail("an exception should have been thrown");
+    } catch(DerivativeException de) {
+      fail("wrong exception caught");
+    } catch(IntegratorException ie) {
+    }
+
+  }
+
+  public void testBackward()
+      throws DerivativeException, IntegratorException {
+
+      TestProblem5 pb = new TestProblem5();
+      double minStep = 0;
+      double maxStep = pb.getFinalTime() - pb.getInitialTime();
+      double scalAbsoluteTolerance = 1.0e-8;
+      double scalRelativeTolerance = 0.01 * scalAbsoluteTolerance;
+
+      FirstOrderIntegrator integ = new GraggBulirschStoerIntegrator(minStep, maxStep,
+                                                                    scalAbsoluteTolerance,
+                                                                    scalRelativeTolerance);
+      TestProblemHandler handler = new TestProblemHandler(pb, integ);
+      integ.addStepHandler(handler);
+      integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(),
+                      pb.getFinalTime(), new double[pb.getDimension()]);
+
+      assertTrue(handler.getLastError() < 7.5e-9);
+      assertTrue(handler.getMaximalValueError() < 8.1e-9);
+      assertEquals(0, handler.getMaximalTimeError(), 1.0e-12);
+      assertEquals("Gragg-Bulirsch-Stoer", integ.getName());
+  }
+
+  public void testIncreasingTolerance()
+    throws DerivativeException, IntegratorException {
+
+    int previousCalls = Integer.MAX_VALUE;
+    for (int i = -12; i < -4; ++i) {
+      TestProblem1 pb     = new TestProblem1();
+      double minStep      = 0;
+      double maxStep      = pb.getFinalTime() - pb.getInitialTime();
+      double absTolerance = FastMath.pow(10.0, i);
+      double relTolerance = absTolerance;
+
+      FirstOrderIntegrator integ =
+        new GraggBulirschStoerIntegrator(minStep, maxStep,
+                                         absTolerance, relTolerance);
+      TestProblemHandler handler = new TestProblemHandler(pb, integ);
+      integ.addStepHandler(handler);
+      integ.integrate(pb,
+                      pb.getInitialTime(), pb.getInitialState(),
+                      pb.getFinalTime(), new double[pb.getDimension()]);
+
+      // the coefficients are only valid for this test
+      // and have been obtained from trial and error
+      // there is no general relation between local and global errors
+      double ratio =  handler.getMaximalValueError() / absTolerance;
+      assertTrue(ratio < 2.4);
+      assertTrue(ratio > 0.02);
+      assertEquals(0, handler.getMaximalTimeError(), 1.0e-12);
+
+      int calls = pb.getCalls();
+      assertEquals(integ.getEvaluations(), calls);
+      assertTrue(calls <= previousCalls);
+      previousCalls = calls;
+
+    }
+
+  }
+
+  public void testIntegratorControls()
+  throws DerivativeException, IntegratorException {
+
+    TestProblem3 pb = new TestProblem3(0.999);
+    GraggBulirschStoerIntegrator integ =
+        new GraggBulirschStoerIntegrator(0, pb.getFinalTime() - pb.getInitialTime(),
+                1.0e-8, 1.0e-10);
+
+    double errorWithDefaultSettings = getMaxError(integ, pb);
+
+    // stability control
+    integ.setStabilityCheck(true, 2, 1, 0.99);
+    assertTrue(errorWithDefaultSettings < getMaxError(integ, pb));
+    integ.setStabilityCheck(true, -1, -1, -1);
+
+    integ.setStepsizeControl(0.5, 0.99, 0.1, 2.5);
+    assertTrue(errorWithDefaultSettings < getMaxError(integ, pb));
+    integ.setStepsizeControl(-1, -1, -1, -1);
+
+    integ.setOrderControl(10, 0.7, 0.95);
+    assertTrue(errorWithDefaultSettings < getMaxError(integ, pb));
+    integ.setOrderControl(-1, -1, -1);
+
+    integ.setInterpolationControl(true, 3);
+    assertTrue(errorWithDefaultSettings < getMaxError(integ, pb));
+    integ.setInterpolationControl(true, -1);
+
+  }
+
+  private double getMaxError(FirstOrderIntegrator integrator, TestProblemAbstract pb)
+    throws DerivativeException, IntegratorException {
+      TestProblemHandler handler = new TestProblemHandler(pb, integrator);
+      integrator.addStepHandler(handler);
+      integrator.integrate(pb,
+                           pb.getInitialTime(), pb.getInitialState(),
+                           pb.getFinalTime(), new double[pb.getDimension()]);
+      return handler.getMaximalValueError();
+  }
+
+  public void testEvents()
+    throws DerivativeException, IntegratorException {
+
+    TestProblem4 pb = new TestProblem4();
+    double minStep = 0;
+    double maxStep = pb.getFinalTime() - pb.getInitialTime();
+    double scalAbsoluteTolerance = 1.0e-10;
+    double scalRelativeTolerance = 0.01 * scalAbsoluteTolerance;
+
+    FirstOrderIntegrator integ = new GraggBulirschStoerIntegrator(minStep, maxStep,
+                                                                  scalAbsoluteTolerance,
+                                                                  scalRelativeTolerance);
+    TestProblemHandler handler = new TestProblemHandler(pb, integ);
+    integ.addStepHandler(handler);
+    EventHandler[] functions = pb.getEventsHandlers();
+    double convergence = 1.0e-8 * maxStep;
+    for (int l = 0; l < functions.length; ++l) {
+      integ.addEventHandler(functions[l], Double.POSITIVE_INFINITY, convergence, 1000);
+    }
+    assertEquals(functions.length, integ.getEventHandlers().size());
+    integ.integrate(pb,
+                    pb.getInitialTime(), pb.getInitialState(),
+                    pb.getFinalTime(), new double[pb.getDimension()]);
+
+    assertTrue(handler.getMaximalValueError() < 5.0e-8);
+    assertEquals(0, handler.getMaximalTimeError(), convergence);
+    assertEquals(12.0, handler.getLastTime(), convergence);
+    integ.clearEventHandlers();
+    assertEquals(0, integ.getEventHandlers().size());
+
+  }
+
+  public void testKepler()
+    throws DerivativeException, IntegratorException {
+
+    final TestProblem3 pb = new TestProblem3(0.9);
+    double minStep        = 0;
+    double maxStep        = pb.getFinalTime() - pb.getInitialTime();
+    double absTolerance   = 1.0e-6;
+    double relTolerance   = 1.0e-6;
+
+    FirstOrderIntegrator integ =
+      new GraggBulirschStoerIntegrator(minStep, maxStep,
+                                       absTolerance, relTolerance);
+    integ.addStepHandler(new KeplerStepHandler(pb));
+    integ.integrate(pb,
+                    pb.getInitialTime(), pb.getInitialState(),
+                    pb.getFinalTime(), new double[pb.getDimension()]);
+
+    assertEquals(integ.getEvaluations(), pb.getCalls());
+    assertTrue(pb.getCalls() < 2150);
+
+  }
+
+  public void testVariableSteps()
+    throws DerivativeException, IntegratorException {
+
+    final TestProblem3 pb = new TestProblem3(0.9);
+    double minStep        = 0;
+    double maxStep        = pb.getFinalTime() - pb.getInitialTime();
+    double absTolerance   = 1.0e-8;
+    double relTolerance   = 1.0e-8;
+    FirstOrderIntegrator integ =
+      new GraggBulirschStoerIntegrator(minStep, maxStep,
+                                       absTolerance, relTolerance);
+    integ.addStepHandler(new VariableStepHandler());
+    double stopTime = integ.integrate(pb,
+                                      pb.getInitialTime(), pb.getInitialState(),
+                                      pb.getFinalTime(), new double[pb.getDimension()]);
+    assertEquals(pb.getFinalTime(), stopTime, 1.0e-10);
+    assertEquals("Gragg-Bulirsch-Stoer", integ.getName());
+  }
+
+  public void testUnstableDerivative()
+    throws DerivativeException, IntegratorException {
+    final StepProblem stepProblem = new StepProblem(0.0, 1.0, 2.0);
+    FirstOrderIntegrator integ =
+      new GraggBulirschStoerIntegrator(0.1, 10, 1.0e-12, 0.0);
+    integ.addEventHandler(stepProblem, 1.0, 1.0e-12, 1000);
+    double[] y = { Double.NaN };
+    integ.integrate(stepProblem, 0.0, new double[] { 0.0 }, 10.0, y);
+    assertEquals(8.0, y[0], 1.0e-12);
+  }
+
+  private static class KeplerStepHandler implements StepHandler {
+    public KeplerStepHandler(TestProblem3 pb) {
+      this.pb = pb;
+      reset();
+    }
+    public boolean requiresDenseOutput() {
+      return true;
+    }
+    public void reset() {
+      nbSteps = 0;
+      maxError = 0;
+    }
+    public void handleStep(StepInterpolator interpolator,
+                           boolean isLast)
+    throws DerivativeException {
+
+      ++nbSteps;
+      for (int a = 1; a < 100; ++a) {
+
+        double prev   = interpolator.getPreviousTime();
+        double curr   = interpolator.getCurrentTime();
+        double interp = ((100 - a) * prev + a * curr) / 100;
+        interpolator.setInterpolatedTime(interp);
+
+        double[] interpolatedY = interpolator.getInterpolatedState ();
+        double[] theoreticalY  = pb.computeTheoreticalState(interpolator.getInterpolatedTime());
+        double dx = interpolatedY[0] - theoreticalY[0];
+        double dy = interpolatedY[1] - theoreticalY[1];
+        double error = dx * dx + dy * dy;
+        if (error > maxError) {
+          maxError = error;
+        }
+      }
+      if (isLast) {
+        assertTrue(maxError < 2.7e-6);
+        assertTrue(nbSteps < 80);
+      }
+    }
+    private int nbSteps;
+    private double maxError;
+    private TestProblem3 pb;
+  }
+
+  public static class VariableStepHandler implements StepHandler {
+    public VariableStepHandler() {
+      reset();
+    }
+    public boolean requiresDenseOutput() {
+      return false;
+    }
+    public void reset() {
+      firstTime = true;
+      minStep = 0;
+      maxStep = 0;
+    }
+    public void handleStep(StepInterpolator interpolator,
+                           boolean isLast) {
+
+      double step = FastMath.abs(interpolator.getCurrentTime()
+                             - interpolator.getPreviousTime());
+      if (firstTime) {
+        minStep   = FastMath.abs(step);
+        maxStep   = minStep;
+        firstTime = false;
+      } else {
+        if (step < minStep) {
+          minStep = step;
+        }
+        if (step > maxStep) {
+          maxStep = step;
+        }
+      }
+
+      if (isLast) {
+        assertTrue(minStep < 8.2e-3);
+        assertTrue(maxStep > 1.7);
+      }
+    }
+    private boolean firstTime;
+    private double  minStep;
+    private double  maxStep;
+  }
+
+}
diff --git a/src/test/java/org/apache/commons/math/ode/nonstiff/GraggBulirschStoerStepInterpolatorTest.java b/src/test/java/org/apache/commons/math/ode/nonstiff/GraggBulirschStoerStepInterpolatorTest.java
new file mode 100644
index 0000000..82d9372
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/nonstiff/GraggBulirschStoerStepInterpolatorTest.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.ode.nonstiff;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Random;
+
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.ode.ContinuousOutputModel;
+import org.apache.commons.math.ode.IntegratorException;
+import org.apache.commons.math.ode.TestProblem3;
+import org.apache.commons.math.ode.sampling.StepHandler;
+import org.apache.commons.math.ode.sampling.StepInterpolator;
+import org.apache.commons.math.ode.sampling.StepInterpolatorTestUtils;
+import org.apache.commons.math.util.FastMath;
+import org.junit.Test;
+
+public class GraggBulirschStoerStepInterpolatorTest {
+
+  @Test
+  public void derivativesConsistency()
+  throws DerivativeException, IntegratorException {
+    TestProblem3 pb = new TestProblem3(0.9);
+    double minStep   = 0;
+    double maxStep   = pb.getFinalTime() - pb.getInitialTime();
+    double absTolerance = 1.0e-8;
+    double relTolerance = 1.0e-8;
+
+    GraggBulirschStoerIntegrator integ =
+      new GraggBulirschStoerIntegrator(minStep, maxStep,
+                                       absTolerance, relTolerance);
+    StepInterpolatorTestUtils.checkDerivativesConsistency(integ, pb, 1.0e-8);
+  }
+
+  @Test
+  public void serialization()
+    throws DerivativeException, IntegratorException,
+           IOException, ClassNotFoundException {
+
+    TestProblem3 pb  = new TestProblem3(0.9);
+    double minStep   = 0;
+    double maxStep   = pb.getFinalTime() - pb.getInitialTime();
+    double absTolerance = 1.0e-8;
+    double relTolerance = 1.0e-8;
+
+    GraggBulirschStoerIntegrator integ =
+      new GraggBulirschStoerIntegrator(minStep, maxStep,
+                                       absTolerance, relTolerance);
+    integ.addStepHandler(new ContinuousOutputModel());
+    integ.integrate(pb,
+                    pb.getInitialTime(), pb.getInitialState(),
+                    pb.getFinalTime(), new double[pb.getDimension()]);
+
+    ByteArrayOutputStream bos = new ByteArrayOutputStream();
+    ObjectOutputStream    oos = new ObjectOutputStream(bos);
+    for (StepHandler handler : integ.getStepHandlers()) {
+        oos.writeObject(handler);
+    }
+
+    assertTrue(bos.size () > 34000);
+    assertTrue(bos.size () < 35000);
+
+    ByteArrayInputStream  bis = new ByteArrayInputStream(bos.toByteArray());
+    ObjectInputStream     ois = new ObjectInputStream(bis);
+    ContinuousOutputModel cm  = (ContinuousOutputModel) ois.readObject();
+
+    Random random = new Random(347588535632l);
+    double maxError = 0.0;
+    for (int i = 0; i < 1000; ++i) {
+      double r = random.nextDouble();
+      double time = r * pb.getInitialTime() + (1.0 - r) * pb.getFinalTime();
+      cm.setInterpolatedTime(time);
+      double[] interpolatedY = cm.getInterpolatedState ();
+      double[] theoreticalY  = pb.computeTheoreticalState(time);
+      double dx = interpolatedY[0] - theoreticalY[0];
+      double dy = interpolatedY[1] - theoreticalY[1];
+      double error = dx * dx + dy * dy;
+      if (error > maxError) {
+        maxError = error;
+      }
+    }
+
+    assertTrue(maxError < 5.0e-10);
+
+  }
+
+  @Test
+  public void checklone()
+  throws DerivativeException, IntegratorException {
+    TestProblem3 pb = new TestProblem3(0.9);
+    double minStep = 0;
+    double maxStep = pb.getFinalTime() - pb.getInitialTime();
+    double scalAbsoluteTolerance = 1.0e-8;
+    double scalRelativeTolerance = scalAbsoluteTolerance;
+    GraggBulirschStoerIntegrator integ = new GraggBulirschStoerIntegrator(minStep, maxStep,
+                                                                          scalAbsoluteTolerance,
+                                                                          scalRelativeTolerance);
+    integ.addStepHandler(new StepHandler() {
+        public void handleStep(StepInterpolator interpolator, boolean isLast)
+        throws DerivativeException {
+            StepInterpolator cloned = interpolator.copy();
+            double tA = cloned.getPreviousTime();
+            double tB = cloned.getCurrentTime();
+            double halfStep = FastMath.abs(tB - tA) / 2;
+            assertEquals(interpolator.getPreviousTime(), tA, 1.0e-12);
+            assertEquals(interpolator.getCurrentTime(), tB, 1.0e-12);
+            for (int i = 0; i < 10; ++i) {
+                double t = (i * tB + (9 - i) * tA) / 9;
+                interpolator.setInterpolatedTime(t);
+                assertTrue(FastMath.abs(cloned.getInterpolatedTime() - t) > (halfStep / 10));
+                cloned.setInterpolatedTime(t);
+                assertEquals(t, cloned.getInterpolatedTime(), 1.0e-12);
+                double[] referenceState = interpolator.getInterpolatedState();
+                double[] cloneState     = cloned.getInterpolatedState();
+                for (int j = 0; j < referenceState.length; ++j) {
+                    assertEquals(referenceState[j], cloneState[j], 1.0e-12);
+                }
+            }
+        }
+        public boolean requiresDenseOutput() {
+            return true;
+        }
+        public void reset() {
+        }
+    });
+    integ.integrate(pb,
+            pb.getInitialTime(), pb.getInitialState(),
+            pb.getFinalTime(), new double[pb.getDimension()]);
+
+  }
+
+}
diff --git a/src/test/java/org/apache/commons/math/ode/nonstiff/HighamHall54IntegratorTest.java b/src/test/java/org/apache/commons/math/ode/nonstiff/HighamHall54IntegratorTest.java
new file mode 100644
index 0000000..156e2bd
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/nonstiff/HighamHall54IntegratorTest.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.ode.nonstiff;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.math.ConvergenceException;
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.ode.FirstOrderDifferentialEquations;
+import org.apache.commons.math.ode.FirstOrderIntegrator;
+import org.apache.commons.math.ode.IntegratorException;
+import org.apache.commons.math.ode.TestProblem1;
+import org.apache.commons.math.ode.TestProblem3;
+import org.apache.commons.math.ode.TestProblem4;
+import org.apache.commons.math.ode.TestProblem5;
+import org.apache.commons.math.ode.TestProblemHandler;
+import org.apache.commons.math.ode.events.EventException;
+import org.apache.commons.math.ode.events.EventHandler;
+import org.apache.commons.math.util.FastMath;
+
+public class HighamHall54IntegratorTest
+  extends TestCase {
+
+  public HighamHall54IntegratorTest(String name) {
+    super(name);
+  }
+
+  public void testWrongDerivative() throws Exception {
+      HighamHall54Integrator integrator =
+          new HighamHall54Integrator(0.0, 1.0, 1.0e-10, 1.0e-10);
+      FirstOrderDifferentialEquations equations =
+          new FirstOrderDifferentialEquations() {
+            private static final long serialVersionUID = -1157081786301178032L;
+            public void computeDerivatives(double t, double[] y, double[] dot)
+            throws DerivativeException {
+            if (t < -0.5) {
+                throw new DerivativeException(LocalizedFormats.SIMPLE_MESSAGE, "oops");
+            } else {
+                throw new DerivativeException(new RuntimeException("oops"));
+           }
+          }
+          public int getDimension() {
+              return 1;
+          }
+      };
+
+      try  {
+        integrator.integrate(equations, -1.0, new double[1], 0.0, new double[1]);
+        fail("an exception should have been thrown");
+      } catch(DerivativeException de) {
+        // expected behavior
+      }
+
+      try  {
+        integrator.integrate(equations, 0.0, new double[1], 1.0, new double[1]);
+        fail("an exception should have been thrown");
+      } catch(DerivativeException de) {
+        // expected behavior
+      }
+
+  }
+
+  public void testMinStep() {
+
+    try {
+      TestProblem1 pb = new TestProblem1();
+      double minStep = 0.1 * (pb.getFinalTime() - pb.getInitialTime());
+      double maxStep = pb.getFinalTime() - pb.getInitialTime();
+      double[] vecAbsoluteTolerance = { 1.0e-15, 1.0e-16 };
+      double[] vecRelativeTolerance = { 1.0e-15, 1.0e-16 };
+
+      FirstOrderIntegrator integ = new HighamHall54Integrator(minStep, maxStep,
+                                                              vecAbsoluteTolerance,
+                                                              vecRelativeTolerance);
+      TestProblemHandler handler = new TestProblemHandler(pb, integ);
+      integ.addStepHandler(handler);
+      integ.integrate(pb,
+                      pb.getInitialTime(), pb.getInitialState(),
+                      pb.getFinalTime(), new double[pb.getDimension()]);
+      fail("an exception should have been thrown");
+    } catch(DerivativeException de) {
+      fail("wrong exception caught");
+    } catch(IntegratorException ie) {
+    }
+
+  }
+
+  public void testIncreasingTolerance()
+    throws DerivativeException, IntegratorException {
+
+    int previousCalls = Integer.MAX_VALUE;
+    for (int i = -12; i < -2; ++i) {
+      TestProblem1 pb = new TestProblem1();
+      double minStep = 0;
+      double maxStep = pb.getFinalTime() - pb.getInitialTime();
+      double scalAbsoluteTolerance = FastMath.pow(10.0, i);
+      double scalRelativeTolerance = 0.01 * scalAbsoluteTolerance;
+
+      FirstOrderIntegrator integ = new HighamHall54Integrator(minStep, maxStep,
+                                                              scalAbsoluteTolerance,
+                                                              scalRelativeTolerance);
+      TestProblemHandler handler = new TestProblemHandler(pb, integ);
+      integ.addStepHandler(handler);
+      integ.integrate(pb,
+                      pb.getInitialTime(), pb.getInitialState(),
+                      pb.getFinalTime(), new double[pb.getDimension()]);
+
+      // the 1.3 factor is only valid for this test
+      // and has been obtained from trial and error
+      // there is no general relation between local and global errors
+      assertTrue(handler.getMaximalValueError() < (1.3 * scalAbsoluteTolerance));
+      assertEquals(0, handler.getMaximalTimeError(), 1.0e-12);
+
+      int calls = pb.getCalls();
+      assertEquals(integ.getEvaluations(), calls);
+      assertTrue(calls <= previousCalls);
+      previousCalls = calls;
+
+    }
+
+  }
+
+  public void testBackward()
+      throws DerivativeException, IntegratorException {
+
+      TestProblem5 pb = new TestProblem5();
+      double minStep = 0;
+      double maxStep = pb.getFinalTime() - pb.getInitialTime();
+      double scalAbsoluteTolerance = 1.0e-8;
+      double scalRelativeTolerance = 0.01 * scalAbsoluteTolerance;
+
+      FirstOrderIntegrator integ = new HighamHall54Integrator(minStep, maxStep,
+                                                              scalAbsoluteTolerance,
+                                                              scalRelativeTolerance);
+      TestProblemHandler handler = new TestProblemHandler(pb, integ);
+      integ.addStepHandler(handler);
+      integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(),
+                      pb.getFinalTime(), new double[pb.getDimension()]);
+
+      assertTrue(handler.getLastError() < 5.0e-7);
+      assertTrue(handler.getMaximalValueError() < 5.0e-7);
+      assertEquals(0, handler.getMaximalTimeError(), 1.0e-12);
+      assertEquals("Higham-Hall 5(4)", integ.getName());
+  }
+
+  public void testEvents()
+    throws DerivativeException, IntegratorException {
+
+    TestProblem4 pb = new TestProblem4();
+    double minStep = 0;
+    double maxStep = pb.getFinalTime() - pb.getInitialTime();
+    double scalAbsoluteTolerance = 1.0e-8;
+    double scalRelativeTolerance = 0.01 * scalAbsoluteTolerance;
+
+    FirstOrderIntegrator integ = new HighamHall54Integrator(minStep, maxStep,
+                                                            scalAbsoluteTolerance,
+                                                            scalRelativeTolerance);
+    TestProblemHandler handler = new TestProblemHandler(pb, integ);
+    integ.addStepHandler(handler);
+    EventHandler[] functions = pb.getEventsHandlers();
+    double convergence = 1.0e-8 * maxStep;
+    for (int l = 0; l < functions.length; ++l) {
+      integ.addEventHandler(functions[l],
+                                 Double.POSITIVE_INFINITY, convergence, 1000);
+    }
+    assertEquals(functions.length, integ.getEventHandlers().size());
+    integ.integrate(pb,
+                    pb.getInitialTime(), pb.getInitialState(),
+                    pb.getFinalTime(), new double[pb.getDimension()]);
+
+    assertTrue(handler.getMaximalValueError() < 1.0e-7);
+    assertEquals(0, handler.getMaximalTimeError(), convergence);
+    assertEquals(12.0, handler.getLastTime(), convergence);
+    integ.clearEventHandlers();
+    assertEquals(0, integ.getEventHandlers().size());
+
+  }
+
+  public void testEventsErrors() throws Exception {
+
+      final TestProblem1 pb = new TestProblem1();
+      double minStep = 0;
+      double maxStep = pb.getFinalTime() - pb.getInitialTime();
+      double scalAbsoluteTolerance = 1.0e-8;
+      double scalRelativeTolerance = 0.01 * scalAbsoluteTolerance;
+
+      FirstOrderIntegrator integ =
+          new HighamHall54Integrator(minStep, maxStep,
+                                     scalAbsoluteTolerance, scalRelativeTolerance);
+      TestProblemHandler handler = new TestProblemHandler(pb, integ);
+      integ.addStepHandler(handler);
+
+      integ.addEventHandler(new EventHandler() {
+        public int eventOccurred(double t, double[] y, boolean increasing) {
+          return EventHandler.CONTINUE;
+        }
+        public double g(double t, double[] y) throws EventException {
+          double middle = (pb.getInitialTime() + pb.getFinalTime()) / 2;
+          double offset = t - middle;
+          if (offset > 0) {
+            throw new EventException(LocalizedFormats.EVALUATION_FAILED, t);
+          }
+          return offset;
+        }
+        public void resetState(double t, double[] y) {
+        }
+        private static final long serialVersionUID = 935652725339916361L;
+      }, Double.POSITIVE_INFINITY, 1.0e-8 * maxStep, 1000);
+
+      try {
+        integ.integrate(pb,
+                        pb.getInitialTime(), pb.getInitialState(),
+                        pb.getFinalTime(), new double[pb.getDimension()]);
+        fail("an exception should have been thrown");
+      } catch (IntegratorException ie) {
+        // expected behavior
+      }
+
+  }
+
+  public void testEventsNoConvergence() throws Exception {
+
+    final TestProblem1 pb = new TestProblem1();
+    double minStep = 0;
+    double maxStep = pb.getFinalTime() - pb.getInitialTime();
+    double scalAbsoluteTolerance = 1.0e-8;
+    double scalRelativeTolerance = 0.01 * scalAbsoluteTolerance;
+
+    FirstOrderIntegrator integ =
+        new HighamHall54Integrator(minStep, maxStep,
+                                   scalAbsoluteTolerance, scalRelativeTolerance);
+    TestProblemHandler handler = new TestProblemHandler(pb, integ);
+    integ.addStepHandler(handler);
+
+    integ.addEventHandler(new EventHandler() {
+      public int eventOccurred(double t, double[] y, boolean increasing) {
+        return EventHandler.CONTINUE;
+      }
+      public double g(double t, double[] y) {
+        double middle = (pb.getInitialTime() + pb.getFinalTime()) / 2;
+        double offset = t - middle;
+        return (offset > 0) ? (offset + 0.5) : (offset - 0.5);
+      }
+      public void resetState(double t, double[] y) {
+      }
+      private static final long serialVersionUID = 935652725339916361L;
+    }, Double.POSITIVE_INFINITY, 1.0e-8 * maxStep, 3);
+
+    try {
+      integ.integrate(pb,
+                      pb.getInitialTime(), pb.getInitialState(),
+                      pb.getFinalTime(), new double[pb.getDimension()]);
+      fail("an exception should have been thrown");
+    } catch (IntegratorException ie) {
+       assertTrue(ie.getCause() != null);
+       assertTrue(ie.getCause() instanceof ConvergenceException);
+    }
+
+}
+
+  public void testSanityChecks() throws Exception {
+      final TestProblem3 pb  = new TestProblem3(0.9);
+      double minStep = 0;
+      double maxStep = pb.getFinalTime() - pb.getInitialTime();
+
+      try {
+        FirstOrderIntegrator integ =
+            new HighamHall54Integrator(minStep, maxStep, new double[4], new double[4]);
+        integ.integrate(pb, pb.getInitialTime(), new double[6],
+                        pb.getFinalTime(), new double[pb.getDimension()]);
+        fail("an exception should have been thrown");
+      } catch (IntegratorException ie) {
+        // expected behavior
+      }
+
+      try {
+        FirstOrderIntegrator integ =
+            new HighamHall54Integrator(minStep, maxStep, new double[4], new double[4]);
+        integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(),
+                        pb.getFinalTime(), new double[6]);
+        fail("an exception should have been thrown");
+      } catch (IntegratorException ie) {
+        // expected behavior
+      }
+
+      try {
+        FirstOrderIntegrator integ =
+            new HighamHall54Integrator(minStep, maxStep, new double[2], new double[4]);
+        integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(),
+                        pb.getFinalTime(), new double[pb.getDimension()]);
+        fail("an exception should have been thrown");
+      } catch (IntegratorException ie) {
+        // expected behavior
+      }
+
+      try {
+        FirstOrderIntegrator integ =
+            new HighamHall54Integrator(minStep, maxStep, new double[4], new double[2]);
+        integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(),
+                        pb.getFinalTime(), new double[pb.getDimension()]);
+        fail("an exception should have been thrown");
+      } catch (IntegratorException ie) {
+        // expected behavior
+      }
+
+      try {
+        FirstOrderIntegrator integ =
+            new HighamHall54Integrator(minStep, maxStep, new double[4], new double[4]);
+        integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(),
+                        pb.getInitialTime(), new double[pb.getDimension()]);
+        fail("an exception should have been thrown");
+      } catch (IntegratorException ie) {
+        // expected behavior
+      }
+
+  }
+
+  public void testKepler()
+    throws DerivativeException, IntegratorException {
+
+    final TestProblem3 pb  = new TestProblem3(0.9);
+    double minStep = 0;
+    double maxStep = pb.getFinalTime() - pb.getInitialTime();
+    double[] vecAbsoluteTolerance = { 1.0e-8, 1.0e-8, 1.0e-10, 1.0e-10 };
+    double[] vecRelativeTolerance = { 1.0e-10, 1.0e-10, 1.0e-8, 1.0e-8 };
+
+    FirstOrderIntegrator integ = new HighamHall54Integrator(minStep, maxStep,
+                                                            vecAbsoluteTolerance,
+                                                            vecRelativeTolerance);
+    TestProblemHandler handler = new TestProblemHandler(pb, integ); 
+    integ.addStepHandler(handler);
+    integ.integrate(pb,
+                    pb.getInitialTime(), pb.getInitialState(),
+                    pb.getFinalTime(), new double[pb.getDimension()]);
+    assertEquals(0.0, handler.getMaximalValueError(), 1.5e-4);
+    assertEquals("Higham-Hall 5(4)", integ.getName());
+  }
+
+}
diff --git a/src/test/java/org/apache/commons/math/ode/nonstiff/HighamHall54StepInterpolatorTest.java b/src/test/java/org/apache/commons/math/ode/nonstiff/HighamHall54StepInterpolatorTest.java
new file mode 100644
index 0000000..4b7136c
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/nonstiff/HighamHall54StepInterpolatorTest.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.ode.nonstiff;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Random;
+
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.ode.ContinuousOutputModel;
+import org.apache.commons.math.ode.IntegratorException;
+import org.apache.commons.math.ode.TestProblem3;
+import org.apache.commons.math.ode.sampling.StepHandler;
+import org.apache.commons.math.ode.sampling.StepInterpolator;
+import org.apache.commons.math.ode.sampling.StepInterpolatorTestUtils;
+import org.apache.commons.math.util.FastMath;
+import org.junit.Test;
+
+public class HighamHall54StepInterpolatorTest {
+
+  @Test
+  public void derivativesConsistency()
+  throws DerivativeException, IntegratorException {
+    TestProblem3 pb = new TestProblem3(0.1);
+    double minStep = 0;
+    double maxStep = pb.getFinalTime() - pb.getInitialTime();
+    double scalAbsoluteTolerance = 1.0e-8;
+    double scalRelativeTolerance = scalAbsoluteTolerance;
+    HighamHall54Integrator integ = new HighamHall54Integrator(minStep, maxStep,
+                                                              scalAbsoluteTolerance,
+                                                              scalRelativeTolerance);
+    StepInterpolatorTestUtils.checkDerivativesConsistency(integ, pb, 1.1e-10);
+  }
+
+  @Test
+  public void serialization()
+    throws DerivativeException, IntegratorException,
+           IOException, ClassNotFoundException {
+
+    TestProblem3 pb = new TestProblem3(0.9);
+    double minStep = 0;
+    double maxStep = pb.getFinalTime() - pb.getInitialTime();
+    double scalAbsoluteTolerance = 1.0e-8;
+    double scalRelativeTolerance = scalAbsoluteTolerance;
+    HighamHall54Integrator integ = new HighamHall54Integrator(minStep, maxStep,
+                                                              scalAbsoluteTolerance,
+                                                              scalRelativeTolerance);
+    integ.addStepHandler(new ContinuousOutputModel());
+    integ.integrate(pb,
+                    pb.getInitialTime(), pb.getInitialState(),
+                    pb.getFinalTime(), new double[pb.getDimension()]);
+
+    ByteArrayOutputStream bos = new ByteArrayOutputStream();
+    ObjectOutputStream    oos = new ObjectOutputStream(bos);
+    for (StepHandler handler : integ.getStepHandlers()) {
+        oos.writeObject(handler);
+    }
+
+    assertTrue(bos.size () > 167000);
+    assertTrue(bos.size () < 168000);
+
+    ByteArrayInputStream  bis = new ByteArrayInputStream(bos.toByteArray());
+    ObjectInputStream     ois = new ObjectInputStream(bis);
+    ContinuousOutputModel cm  = (ContinuousOutputModel) ois.readObject();
+
+    Random random = new Random(347588535632l);
+    double maxError = 0.0;
+    for (int i = 0; i < 1000; ++i) {
+      double r = random.nextDouble();
+      double time = r * pb.getInitialTime() + (1.0 - r) * pb.getFinalTime();
+      cm.setInterpolatedTime(time);
+      double[] interpolatedY = cm.getInterpolatedState ();
+      double[] theoreticalY  = pb.computeTheoreticalState(time);
+      double dx = interpolatedY[0] - theoreticalY[0];
+      double dy = interpolatedY[1] - theoreticalY[1];
+      double error = dx * dx + dy * dy;
+      if (error > maxError) {
+        maxError = error;
+      }
+    }
+
+    assertTrue(maxError < 1.6e-10);
+
+  }
+
+  @Test
+  public void checkClone()
+  throws DerivativeException, IntegratorException {
+    TestProblem3 pb = new TestProblem3(0.9);
+    double minStep = 0;
+    double maxStep = pb.getFinalTime() - pb.getInitialTime();
+    double scalAbsoluteTolerance = 1.0e-8;
+    double scalRelativeTolerance = scalAbsoluteTolerance;
+    HighamHall54Integrator integ = new HighamHall54Integrator(minStep, maxStep,
+                                                              scalAbsoluteTolerance,
+                                                              scalRelativeTolerance);
+    integ.addStepHandler(new StepHandler() {
+        public void handleStep(StepInterpolator interpolator, boolean isLast)
+        throws DerivativeException {
+            StepInterpolator cloned = interpolator.copy();
+            double tA = cloned.getPreviousTime();
+            double tB = cloned.getCurrentTime();
+            double halfStep = FastMath.abs(tB - tA) / 2;
+            assertEquals(interpolator.getPreviousTime(), tA, 1.0e-12);
+            assertEquals(interpolator.getCurrentTime(), tB, 1.0e-12);
+            for (int i = 0; i < 10; ++i) {
+                double t = (i * tB + (9 - i) * tA) / 9;
+                interpolator.setInterpolatedTime(t);
+                assertTrue(FastMath.abs(cloned.getInterpolatedTime() - t) > (halfStep / 10));
+                cloned.setInterpolatedTime(t);
+                assertEquals(t, cloned.getInterpolatedTime(), 1.0e-12);
+                double[] referenceState = interpolator.getInterpolatedState();
+                double[] cloneState     = cloned.getInterpolatedState();
+                for (int j = 0; j < referenceState.length; ++j) {
+                    assertEquals(referenceState[j], cloneState[j], 1.0e-12);
+                }
+            }
+        }
+        public boolean requiresDenseOutput() {
+            return true;
+        }
+        public void reset() {
+        }
+    });
+    integ.integrate(pb,
+            pb.getInitialTime(), pb.getInitialState(),
+            pb.getFinalTime(), new double[pb.getDimension()]);
+
+  }
+
+}
diff --git a/src/test/java/org/apache/commons/math/ode/nonstiff/MidpointIntegratorTest.java b/src/test/java/org/apache/commons/math/ode/nonstiff/MidpointIntegratorTest.java
new file mode 100644
index 0000000..c85143e
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/nonstiff/MidpointIntegratorTest.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.nonstiff;
+
+import junit.framework.*;
+
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.ode.FirstOrderDifferentialEquations;
+import org.apache.commons.math.ode.FirstOrderIntegrator;
+import org.apache.commons.math.ode.IntegratorException;
+import org.apache.commons.math.ode.TestProblem1;
+import org.apache.commons.math.ode.TestProblem5;
+import org.apache.commons.math.ode.TestProblemAbstract;
+import org.apache.commons.math.ode.TestProblemFactory;
+import org.apache.commons.math.ode.TestProblemHandler;
+import org.apache.commons.math.ode.events.EventHandler;
+import org.apache.commons.math.ode.nonstiff.MidpointIntegrator;
+import org.apache.commons.math.ode.sampling.StepHandler;
+import org.apache.commons.math.ode.sampling.StepInterpolator;
+import org.apache.commons.math.util.FastMath;
+
+public class MidpointIntegratorTest
+  extends TestCase {
+
+  public MidpointIntegratorTest(String name) {
+    super(name);
+  }
+
+  public void testDimensionCheck() {
+    try  {
+      TestProblem1 pb = new TestProblem1();
+      new MidpointIntegrator(0.01).integrate(pb,
+                                             0.0, new double[pb.getDimension()+10],
+                                             1.0, new double[pb.getDimension()+10]);
+        fail("an exception should have been thrown");
+    } catch(DerivativeException de) {
+      fail("wrong exception caught");
+    } catch(IntegratorException ie) {
+    }
+  }
+
+  public void testDecreasingSteps()
+    throws DerivativeException, IntegratorException  {
+
+    TestProblemAbstract[] problems = TestProblemFactory.getProblems();
+    for (int k = 0; k < problems.length; ++k) {
+
+      double previousValueError = Double.NaN;
+      double previousTimeError = Double.NaN;
+      for (int i = 4; i < 10; ++i) {
+
+        TestProblemAbstract pb = problems[k].copy();
+        double step = (pb.getFinalTime() - pb.getInitialTime()) * FastMath.pow(2.0, -i);
+        FirstOrderIntegrator integ = new MidpointIntegrator(step);
+        TestProblemHandler handler = new TestProblemHandler(pb, integ);
+        integ.addStepHandler(handler);
+        EventHandler[] functions = pb.getEventsHandlers();
+        for (int l = 0; l < functions.length; ++l) {
+          integ.addEventHandler(functions[l],
+                                     Double.POSITIVE_INFINITY, 1.0e-6 * step, 1000);
+        }
+        double stopTime = integ.integrate(pb,
+                                          pb.getInitialTime(), pb.getInitialState(),
+                                          pb.getFinalTime(), new double[pb.getDimension()]);
+        if (functions.length == 0) {
+            assertEquals(pb.getFinalTime(), stopTime, 1.0e-10);
+        }
+
+        double valueError = handler.getMaximalValueError();
+        if (i > 4) {
+          assertTrue(valueError < FastMath.abs(previousValueError));
+        }
+        previousValueError = valueError;
+
+        double timeError = handler.getMaximalTimeError();
+        if (i > 4) {
+          assertTrue(timeError <= FastMath.abs(previousTimeError));
+        }
+        previousTimeError = timeError;
+
+      }
+
+    }
+
+  }
+
+  public void testSmallStep()
+    throws DerivativeException, IntegratorException {
+
+    TestProblem1 pb  = new TestProblem1();
+    double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.001;
+
+    FirstOrderIntegrator integ = new MidpointIntegrator(step);
+    TestProblemHandler handler = new TestProblemHandler(pb, integ);
+    integ.addStepHandler(handler);
+    integ.integrate(pb,
+                    pb.getInitialTime(), pb.getInitialState(),
+                    pb.getFinalTime(), new double[pb.getDimension()]);
+
+    assertTrue(handler.getLastError() < 2.0e-7);
+    assertTrue(handler.getMaximalValueError() < 1.0e-6);
+    assertEquals(0, handler.getMaximalTimeError(), 1.0e-12);
+    assertEquals("midpoint", integ.getName());
+
+  }
+
+  public void testBigStep()
+    throws DerivativeException, IntegratorException {
+
+    TestProblem1 pb  = new TestProblem1();
+    double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.2;
+
+    FirstOrderIntegrator integ = new MidpointIntegrator(step);
+    TestProblemHandler handler = new TestProblemHandler(pb, integ);
+    integ.addStepHandler(handler);
+    integ.integrate(pb,
+                    pb.getInitialTime(), pb.getInitialState(),
+                    pb.getFinalTime(), new double[pb.getDimension()]);
+
+    assertTrue(handler.getLastError() > 0.01);
+    assertTrue(handler.getMaximalValueError() > 0.05);
+    assertEquals(0, handler.getMaximalTimeError(), 1.0e-12);
+
+  }
+
+  public void testBackward()
+      throws DerivativeException, IntegratorException {
+
+      TestProblem5 pb = new TestProblem5();
+      double step = FastMath.abs(pb.getFinalTime() - pb.getInitialTime()) * 0.001;
+
+      FirstOrderIntegrator integ = new MidpointIntegrator(step);
+      TestProblemHandler handler = new TestProblemHandler(pb, integ);
+      integ.addStepHandler(handler);
+      integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(),
+                      pb.getFinalTime(), new double[pb.getDimension()]);
+
+      assertTrue(handler.getLastError() < 6.0e-4);
+      assertTrue(handler.getMaximalValueError() < 6.0e-4);
+      assertEquals(0, handler.getMaximalTimeError(), 1.0e-12);
+      assertEquals("midpoint", integ.getName());
+  }
+
+  public void testStepSize()
+    throws DerivativeException, IntegratorException {
+      final double step = 1.23456;
+      FirstOrderIntegrator integ = new MidpointIntegrator(step);
+      integ.addStepHandler(new StepHandler() {
+          public void handleStep(StepInterpolator interpolator, boolean isLast) {
+              if (! isLast) {
+                  assertEquals(step,
+                               interpolator.getCurrentTime() - interpolator.getPreviousTime(),
+                               1.0e-12);
+              }
+          }
+          public boolean requiresDenseOutput() {
+              return false;
+          }
+          public void reset() {
+          }
+      });
+      integ.integrate(new FirstOrderDifferentialEquations() {
+          private static final long serialVersionUID = 0L;
+          public void computeDerivatives(double t, double[] y, double[] dot) {
+              dot[0] = 1.0;
+          }
+          public int getDimension() {
+              return 1;
+          }
+      }, 0.0, new double[] { 0.0 }, 5.0, new double[1]);
+  }
+
+}
diff --git a/src/test/java/org/apache/commons/math/ode/nonstiff/MidpointStepInterpolatorTest.java b/src/test/java/org/apache/commons/math/ode/nonstiff/MidpointStepInterpolatorTest.java
new file mode 100644
index 0000000..7ae2d3a
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/nonstiff/MidpointStepInterpolatorTest.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.ode.nonstiff;
+
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Random;
+
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.ode.ContinuousOutputModel;
+import org.apache.commons.math.ode.IntegratorException;
+import org.apache.commons.math.ode.TestProblem1;
+import org.apache.commons.math.ode.TestProblem3;
+import org.apache.commons.math.ode.sampling.StepHandler;
+import org.apache.commons.math.ode.sampling.StepInterpolatorTestUtils;
+import org.junit.Test;
+
+public class MidpointStepInterpolatorTest {
+
+  @Test
+  public void testDerivativesConsistency()
+  throws DerivativeException, IntegratorException {
+    TestProblem3 pb = new TestProblem3();
+    double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.001;
+    MidpointIntegrator integ = new MidpointIntegrator(step);
+    StepInterpolatorTestUtils.checkDerivativesConsistency(integ, pb, 1.0e-10);
+  }
+
+  @Test
+  public void serialization()
+    throws DerivativeException, IntegratorException,
+           IOException, ClassNotFoundException {
+
+    TestProblem1 pb = new TestProblem1();
+    double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.001;
+    MidpointIntegrator integ = new MidpointIntegrator(step);
+    integ.addStepHandler(new ContinuousOutputModel());
+    integ.integrate(pb,
+                    pb.getInitialTime(), pb.getInitialState(),
+                    pb.getFinalTime(), new double[pb.getDimension()]);
+
+    ByteArrayOutputStream bos = new ByteArrayOutputStream();
+    ObjectOutputStream    oos = new ObjectOutputStream(bos);
+    for (StepHandler handler : integ.getStepHandlers()) {
+        oos.writeObject(handler);
+    }
+
+    assertTrue(bos.size () > 114000);
+    assertTrue(bos.size () < 115000);
+
+    ByteArrayInputStream  bis = new ByteArrayInputStream(bos.toByteArray());
+    ObjectInputStream     ois = new ObjectInputStream(bis);
+    ContinuousOutputModel cm  = (ContinuousOutputModel) ois.readObject();
+
+    Random random = new Random(347588535632l);
+    double maxError = 0.0;
+    for (int i = 0; i < 1000; ++i) {
+      double r = random.nextDouble();
+      double time = r * pb.getInitialTime() + (1.0 - r) * pb.getFinalTime();
+      cm.setInterpolatedTime(time);
+      double[] interpolatedY = cm.getInterpolatedState ();
+      double[] theoreticalY  = pb.computeTheoreticalState(time);
+      double dx = interpolatedY[0] - theoreticalY[0];
+      double dy = interpolatedY[1] - theoreticalY[1];
+      double error = dx * dx + dy * dy;
+      if (error > maxError) {
+        maxError = error;
+      }
+    }
+
+    assertTrue(maxError < 1.0e-6);
+
+  }
+
+}
diff --git a/src/test/java/org/apache/commons/math/ode/nonstiff/StepProblem.java b/src/test/java/org/apache/commons/math/ode/nonstiff/StepProblem.java
new file mode 100644
index 0000000..dc5420b
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/nonstiff/StepProblem.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.nonstiff;
+
+import org.apache.commons.math.ode.FirstOrderDifferentialEquations;
+import org.apache.commons.math.ode.events.EventHandler;
+
+
+public class StepProblem
+  implements FirstOrderDifferentialEquations, EventHandler {
+
+  public StepProblem(double rateBefore, double rateAfter,
+                     double switchTime) {
+    this.rateAfter  = rateAfter;
+    this.switchTime = switchTime;
+    setRate(rateBefore);
+  }
+
+  public void computeDerivatives(double t, double[] y, double[] yDot) {
+    yDot[0] = rate;
+  }
+
+  public int getDimension() {
+    return 1;
+  }
+
+  public void setRate(double rate) {
+    this.rate = rate;
+  }
+
+  public int eventOccurred(double t, double[] y, boolean increasing) {
+    setRate(rateAfter);
+    return RESET_DERIVATIVES;
+  }
+
+  public double g(double t, double[] y) {
+    return t - switchTime;
+  }
+
+  public void resetState(double t, double[] y) {
+  }
+
+  private double rate;
+  private double rateAfter;
+  private double switchTime;
+
+  private static final long serialVersionUID = 7590601995477504318L;
+
+}
diff --git a/src/test/java/org/apache/commons/math/ode/nonstiff/ThreeEighthesIntegratorTest.java b/src/test/java/org/apache/commons/math/ode/nonstiff/ThreeEighthesIntegratorTest.java
new file mode 100644
index 0000000..9f24dc9
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/nonstiff/ThreeEighthesIntegratorTest.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.ode.nonstiff;
+
+import junit.framework.*;
+
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.ode.FirstOrderDifferentialEquations;
+import org.apache.commons.math.ode.FirstOrderIntegrator;
+import org.apache.commons.math.ode.IntegratorException;
+import org.apache.commons.math.ode.TestProblem1;
+import org.apache.commons.math.ode.TestProblem3;
+import org.apache.commons.math.ode.TestProblem5;
+import org.apache.commons.math.ode.TestProblemAbstract;
+import org.apache.commons.math.ode.TestProblemFactory;
+import org.apache.commons.math.ode.TestProblemHandler;
+import org.apache.commons.math.ode.events.EventHandler;
+import org.apache.commons.math.ode.nonstiff.ThreeEighthesIntegrator;
+import org.apache.commons.math.ode.sampling.StepHandler;
+import org.apache.commons.math.ode.sampling.StepInterpolator;
+import org.apache.commons.math.util.FastMath;
+
+public class ThreeEighthesIntegratorTest
+  extends TestCase {
+
+  public ThreeEighthesIntegratorTest(String name) {
+    super(name);
+  }
+
+  public void testDimensionCheck() {
+    try  {
+      TestProblem1 pb = new TestProblem1();
+      new ThreeEighthesIntegrator(0.01).integrate(pb,
+                                                  0.0, new double[pb.getDimension()+10],
+                                                  1.0, new double[pb.getDimension()+10]);
+        fail("an exception should have been thrown");
+    } catch(DerivativeException de) {
+      fail("wrong exception caught");
+    } catch(IntegratorException ie) {
+    }
+  }
+
+  public void testDecreasingSteps()
+    throws DerivativeException, IntegratorException  {
+
+    TestProblemAbstract[] problems = TestProblemFactory.getProblems();
+    for (int k = 0; k < problems.length; ++k) {
+
+      double previousValueError = Double.NaN;
+      double previousTimeError = Double.NaN;
+      for (int i = 4; i < 10; ++i) {
+
+        TestProblemAbstract pb = problems[k].copy();
+        double step = (pb.getFinalTime() - pb.getInitialTime()) * FastMath.pow(2.0, -i);
+
+        FirstOrderIntegrator integ = new ThreeEighthesIntegrator(step);
+        TestProblemHandler handler = new TestProblemHandler(pb, integ);
+        integ.addStepHandler(handler);
+        EventHandler[] functions = pb.getEventsHandlers();
+        for (int l = 0; l < functions.length; ++l) {
+          integ.addEventHandler(functions[l],
+                                     Double.POSITIVE_INFINITY, 1.0e-6 * step, 1000);
+        }
+        double stopTime = integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(),
+                                          pb.getFinalTime(), new double[pb.getDimension()]);
+        if (functions.length == 0) {
+            assertEquals(pb.getFinalTime(), stopTime, 1.0e-10);
+        }
+
+        double error = handler.getMaximalValueError();
+        if (i > 4) {
+          assertTrue(error < FastMath.abs(previousValueError));
+        }
+        previousValueError = error;
+
+        double timeError = handler.getMaximalTimeError();
+        if (i > 4) {
+          assertTrue(timeError <= FastMath.abs(previousTimeError));
+        }
+        previousTimeError = timeError;
+
+      }
+
+    }
+
+  }
+
+ public void testSmallStep()
+    throws DerivativeException, IntegratorException {
+
+    TestProblem1 pb = new TestProblem1();
+    double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.001;
+
+    FirstOrderIntegrator integ = new ThreeEighthesIntegrator(step);
+    TestProblemHandler handler = new TestProblemHandler(pb, integ);
+    integ.addStepHandler(handler);
+    integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(),
+                    pb.getFinalTime(), new double[pb.getDimension()]);
+
+    assertTrue(handler.getLastError() < 2.0e-13);
+    assertTrue(handler.getMaximalValueError() < 4.0e-12);
+    assertEquals(0, handler.getMaximalTimeError(), 1.0e-12);
+    assertEquals("3/8", integ.getName());
+
+  }
+
+  public void testBigStep()
+    throws DerivativeException, IntegratorException {
+
+    TestProblem1 pb = new TestProblem1();
+    double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.2;
+
+    FirstOrderIntegrator integ = new ThreeEighthesIntegrator(step);
+    TestProblemHandler handler = new TestProblemHandler(pb, integ);
+    integ.addStepHandler(handler);
+    integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(),
+                    pb.getFinalTime(), new double[pb.getDimension()]);
+
+    assertTrue(handler.getLastError() > 0.0004);
+    assertTrue(handler.getMaximalValueError() > 0.005);
+    assertEquals(0, handler.getMaximalTimeError(), 1.0e-12);
+
+  }
+
+  public void testBackward()
+      throws DerivativeException, IntegratorException {
+
+      TestProblem5 pb = new TestProblem5();
+      double step = FastMath.abs(pb.getFinalTime() - pb.getInitialTime()) * 0.001;
+
+      FirstOrderIntegrator integ = new ThreeEighthesIntegrator(step);
+      TestProblemHandler handler = new TestProblemHandler(pb, integ);
+      integ.addStepHandler(handler);
+      integ.integrate(pb, pb.getInitialTime(), pb.getInitialState(),
+                      pb.getFinalTime(), new double[pb.getDimension()]);
+
+      assertTrue(handler.getLastError() < 5.0e-10);
+      assertTrue(handler.getMaximalValueError() < 7.0e-10);
+      assertEquals(0, handler.getMaximalTimeError(), 1.0e-12);
+      assertEquals("3/8", integ.getName());
+  }
+
+  public void testKepler()
+    throws DerivativeException, IntegratorException {
+
+    final TestProblem3 pb  = new TestProblem3(0.9);
+    double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.0003;
+
+    FirstOrderIntegrator integ = new ThreeEighthesIntegrator(step);
+    integ.addStepHandler(new KeplerHandler(pb));
+    integ.integrate(pb,
+                    pb.getInitialTime(), pb.getInitialState(),
+                    pb.getFinalTime(), new double[pb.getDimension()]);
+  }
+
+  private static class KeplerHandler implements StepHandler {
+
+    public KeplerHandler(TestProblem3 pb) {
+      this.pb = pb;
+      maxError = 0;
+    }
+
+    public boolean requiresDenseOutput() {
+      return false;
+    }
+
+    public void reset() {
+      maxError = 0;
+    }
+
+    public void handleStep(StepInterpolator interpolator,
+                           boolean isLast) throws DerivativeException {
+
+      double[] interpolatedY = interpolator.getInterpolatedState();
+      double[] theoreticalY  = pb.computeTheoreticalState(interpolator.getCurrentTime());
+      double dx = interpolatedY[0] - theoreticalY[0];
+      double dy = interpolatedY[1] - theoreticalY[1];
+      double error = dx * dx + dy * dy;
+      if (error > maxError) {
+        maxError = error;
+      }
+      if (isLast) {
+        // even with more than 1000 evaluations per period,
+        // RK4 is not able to integrate such an eccentric
+        // orbit with a good accuracy
+        assertTrue(maxError > 0.005);
+      }
+    }
+
+    private TestProblem3 pb;
+    private double maxError = 0;
+
+  }
+
+  public void testStepSize()
+    throws DerivativeException, IntegratorException {
+      final double step = 1.23456;
+      FirstOrderIntegrator integ = new ThreeEighthesIntegrator(step);
+      integ.addStepHandler(new StepHandler() {
+          public void handleStep(StepInterpolator interpolator, boolean isLast) {
+              if (! isLast) {
+                  assertEquals(step,
+                               interpolator.getCurrentTime() - interpolator.getPreviousTime(),
+                               1.0e-12);
+              }
+          }
+          public boolean requiresDenseOutput() {
+              return false;
+          }
+          public void reset() {
+          }
+      });
+      integ.integrate(new FirstOrderDifferentialEquations() {
+          public void computeDerivatives(double t, double[] y, double[] dot) {
+              dot[0] = 1.0;
+          }
+          public int getDimension() {
+              return 1;
+          }
+      }, 0.0, new double[] { 0.0 }, 5.0, new double[1]);
+  }
+
+}
diff --git a/src/test/java/org/apache/commons/math/ode/nonstiff/ThreeEighthesStepInterpolatorTest.java b/src/test/java/org/apache/commons/math/ode/nonstiff/ThreeEighthesStepInterpolatorTest.java
new file mode 100644
index 0000000..99fc6d4
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/nonstiff/ThreeEighthesStepInterpolatorTest.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.ode.nonstiff;
+
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Random;
+
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.ode.ContinuousOutputModel;
+import org.apache.commons.math.ode.IntegratorException;
+import org.apache.commons.math.ode.TestProblem3;
+import org.apache.commons.math.ode.sampling.StepHandler;
+import org.apache.commons.math.ode.sampling.StepInterpolatorTestUtils;
+import org.junit.Test;
+
+public class ThreeEighthesStepInterpolatorTest {
+
+  @Test
+  public void derivativesConsistency()
+  throws DerivativeException, IntegratorException {
+    TestProblem3 pb = new TestProblem3();
+    double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.001;
+    ThreeEighthesIntegrator integ = new ThreeEighthesIntegrator(step);
+    StepInterpolatorTestUtils.checkDerivativesConsistency(integ, pb, 1.0e-10);
+  }
+
+  @Test
+  public void serialization()
+    throws DerivativeException, IntegratorException,
+           IOException, ClassNotFoundException {
+
+    TestProblem3 pb = new TestProblem3(0.9);
+    double step = (pb.getFinalTime() - pb.getInitialTime()) * 0.0003;
+    ThreeEighthesIntegrator integ = new ThreeEighthesIntegrator(step);
+    integ.addStepHandler(new ContinuousOutputModel());
+    integ.integrate(pb,
+                    pb.getInitialTime(), pb.getInitialState(),
+                    pb.getFinalTime(), new double[pb.getDimension()]);
+
+    ByteArrayOutputStream bos = new ByteArrayOutputStream();
+    ObjectOutputStream    oos = new ObjectOutputStream(bos);
+    for (StepHandler handler : integ.getStepHandlers()) {
+        oos.writeObject(handler);
+    }
+
+    assertTrue(bos.size () > 753000);
+    assertTrue(bos.size () < 754000);
+
+    ByteArrayInputStream  bis = new ByteArrayInputStream(bos.toByteArray());
+    ObjectInputStream     ois = new ObjectInputStream(bis);
+    ContinuousOutputModel cm  = (ContinuousOutputModel) ois.readObject();
+
+    Random random = new Random(347588535632l);
+    double maxError = 0.0;
+    for (int i = 0; i < 1000; ++i) {
+      double r = random.nextDouble();
+      double time = r * pb.getInitialTime() + (1.0 - r) * pb.getFinalTime();
+      cm.setInterpolatedTime(time);
+      double[] interpolatedY = cm.getInterpolatedState ();
+      double[] theoreticalY  = pb.computeTheoreticalState(time);
+      double dx = interpolatedY[0] - theoreticalY[0];
+      double dy = interpolatedY[1] - theoreticalY[1];
+      double error = dx * dx + dy * dy;
+      if (error > maxError) {
+        maxError = error;
+      }
+    }
+
+    assertTrue(maxError > 0.005);
+
+  }
+
+}
diff --git a/src/test/java/org/apache/commons/math/ode/sampling/DummyStepInterpolatorTest.java b/src/test/java/org/apache/commons/math/ode/sampling/DummyStepInterpolatorTest.java
new file mode 100644
index 0000000..ddd40c8
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/sampling/DummyStepInterpolatorTest.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.ode.sampling;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.ByteArrayOutputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ObjectOutputStream;
+import java.io.ObjectInputStream;
+import java.io.IOException;
+
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.exception.util.Localizable;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.ode.sampling.AbstractStepInterpolator;
+import org.apache.commons.math.ode.sampling.DummyStepInterpolator;
+import org.apache.commons.math.util.FastMath;
+import org.junit.Test;
+
+public class DummyStepInterpolatorTest {
+
+  @Test
+  public void testNoReset() throws DerivativeException {
+
+    double[]   y    =   { 0.0, 1.0, -2.0 };
+    DummyStepInterpolator interpolator = new DummyStepInterpolator(y, new double[y.length], true);
+    interpolator.storeTime(0);
+    interpolator.shift();
+    interpolator.storeTime(1);
+
+    double[] result = interpolator.getInterpolatedState();
+    for (int i = 0; i < result.length; ++i) {
+      assertTrue(FastMath.abs(result[i] - y[i]) < 1.0e-10);
+    }
+
+  }
+
+  @Test
+  public void testFixedState()
+    throws DerivativeException {
+
+    double[]   y    =   { 1.0, 3.0, -4.0 };
+    DummyStepInterpolator interpolator = new DummyStepInterpolator(y, new double[y.length], true);
+    interpolator.storeTime(0);
+    interpolator.shift();
+    interpolator.storeTime(1);
+
+    interpolator.setInterpolatedTime(0.1);
+    double[] result = interpolator.getInterpolatedState();
+    for (int i = 0; i < result.length; ++i) {
+        assertTrue(FastMath.abs(result[i] - y[i]) < 1.0e-10);
+    }
+
+    interpolator.setInterpolatedTime(0.5);
+    result = interpolator.getInterpolatedState();
+    for (int i = 0; i < result.length; ++i) {
+        assertTrue(FastMath.abs(result[i] - y[i]) < 1.0e-10);
+    }
+
+  }
+
+  @Test
+  public void testSerialization()
+  throws DerivativeException, IOException, ClassNotFoundException {
+
+    double[]   y    =   { 0.0, 1.0, -2.0 };
+    DummyStepInterpolator interpolator = new DummyStepInterpolator(y, new double[y.length], true);
+    interpolator.storeTime(0);
+    interpolator.shift();
+    interpolator.storeTime(1);
+
+    ByteArrayOutputStream bos = new ByteArrayOutputStream();
+    ObjectOutputStream    oos = new ObjectOutputStream(bos);
+    oos.writeObject(interpolator);
+
+    assertTrue(bos.size () > 200);
+    assertTrue(bos.size () < 300);
+
+    ByteArrayInputStream  bis = new ByteArrayInputStream(bos.toByteArray());
+    ObjectInputStream     ois = new ObjectInputStream(bis);
+    DummyStepInterpolator dsi = (DummyStepInterpolator) ois.readObject();
+
+    dsi.setInterpolatedTime(0.5);
+    double[] result = dsi.getInterpolatedState();
+    for (int i = 0; i < result.length; ++i) {
+        assertTrue(FastMath.abs(result[i] - y[i]) < 1.0e-10);
+    }
+
+  }
+
+  @Test
+  public void testImpossibleSerialization()
+  throws IOException {
+
+    double[] y = { 0.0, 1.0, -2.0 };
+    AbstractStepInterpolator interpolator = new BadStepInterpolator(y, true);
+    interpolator.storeTime(0);
+    interpolator.shift();
+    interpolator.storeTime(1);
+
+    ByteArrayOutputStream bos = new ByteArrayOutputStream();
+    ObjectOutputStream    oos = new ObjectOutputStream(bos);
+    try {
+        oos.writeObject(interpolator);
+        fail("an exception should have been thrown");
+    } catch (IOException ioe) {
+        // expected behavior
+        assertEquals(0, ioe.getMessage().length());
+    }
+
+  }
+
+  private static class BadStepInterpolator extends DummyStepInterpolator {
+      @SuppressWarnings("unused")
+      public BadStepInterpolator() {
+      }
+      public BadStepInterpolator(double[] y, boolean forward) {
+          super(y, new double[y.length], forward);
+      }
+      @Override
+      protected void doFinalize() throws DerivativeException {
+          throw new DerivativeException((Localizable) null, LocalizedFormats.SIMPLE_MESSAGE, "");
+      }
+  }
+
+}
diff --git a/src/test/java/org/apache/commons/math/ode/sampling/NordsieckStepInterpolatorTest.java b/src/test/java/org/apache/commons/math/ode/sampling/NordsieckStepInterpolatorTest.java
new file mode 100644
index 0000000..d27ea0a
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/sampling/NordsieckStepInterpolatorTest.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.ode.sampling;
+
+import static org.junit.Assert.assertTrue;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.Random;
+
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.ode.ContinuousOutputModel;
+import org.apache.commons.math.ode.IntegratorException;
+import org.apache.commons.math.ode.TestProblem1;
+import org.apache.commons.math.ode.TestProblem3;
+import org.apache.commons.math.ode.nonstiff.AdamsBashforthIntegrator;
+import org.junit.Test;
+
+public class NordsieckStepInterpolatorTest {
+
+    @Test
+    public void derivativesConsistency()
+    throws DerivativeException, IntegratorException {
+        TestProblem3 pb = new TestProblem3();
+        AdamsBashforthIntegrator integ = new AdamsBashforthIntegrator(4, 0.0, 1.0, 1.0e-10, 1.0e-10);
+        StepInterpolatorTestUtils.checkDerivativesConsistency(integ, pb, 5e-9);
+    }
+
+    @Test
+    public void serialization()
+    throws DerivativeException, IntegratorException,
+    IOException, ClassNotFoundException {
+
+        TestProblem1 pb = new TestProblem1();
+        AdamsBashforthIntegrator integ = new AdamsBashforthIntegrator(4, 0.0, 1.0, 1.0e-10, 1.0e-10);
+        integ.addStepHandler(new ContinuousOutputModel());
+        integ.integrate(pb,
+                        pb.getInitialTime(), pb.getInitialState(),
+                        pb.getFinalTime(), new double[pb.getDimension()]);
+
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        ObjectOutputStream    oos = new ObjectOutputStream(bos);
+        for (StepHandler handler : integ.getStepHandlers()) {
+            oos.writeObject(handler);
+        }
+
+        assertTrue(bos.size () >  25500);
+        assertTrue(bos.size () <  26500);
+
+        ByteArrayInputStream  bis = new ByteArrayInputStream(bos.toByteArray());
+        ObjectInputStream     ois = new ObjectInputStream(bis);
+        ContinuousOutputModel cm  = (ContinuousOutputModel) ois.readObject();
+
+        Random random = new Random(347588535632l);
+        double maxError = 0.0;
+        for (int i = 0; i < 1000; ++i) {
+            double r = random.nextDouble();
+            double time = r * pb.getInitialTime() + (1.0 - r) * pb.getFinalTime();
+            cm.setInterpolatedTime(time);
+            double[] interpolatedY = cm.getInterpolatedState ();
+            double[] theoreticalY  = pb.computeTheoreticalState(time);
+            double dx = interpolatedY[0] - theoreticalY[0];
+            double dy = interpolatedY[1] - theoreticalY[1];
+            double error = dx * dx + dy * dy;
+            if (error > maxError) {
+                maxError = error;
+            }
+        }
+
+        assertTrue(maxError < 1.0e-6);
+
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/ode/sampling/StepInterpolatorTestUtils.java b/src/test/java/org/apache/commons/math/ode/sampling/StepInterpolatorTestUtils.java
new file mode 100644
index 0000000..a0ca741
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/sampling/StepInterpolatorTestUtils.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.ode.sampling;
+
+import static org.junit.Assert.assertEquals;
+
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.ode.FirstOrderIntegrator;
+import org.apache.commons.math.ode.IntegratorException;
+import org.apache.commons.math.ode.TestProblemAbstract;
+import org.apache.commons.math.util.FastMath;
+
+public class StepInterpolatorTestUtils {
+
+    public static void checkDerivativesConsistency(final FirstOrderIntegrator integrator,
+                                                   final TestProblemAbstract problem,
+                                                   final double threshold)
+        throws DerivativeException, IntegratorException {
+        integrator.addStepHandler(new StepHandler() {
+
+            public boolean requiresDenseOutput() {
+                return true;
+            }
+
+            public void handleStep(StepInterpolator interpolator, boolean isLast)
+                throws DerivativeException {
+
+                final double h = 0.001 * (interpolator.getCurrentTime() - interpolator.getPreviousTime());
+                final double t = interpolator.getCurrentTime() - 300 * h;
+
+                if (FastMath.abs(h) < 10 * FastMath.ulp(t)) {
+                    return;
+                }
+
+                interpolator.setInterpolatedTime(t - 4 * h);
+                final double[] yM4h = interpolator.getInterpolatedState().clone();
+                interpolator.setInterpolatedTime(t - 3 * h);
+                final double[] yM3h = interpolator.getInterpolatedState().clone();
+                interpolator.setInterpolatedTime(t - 2 * h);
+                final double[] yM2h = interpolator.getInterpolatedState().clone();
+                interpolator.setInterpolatedTime(t - h);
+                final double[] yM1h = interpolator.getInterpolatedState().clone();
+                interpolator.setInterpolatedTime(t + h);
+                final double[] yP1h = interpolator.getInterpolatedState().clone();
+                interpolator.setInterpolatedTime(t + 2 * h);
+                final double[] yP2h = interpolator.getInterpolatedState().clone();
+                interpolator.setInterpolatedTime(t + 3 * h);
+                final double[] yP3h = interpolator.getInterpolatedState().clone();
+                interpolator.setInterpolatedTime(t + 4 * h);
+                final double[] yP4h = interpolator.getInterpolatedState().clone();
+
+                interpolator.setInterpolatedTime(t);
+                final double[] yDot = interpolator.getInterpolatedDerivatives();
+
+                for (int i = 0; i < yDot.length; ++i) {
+                    final double approYDot = ( -3 * (yP4h[i] - yM4h[i]) +
+                                               32 * (yP3h[i] - yM3h[i]) +
+                                             -168 * (yP2h[i] - yM2h[i]) +
+                                              672 * (yP1h[i] - yM1h[i])) / (840 * h);
+                    assertEquals(approYDot, yDot[i], threshold);
+                }
+
+            }
+
+            public void reset() {
+            }
+
+        });
+
+        integrator.integrate(problem,
+                             problem.getInitialTime(), problem.getInitialState(),
+                             problem.getFinalTime(), new double[problem.getDimension()]);
+
+    }
+}
+
diff --git a/src/test/java/org/apache/commons/math/ode/sampling/StepNormalizerTest.java b/src/test/java/org/apache/commons/math/ode/sampling/StepNormalizerTest.java
new file mode 100644
index 0000000..0f0f74b
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/ode/sampling/StepNormalizerTest.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.sampling;
+
+import org.apache.commons.math.ode.DerivativeException;
+import org.apache.commons.math.ode.FirstOrderIntegrator;
+import org.apache.commons.math.ode.IntegratorException;
+import org.apache.commons.math.ode.TestProblem3;
+import org.apache.commons.math.ode.nonstiff.DormandPrince54Integrator;
+import org.apache.commons.math.ode.sampling.FixedStepHandler;
+import org.apache.commons.math.ode.sampling.StepNormalizer;
+import org.apache.commons.math.util.FastMath;
+
+import junit.framework.*;
+
+public class StepNormalizerTest
+  extends TestCase {
+
+  public StepNormalizerTest(String name) {
+    super(name);
+    pb    = null;
+    integ = null;
+  }
+
+  public void testBoundaries()
+    throws DerivativeException, IntegratorException {
+    double range = pb.getFinalTime() - pb.getInitialTime();
+    setLastSeen(false);
+    integ.addStepHandler(new StepNormalizer(range / 10.0,
+                                       new FixedStepHandler() {
+                                        private static final long serialVersionUID = 1650337364641626444L;
+                                        private boolean firstCall = true;
+                                         public void handleStep(double t,
+                                                                double[] y,
+                                                                double[] yDot,
+                                                                boolean isLast) {
+                                           if (firstCall) {
+                                             checkValue(t, pb.getInitialTime());
+                                             firstCall = false;
+                                           }
+                                           if (isLast) {
+                                             setLastSeen(true);
+                                             checkValue(t, pb.getFinalTime());
+                                           }
+                                         }
+                                       }));
+    integ.integrate(pb,
+                    pb.getInitialTime(), pb.getInitialState(),
+                    pb.getFinalTime(), new double[pb.getDimension()]);
+    assertTrue(lastSeen);
+  }
+
+  public void testBeforeEnd()
+    throws DerivativeException, IntegratorException {
+    final double range = pb.getFinalTime() - pb.getInitialTime();
+    setLastSeen(false);
+    integ.addStepHandler(new StepNormalizer(range / 10.5,
+                                       new FixedStepHandler() {
+                                        private static final long serialVersionUID = 2228457391561277298L;
+                                        public void handleStep(double t,
+                                                                double[] y,
+                                                                double[] yDot,
+                                                                boolean isLast) {
+                                           if (isLast) {
+                                             setLastSeen(true);
+                                             checkValue(t,
+                                                        pb.getFinalTime() - range / 21.0);
+                                           }
+                                         }
+                                       }));
+    integ.integrate(pb,
+                    pb.getInitialTime(), pb.getInitialState(),
+                    pb.getFinalTime(), new double[pb.getDimension()]);
+    assertTrue(lastSeen);
+  }
+
+  public void checkValue(double value, double reference) {
+    assertTrue(FastMath.abs(value - reference) < 1.0e-10);
+  }
+
+  public void setLastSeen(boolean lastSeen) {
+    this.lastSeen = lastSeen;
+  }
+
+  @Override
+  public void setUp() {
+    pb = new TestProblem3(0.9);
+    double minStep = 0;
+    double maxStep = pb.getFinalTime() - pb.getInitialTime();
+    integ = new DormandPrince54Integrator(minStep, maxStep, 10.e-8, 1.0e-8);
+    lastSeen = false;
+  }
+
+  @Override
+  public void tearDown() {
+    pb    = null;
+    integ = null;
+  }
+
+  TestProblem3 pb;
+  FirstOrderIntegrator integ;
+  boolean lastSeen;
+
+}
diff --git a/src/test/java/org/apache/commons/math/optimization/MultiStartDifferentiableMultivariateRealOptimizerTest.java b/src/test/java/org/apache/commons/math/optimization/MultiStartDifferentiableMultivariateRealOptimizerTest.java
new file mode 100644
index 0000000..72ec730
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/optimization/MultiStartDifferentiableMultivariateRealOptimizerTest.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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.awt.geom.Point2D;
+import java.util.ArrayList;
+
+import org.apache.commons.math.analysis.DifferentiableMultivariateRealFunction;
+import org.apache.commons.math.analysis.MultivariateRealFunction;
+import org.apache.commons.math.analysis.MultivariateVectorialFunction;
+import org.apache.commons.math.analysis.solvers.BrentSolver;
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.optimization.general.ConjugateGradientFormula;
+import org.apache.commons.math.optimization.general.NonLinearConjugateGradientOptimizer;
+import org.apache.commons.math.random.GaussianRandomGenerator;
+import org.apache.commons.math.random.JDKRandomGenerator;
+import org.apache.commons.math.random.RandomVectorGenerator;
+import org.apache.commons.math.random.UncorrelatedRandomVectorGenerator;
+import org.junit.Test;
+
+public class MultiStartDifferentiableMultivariateRealOptimizerTest {
+
+    @Test
+    public void testCircleFitting() throws Exception {
+        Circle circle = new Circle();
+        circle.addPoint( 30.0,  68.0);
+        circle.addPoint( 50.0,  -6.0);
+        circle.addPoint(110.0, -20.0);
+        circle.addPoint( 35.0,  15.0);
+        circle.addPoint( 45.0,  97.0);
+        NonLinearConjugateGradientOptimizer underlying =
+            new NonLinearConjugateGradientOptimizer(ConjugateGradientFormula.POLAK_RIBIERE);
+        JDKRandomGenerator g = new JDKRandomGenerator();
+        g.setSeed(753289573253l);
+        RandomVectorGenerator generator =
+            new UncorrelatedRandomVectorGenerator(new double[] { 50.0, 50.0 }, new double[] { 10.0, 10.0 },
+                                                  new GaussianRandomGenerator(g));
+        MultiStartDifferentiableMultivariateRealOptimizer optimizer =
+            new MultiStartDifferentiableMultivariateRealOptimizer(underlying, 10, generator);
+        optimizer.setMaxIterations(100);
+        assertEquals(100, optimizer.getMaxIterations());
+        optimizer.setMaxEvaluations(100);
+        assertEquals(100, optimizer.getMaxEvaluations());
+        optimizer.setConvergenceChecker(new SimpleScalarValueChecker(1.0e-10, 1.0e-10));
+        BrentSolver solver = new BrentSolver();
+        solver.setAbsoluteAccuracy(1.0e-13);
+        solver.setRelativeAccuracy(1.0e-15);
+        RealPointValuePair optimum =
+            optimizer.optimize(circle, GoalType.MINIMIZE, new double[] { 98.680, 47.345 });
+        RealPointValuePair[] optima = optimizer.getOptima();
+        for (RealPointValuePair o : optima) {
+            Point2D.Double center = new Point2D.Double(o.getPointRef()[0], o.getPointRef()[1]);
+            assertEquals(69.960161753, circle.getRadius(center), 1.0e-8);
+            assertEquals(96.075902096, center.x, 1.0e-8);
+            assertEquals(48.135167894, center.y, 1.0e-8);
+        }
+        assertTrue(optimizer.getGradientEvaluations() > 650);
+        assertTrue(optimizer.getGradientEvaluations() < 700);
+        assertTrue(optimizer.getEvaluations() > 70);
+        assertTrue(optimizer.getEvaluations() < 90);
+        assertTrue(optimizer.getIterations() > 70);
+        assertTrue(optimizer.getIterations() < 90);
+        assertEquals(3.1267527, optimum.getValue(), 1.0e-8);
+    }
+
+    private static class Circle implements DifferentiableMultivariateRealFunction {
+
+        private ArrayList<Point2D.Double> points;
+
+        public Circle() {
+            points  = new ArrayList<Point2D.Double>();
+        }
+
+        public void addPoint(double px, double py) {
+            points.add(new Point2D.Double(px, py));
+        }
+
+        public double getRadius(Point2D.Double center) {
+            double r = 0;
+            for (Point2D.Double point : points) {
+                r += point.distance(center);
+            }
+            return r / points.size();
+        }
+
+        private double[] gradient(double[] point) {
+
+            // optimal radius
+            Point2D.Double center = new Point2D.Double(point[0], point[1]);
+            double radius = getRadius(center);
+
+            // gradient of the sum of squared residuals
+            double dJdX = 0;
+            double dJdY = 0;
+            for (Point2D.Double pk : points) {
+                double dk = pk.distance(center);
+                dJdX += (center.x - pk.x) * (dk - radius) / dk;
+                dJdY += (center.y - pk.y) * (dk - radius) / dk;
+            }
+            dJdX *= 2;
+            dJdY *= 2;
+
+            return new double[] { dJdX, dJdY };
+
+        }
+
+        public double value(double[] variables)
+        throws IllegalArgumentException, FunctionEvaluationException {
+
+            Point2D.Double center = new Point2D.Double(variables[0], variables[1]);
+            double radius = getRadius(center);
+
+            double sum = 0;
+            for (Point2D.Double point : points) {
+                double di = point.distance(center) - radius;
+                sum += di * di;
+            }
+
+            return sum;
+
+        }
+
+        public MultivariateVectorialFunction gradient() {
+            return new MultivariateVectorialFunction() {
+                public double[] value(double[] point) {
+                    return gradient(point);
+                }
+            };
+        }
+
+        public MultivariateRealFunction partialDerivative(final int k) {
+            return new MultivariateRealFunction() {
+                public double value(double[] point) {
+                    return gradient(point)[k];
+                }
+            };
+        }
+
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/optimization/MultiStartDifferentiableMultivariateVectorialOptimizerTest.java b/src/test/java/org/apache/commons/math/optimization/MultiStartDifferentiableMultivariateVectorialOptimizerTest.java
new file mode 100644
index 0000000..38ad2af
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/optimization/MultiStartDifferentiableMultivariateVectorialOptimizerTest.java
@@ -0,0 +1,192 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.optimization;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.Serializable;
+
+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.linear.BlockRealMatrix;
+import org.apache.commons.math.linear.RealMatrix;
+import org.apache.commons.math.optimization.general.GaussNewtonOptimizer;
+import org.apache.commons.math.random.GaussianRandomGenerator;
+import org.apache.commons.math.random.JDKRandomGenerator;
+import org.apache.commons.math.random.RandomVectorGenerator;
+import org.apache.commons.math.random.UncorrelatedRandomVectorGenerator;
+import org.junit.Test;
+
+/**
+ * <p>Some of the unit tests are re-implementations of the MINPACK <a
+ * href="http://www.netlib.org/minpack/ex/file17">file17</a> and <a
+ * href="http://www.netlib.org/minpack/ex/file22">file22</a> test files.
+ * 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>
+
+ * @author Argonne National Laboratory. MINPACK project. March 1980 (original fortran minpack tests)
+ * @author Burton S. Garbow (original fortran minpack tests)
+ * @author Kenneth E. Hillstrom (original fortran minpack tests)
+ * @author Jorge J. More (original fortran minpack tests)
+ * @author Luc Maisonobe (non-minpack tests and minpack tests Java translation)
+ */
+public class MultiStartDifferentiableMultivariateVectorialOptimizerTest {
+
+    @Test
+    public void testTrivial() throws FunctionEvaluationException, OptimizationException {
+        LinearProblem problem =
+            new LinearProblem(new double[][] { { 2 } }, new double[] { 3 });
+        DifferentiableMultivariateVectorialOptimizer underlyingOptimizer =
+            new GaussNewtonOptimizer(true);
+        JDKRandomGenerator g = new JDKRandomGenerator();
+        g.setSeed(16069223052l);
+        RandomVectorGenerator generator =
+            new UncorrelatedRandomVectorGenerator(1, new GaussianRandomGenerator(g));
+        MultiStartDifferentiableMultivariateVectorialOptimizer optimizer =
+            new MultiStartDifferentiableMultivariateVectorialOptimizer(underlyingOptimizer,
+                                                                       10, generator);
+        optimizer.setMaxIterations(100);
+        optimizer.setConvergenceChecker(new SimpleVectorialValueChecker(1.0e-6, 1.0e-6));
+
+        // no optima before first optimization attempt
+        try {
+            optimizer.getOptima();
+            fail("an exception should have been thrown");
+        } catch (IllegalStateException ise) {
+            // expected
+        }
+        VectorialPointValuePair optimum =
+            optimizer.optimize(problem, problem.target, new double[] { 1 }, new double[] { 0 });
+        assertEquals(1.5, optimum.getPoint()[0], 1.0e-10);
+        assertEquals(3.0, optimum.getValue()[0], 1.0e-10);
+        VectorialPointValuePair[] optima = optimizer.getOptima();
+        assertEquals(10, optima.length);
+        for (int i = 0; i < optima.length; ++i) {
+            assertEquals(1.5, optima[i].getPoint()[0], 1.0e-10);
+            assertEquals(3.0, optima[i].getValue()[0], 1.0e-10);
+        }
+        assertTrue(optimizer.getEvaluations() > 20);
+        assertTrue(optimizer.getEvaluations() < 50);
+        assertTrue(optimizer.getIterations() > 20);
+        assertTrue(optimizer.getIterations() < 50);
+        assertTrue(optimizer.getJacobianEvaluations() > 20);
+        assertTrue(optimizer.getJacobianEvaluations() < 50);
+        assertEquals(100, optimizer.getMaxIterations());
+    }
+
+    @Test(expected = OptimizationException.class)
+    public void testNoOptimum() throws FunctionEvaluationException, OptimizationException {
+        DifferentiableMultivariateVectorialOptimizer underlyingOptimizer =
+            new GaussNewtonOptimizer(true);
+        JDKRandomGenerator g = new JDKRandomGenerator();
+        g.setSeed(12373523445l);
+        RandomVectorGenerator generator =
+            new UncorrelatedRandomVectorGenerator(1, new GaussianRandomGenerator(g));
+        MultiStartDifferentiableMultivariateVectorialOptimizer optimizer =
+            new MultiStartDifferentiableMultivariateVectorialOptimizer(underlyingOptimizer,
+                                                                       10, generator);
+        optimizer.setMaxIterations(100);
+        optimizer.setConvergenceChecker(new SimpleVectorialValueChecker(1.0e-6, 1.0e-6));
+        optimizer.optimize(new DifferentiableMultivariateVectorialFunction() {
+                public MultivariateMatrixFunction jacobian() {
+                    return null;
+                }
+                public double[] value(double[] point) throws FunctionEvaluationException {
+                    throw new FunctionEvaluationException(point);
+                }
+            }, new double[] { 2 }, new double[] { 1 }, new double[] { 0 });
+    }
+
+    private static class LinearProblem implements DifferentiableMultivariateVectorialFunction, Serializable {
+
+        private static final long serialVersionUID = -8804268799379350190L;
+        final RealMatrix factors;
+        final double[] target;
+        public LinearProblem(double[][] factors, double[] target) {
+            this.factors = new BlockRealMatrix(factors);
+            this.target  = target;
+        }
+
+        public double[] value(double[] variables) {
+            return factors.operate(variables);
+        }
+
+        public MultivariateMatrixFunction jacobian() {
+            return new MultivariateMatrixFunction() {
+                private static final long serialVersionUID = -8387467946663627585L;
+                public double[][] value(double[] point) {
+                    return factors.getData();
+                }
+            };
+        }
+
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/optimization/MultiStartMultivariateRealOptimizerTest.java b/src/test/java/org/apache/commons/math/optimization/MultiStartMultivariateRealOptimizerTest.java
new file mode 100644
index 0000000..97176a4
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/optimization/MultiStartMultivariateRealOptimizerTest.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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.commons.math.analysis.MultivariateRealFunction;
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.optimization.direct.NelderMead;
+import org.apache.commons.math.random.GaussianRandomGenerator;
+import org.apache.commons.math.random.JDKRandomGenerator;
+import org.apache.commons.math.random.RandomVectorGenerator;
+import org.apache.commons.math.random.UncorrelatedRandomVectorGenerator;
+import org.junit.Test;
+
+public class MultiStartMultivariateRealOptimizerTest {
+
+  @Test
+  public void testRosenbrock() throws Exception {
+
+    Rosenbrock rosenbrock = new Rosenbrock();
+    NelderMead underlying = new NelderMead();
+    underlying.setStartConfiguration(new double[][] {
+                                         { -1.2,  1.0 }, { 0.9, 1.2 } , {  3.5, -2.3 }
+                                     });
+    JDKRandomGenerator g = new JDKRandomGenerator();
+    g.setSeed(16069223052l);
+    RandomVectorGenerator generator =
+        new UncorrelatedRandomVectorGenerator(2, new GaussianRandomGenerator(g));
+    MultiStartMultivariateRealOptimizer optimizer =
+        new MultiStartMultivariateRealOptimizer(underlying, 10, generator);
+    optimizer.setConvergenceChecker(new SimpleScalarValueChecker(-1, 1.0e-3));
+    optimizer.setMaxIterations(100);
+    RealPointValuePair optimum =
+        optimizer.optimize(rosenbrock, GoalType.MINIMIZE, new double[] { -1.2, 1.0 });
+
+    assertEquals(rosenbrock.getCount(), optimizer.getEvaluations());
+    assertTrue(optimizer.getEvaluations() > 20);
+    assertTrue(optimizer.getEvaluations() < 250);
+    assertTrue(optimum.getValue() < 8.0e-4);
+
+  }
+
+  private static class Rosenbrock implements MultivariateRealFunction {
+
+      private int count;
+
+      public Rosenbrock() {
+          count = 0;
+      }
+
+      public double value(double[] x) throws FunctionEvaluationException {
+          ++count;
+          double a = x[1] - x[0] * x[0];
+          double b = 1.0 - x[0];
+          return 100 * a * a + b * b;
+      }
+
+      public int getCount() {
+          return count;
+      }
+
+  }
+
+}
diff --git a/src/test/java/org/apache/commons/math/optimization/MultiStartUnivariateRealOptimizerTest.java b/src/test/java/org/apache/commons/math/optimization/MultiStartUnivariateRealOptimizerTest.java
new file mode 100644
index 0000000..df5403c
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/optimization/MultiStartUnivariateRealOptimizerTest.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.optimization;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.analysis.QuinticFunction;
+import org.apache.commons.math.analysis.SinFunction;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.optimization.univariate.BrentOptimizer;
+import org.apache.commons.math.random.JDKRandomGenerator;
+import org.apache.commons.math.util.FastMath;
+import org.junit.Test;
+
+public class MultiStartUnivariateRealOptimizerTest {
+
+    @Test
+    public void testSinMin() throws MathException {
+        UnivariateRealFunction f = new SinFunction();
+        UnivariateRealOptimizer underlying = new BrentOptimizer();
+        JDKRandomGenerator g = new JDKRandomGenerator();
+        g.setSeed(44428400075l);
+        MultiStartUnivariateRealOptimizer minimizer =
+            new MultiStartUnivariateRealOptimizer(underlying, 10, g);
+        minimizer.optimize(f, GoalType.MINIMIZE, -100.0, 100.0);
+        double[] optima = minimizer.getOptima();
+        double[] optimaValues = minimizer.getOptimaValues();
+        for (int i = 1; i < optima.length; ++i) {
+            double d = (optima[i] - optima[i-1]) / (2 * FastMath.PI);
+            assertTrue (FastMath.abs(d - FastMath.rint(d)) < 1.0e-8);
+            assertEquals(-1.0, f.value(optima[i]), 1.0e-10);
+            assertEquals(f.value(optima[i]), optimaValues[i], 1.0e-10);
+        }
+        assertTrue(minimizer.getEvaluations() > 150);
+        assertTrue(minimizer.getEvaluations() < 250);
+    }
+
+    @Test
+    public void testQuinticMin() throws MathException {
+        // The quintic function has zeros at 0, +-0.5 and +-1.
+        // The function has extrema (first derivative is zero) at 0.27195613 and 0.82221643,
+        UnivariateRealFunction f = new QuinticFunction();
+        UnivariateRealOptimizer underlying = new BrentOptimizer();
+        underlying.setRelativeAccuracy(1e-15);
+        JDKRandomGenerator g = new JDKRandomGenerator();
+        g.setSeed(4312000053L);
+        MultiStartUnivariateRealOptimizer minimizer =
+            new MultiStartUnivariateRealOptimizer(underlying, 5, g);
+        minimizer.setAbsoluteAccuracy(10 * minimizer.getAbsoluteAccuracy());
+        minimizer.setRelativeAccuracy(10 * minimizer.getRelativeAccuracy());
+
+        try {
+            minimizer.getOptima();
+            fail("an exception should have been thrown");
+        } catch (IllegalStateException ise) {
+            // expected
+        } catch (Exception e) {
+            fail("wrong exception caught");
+        }
+        try {
+            minimizer.getOptimaValues();
+            fail("an exception should have been thrown");
+        } catch (IllegalStateException ise) {
+            // expected
+        } catch (Exception e) {
+            fail("wrong exception caught");
+        }
+
+        double result = minimizer.optimize(f, GoalType.MINIMIZE, -0.3, -0.2);
+        assertEquals(-0.2719561270319131, result, 1.0e-13);
+        assertEquals(-0.2719561270319131, minimizer.getResult(), 1.0e-13);
+        assertEquals(-0.04433426954946637, minimizer.getFunctionValue(), 1.0e-13);
+
+        double[] optima = minimizer.getOptima();
+        double[] optimaValues = minimizer.getOptimaValues();
+        for (int i = 0; i < optima.length; ++i) {
+            assertEquals(f.value(optima[i]), optimaValues[i], 1.0e-10);
+        }
+        assertTrue(minimizer.getEvaluations()    >= 120);
+        assertTrue(minimizer.getEvaluations()    <= 170);
+        assertTrue(minimizer.getIterationCount() >= 120);
+        assertTrue(minimizer.getIterationCount() <= 170);
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/optimization/direct/MultiDirectionalTest.java b/src/test/java/org/apache/commons/math/optimization/direct/MultiDirectionalTest.java
new file mode 100644
index 0000000..1400727
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/optimization/direct/MultiDirectionalTest.java
@@ -0,0 +1,245 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.optimization.direct;
+
+import org.apache.commons.math.ConvergenceException;
+import org.apache.commons.math.FunctionEvaluationException;
+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.OptimizationException;
+import org.apache.commons.math.optimization.RealPointValuePair;
+import org.apache.commons.math.optimization.SimpleScalarValueChecker;
+import org.apache.commons.math.util.FastMath;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class MultiDirectionalTest {
+
+  @Test
+  public void testFunctionEvaluationException() throws OptimizationException, FunctionEvaluationException, IllegalArgumentException {
+      MultivariateRealFunction wrong =
+          new MultivariateRealFunction() {
+            private static final long serialVersionUID = 4751314470965489371L;
+            public double value(double[] x) throws FunctionEvaluationException {
+                if (x[0] < 0) {
+                    throw new FunctionEvaluationException(x,LocalizedFormats.SIMPLE_MESSAGE, "oops");
+                } else if (x[0] > 1) {
+                    throw (new FunctionEvaluationException(new RuntimeException("oops"), x));
+                } else {
+                    return x[0] * (1 - x[0]);
+                }
+            }
+      };
+      try {
+          MultiDirectional optimizer = new MultiDirectional(0.9, 1.9);
+          optimizer.optimize(wrong, GoalType.MINIMIZE, new double[] { -1.0 });
+          Assert.fail("an exception should have been thrown");
+      } catch (FunctionEvaluationException ce) {
+          // expected behavior
+          Assert.assertNull(ce.getCause());
+      }
+      try {
+          MultiDirectional optimizer = new MultiDirectional(0.9, 1.9);
+          optimizer.optimize(wrong, GoalType.MINIMIZE, new double[] { +2.0 });
+          Assert.fail("an exception should have been thrown");
+      } catch (FunctionEvaluationException ce) {
+          // expected behavior
+          Assert.assertNotNull(ce.getCause());
+      }
+  }
+
+  @Test
+  public void testMinimizeMaximize()
+      throws FunctionEvaluationException, ConvergenceException {
+
+      // the following function has 4 local extrema:
+      final double xM        = -3.841947088256863675365;
+      final double yM        = -1.391745200270734924416;
+      final double xP        =  0.2286682237349059125691;
+      final double yP        = -yM;
+      final double valueXmYm =  0.2373295333134216789769; // local  maximum
+      final double valueXmYp = -valueXmYm;                // local  minimum
+      final double valueXpYm = -0.7290400707055187115322; // global minimum
+      final double valueXpYp = -valueXpYm;                // global maximum
+      MultivariateRealFunction fourExtrema = new MultivariateRealFunction() {
+          private static final long serialVersionUID = -7039124064449091152L;
+          public double value(double[] variables) throws FunctionEvaluationException {
+              final double x = variables[0];
+              final double y = variables[1];
+              return ((x == 0) || (y == 0)) ? 0 : (FastMath.atan(x) * FastMath.atan(x + 2) * FastMath.atan(y) * FastMath.atan(y) / (x * y));
+          }
+      };
+
+      MultiDirectional optimizer = new MultiDirectional();
+      optimizer.setConvergenceChecker(new SimpleScalarValueChecker(1.0e-11, 1.0e-30));
+      optimizer.setMaxIterations(200);
+      optimizer.setStartConfiguration(new double[] { 0.2, 0.2 });
+      RealPointValuePair optimum;
+
+      // minimization
+      optimum = optimizer.optimize(fourExtrema, GoalType.MINIMIZE, new double[] { -3.0, 0 });
+      Assert.assertEquals(xM,        optimum.getPoint()[0], 4.0e-6);
+      Assert.assertEquals(yP,        optimum.getPoint()[1], 3.0e-6);
+      Assert.assertEquals(valueXmYp, optimum.getValue(),    8.0e-13);
+      Assert.assertTrue(optimizer.getEvaluations() > 120);
+      Assert.assertTrue(optimizer.getEvaluations() < 150);
+
+      optimum = optimizer.optimize(fourExtrema, GoalType.MINIMIZE, new double[] { +1, 0 });
+      Assert.assertEquals(xP,        optimum.getPoint()[0], 2.0e-8);
+      Assert.assertEquals(yM,        optimum.getPoint()[1], 3.0e-6);
+      Assert.assertEquals(valueXpYm, optimum.getValue(),    2.0e-12);
+      Assert.assertTrue(optimizer.getEvaluations() > 120);
+      Assert.assertTrue(optimizer.getEvaluations() < 150);
+
+      // maximization
+      optimum = optimizer.optimize(fourExtrema, GoalType.MAXIMIZE, new double[] { -3.0, 0.0 });
+      Assert.assertEquals(xM,        optimum.getPoint()[0], 7.0e-7);
+      Assert.assertEquals(yM,        optimum.getPoint()[1], 3.0e-7);
+      Assert.assertEquals(valueXmYm, optimum.getValue(),    2.0e-14);
+      Assert.assertTrue(optimizer.getEvaluations() > 120);
+      Assert.assertTrue(optimizer.getEvaluations() < 150);
+
+      optimizer.setConvergenceChecker(new SimpleScalarValueChecker(1.0e-15, 1.0e-30));
+      optimum = optimizer.optimize(fourExtrema, GoalType.MAXIMIZE, new double[] { +1, 0 });
+      Assert.assertEquals(xP,        optimum.getPoint()[0], 2.0e-8);
+      Assert.assertEquals(yP,        optimum.getPoint()[1], 3.0e-6);
+      Assert.assertEquals(valueXpYp, optimum.getValue(),    2.0e-12);
+      Assert.assertTrue(optimizer.getEvaluations() > 180);
+      Assert.assertTrue(optimizer.getEvaluations() < 220);
+
+  }
+
+  @Test
+  public void testRosenbrock()
+    throws FunctionEvaluationException, ConvergenceException {
+
+    MultivariateRealFunction rosenbrock =
+      new MultivariateRealFunction() {
+        private static final long serialVersionUID = -9044950469615237490L;
+        public double value(double[] x) throws FunctionEvaluationException {
+          ++count;
+          double a = x[1] - x[0] * x[0];
+          double b = 1.0 - x[0];
+          return 100 * a * a + b * b;
+        }
+      };
+
+    count = 0;
+    MultiDirectional optimizer = new MultiDirectional();
+    optimizer.setConvergenceChecker(new SimpleScalarValueChecker(-1, 1.0e-3));
+    optimizer.setMaxIterations(100);
+    optimizer.setStartConfiguration(new double[][] {
+            { -1.2,  1.0 }, { 0.9, 1.2 } , {  3.5, -2.3 }
+    });
+    RealPointValuePair optimum =
+        optimizer.optimize(rosenbrock, GoalType.MINIMIZE, new double[] { -1.2, 1.0 });
+
+    Assert.assertEquals(count, optimizer.getEvaluations());
+    Assert.assertTrue(optimizer.getEvaluations() > 50);
+    Assert.assertTrue(optimizer.getEvaluations() < 100);
+    Assert.assertTrue(optimum.getValue() > 1.0e-2);
+
+  }
+
+  @Test
+  public void testPowell()
+    throws FunctionEvaluationException, ConvergenceException {
+
+    MultivariateRealFunction powell =
+      new MultivariateRealFunction() {
+        private static final long serialVersionUID = -832162886102041840L;
+        public double value(double[] x) throws FunctionEvaluationException {
+          ++count;
+          double a = x[0] + 10 * x[1];
+          double b = x[2] - x[3];
+          double c = x[1] - 2 * x[2];
+          double d = x[0] - x[3];
+          return a * a + 5 * b * b + c * c * c * c + 10 * d * d * d * d;
+        }
+      };
+
+    count = 0;
+    MultiDirectional optimizer = new MultiDirectional();
+    optimizer.setConvergenceChecker(new SimpleScalarValueChecker(-1.0, 1.0e-3));
+    optimizer.setMaxIterations(1000);
+    RealPointValuePair optimum =
+      optimizer.optimize(powell, GoalType.MINIMIZE, new double[] { 3.0, -1.0, 0.0, 1.0 });
+    Assert.assertEquals(count, optimizer.getEvaluations());
+    Assert.assertTrue(optimizer.getEvaluations() > 800);
+    Assert.assertTrue(optimizer.getEvaluations() < 900);
+    Assert.assertTrue(optimum.getValue() > 1.0e-2);
+
+  }
+
+  @Test
+  public void testMath283()
+      throws FunctionEvaluationException, OptimizationException {
+      // fails because MultiDirectional.iterateSimplex is looping forever
+      // the while(true) should be replaced with a convergence check
+      MultiDirectional multiDirectional = new MultiDirectional();
+      multiDirectional.setMaxIterations(100);
+      multiDirectional.setMaxEvaluations(1000);
+
+      final Gaussian2D function = new Gaussian2D(0.0, 0.0, 1.0);
+
+      RealPointValuePair estimate = multiDirectional.optimize(function,
+                                    GoalType.MAXIMIZE, function.getMaximumPosition());
+
+      final double EPSILON = 1e-5;
+
+      final double expectedMaximum = function.getMaximum();
+      final double actualMaximum = estimate.getValue();
+      Assert.assertEquals(expectedMaximum, actualMaximum, EPSILON);
+
+      final double[] expectedPosition = function.getMaximumPosition();
+      final double[] actualPosition = estimate.getPoint();
+      Assert.assertEquals(expectedPosition[0], actualPosition[0], EPSILON );
+      Assert.assertEquals(expectedPosition[1], actualPosition[1], EPSILON );
+
+  }
+
+  private static class Gaussian2D implements MultivariateRealFunction {
+
+      private final double[] maximumPosition;
+
+      private final double std;
+
+      public Gaussian2D(double xOpt, double yOpt, double std) {
+          maximumPosition = new double[] { xOpt, yOpt };
+          this.std = std;
+      }
+
+      public double getMaximum() {
+          return value(maximumPosition);
+      }
+
+      public double[] getMaximumPosition() {
+          return maximumPosition.clone();
+      }
+
+      public double value(double[] point) {
+          final double x = point[0], y = point[1];
+          final double twoS2 = 2.0 * std * std;
+          return 1.0 / (twoS2 * FastMath.PI) * FastMath.exp(-(x * x + y * y) / twoS2);
+      }
+  }
+
+  private int count;
+
+}
diff --git a/src/test/java/org/apache/commons/math/optimization/direct/NelderMeadTest.java b/src/test/java/org/apache/commons/math/optimization/direct/NelderMeadTest.java
new file mode 100644
index 0000000..e8ddc1d
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/optimization/direct/NelderMeadTest.java
@@ -0,0 +1,334 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.optimization.direct;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.apache.commons.math.ConvergenceException;
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.MaxEvaluationsExceededException;
+import org.apache.commons.math.MaxIterationsExceededException;
+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.Array2DRowRealMatrix;
+import org.apache.commons.math.linear.RealMatrix;
+import org.apache.commons.math.optimization.GoalType;
+import org.apache.commons.math.optimization.LeastSquaresConverter;
+import org.apache.commons.math.optimization.OptimizationException;
+import org.apache.commons.math.optimization.RealPointValuePair;
+import org.apache.commons.math.optimization.SimpleRealPointChecker;
+import org.apache.commons.math.optimization.SimpleScalarValueChecker;
+import org.apache.commons.math.util.FastMath;
+import org.junit.Test;
+
+public class NelderMeadTest {
+
+  @Test
+  public void testFunctionEvaluationException() throws OptimizationException, FunctionEvaluationException, IllegalArgumentException {
+      MultivariateRealFunction wrong =
+          new MultivariateRealFunction() {
+            private static final long serialVersionUID = 4751314470965489371L;
+            public double value(double[] x) throws FunctionEvaluationException {
+                if (x[0] < 0) {
+                    throw new FunctionEvaluationException(x, LocalizedFormats.SIMPLE_MESSAGE, "oops");
+                } else if (x[0] > 1) {
+                    throw new FunctionEvaluationException(new RuntimeException("oops"), x);
+                } else {
+                    return x[0] * (1 - x[0]);
+                }
+            }
+      };
+      try {
+          NelderMead optimizer = new NelderMead(0.9, 1.9, 0.4, 0.6);
+          optimizer.optimize(wrong, GoalType.MINIMIZE, new double[] { -1.0 });
+          fail("an exception should have been thrown");
+      } catch (FunctionEvaluationException ce) {
+          // expected behavior
+          assertNull(ce.getCause());
+      }
+      try {
+          NelderMead optimizer = new NelderMead(0.9, 1.9, 0.4, 0.6);
+          optimizer.optimize(wrong, GoalType.MINIMIZE, new double[] { +2.0 });
+          fail("an exception should have been thrown");
+      } catch (FunctionEvaluationException ce) {
+          // expected behavior
+          assertNotNull(ce.getCause());
+      }
+  }
+
+  @Test
+  public void testMinimizeMaximize()
+      throws FunctionEvaluationException, ConvergenceException {
+
+      // the following function has 4 local extrema:
+      final double xM        = -3.841947088256863675365;
+      final double yM        = -1.391745200270734924416;
+      final double xP        =  0.2286682237349059125691;
+      final double yP        = -yM;
+      final double valueXmYm =  0.2373295333134216789769; // local  maximum
+      final double valueXmYp = -valueXmYm;                // local  minimum
+      final double valueXpYm = -0.7290400707055187115322; // global minimum
+      final double valueXpYp = -valueXpYm;                // global maximum
+      MultivariateRealFunction fourExtrema = new MultivariateRealFunction() {
+          private static final long serialVersionUID = -7039124064449091152L;
+          public double value(double[] variables) throws FunctionEvaluationException {
+              final double x = variables[0];
+              final double y = variables[1];
+              return ((x == 0) || (y == 0)) ? 0 : (FastMath.atan(x) * FastMath.atan(x + 2) * FastMath.atan(y) * FastMath.atan(y) / (x * y));
+          }
+      };
+
+      NelderMead optimizer = new NelderMead();
+      optimizer.setConvergenceChecker(new SimpleScalarValueChecker(1.0e-10, 1.0e-30));
+      optimizer.setMaxIterations(100);
+      optimizer.setStartConfiguration(new double[] { 0.2, 0.2 });
+      RealPointValuePair optimum;
+
+      // minimization
+      optimum = optimizer.optimize(fourExtrema, GoalType.MINIMIZE, new double[] { -3.0, 0 });
+      assertEquals(xM,        optimum.getPoint()[0], 2.0e-7);
+      assertEquals(yP,        optimum.getPoint()[1], 2.0e-5);
+      assertEquals(valueXmYp, optimum.getValue(),    6.0e-12);
+      assertTrue(optimizer.getEvaluations() > 60);
+      assertTrue(optimizer.getEvaluations() < 90);
+
+      optimum = optimizer.optimize(fourExtrema, GoalType.MINIMIZE, new double[] { +1, 0 });
+      assertEquals(xP,        optimum.getPoint()[0], 5.0e-6);
+      assertEquals(yM,        optimum.getPoint()[1], 6.0e-6);
+      assertEquals(valueXpYm, optimum.getValue(),    1.0e-11);
+      assertTrue(optimizer.getEvaluations() > 60);
+      assertTrue(optimizer.getEvaluations() < 90);
+
+      // maximization
+      optimum = optimizer.optimize(fourExtrema, GoalType.MAXIMIZE, new double[] { -3.0, 0.0 });
+      assertEquals(xM,        optimum.getPoint()[0], 1.0e-5);
+      assertEquals(yM,        optimum.getPoint()[1], 3.0e-6);
+      assertEquals(valueXmYm, optimum.getValue(),    3.0e-12);
+      assertTrue(optimizer.getEvaluations() > 60);
+      assertTrue(optimizer.getEvaluations() < 90);
+
+      optimum = optimizer.optimize(fourExtrema, GoalType.MAXIMIZE, new double[] { +1, 0 });
+      assertEquals(xP,        optimum.getPoint()[0], 4.0e-6);
+      assertEquals(yP,        optimum.getPoint()[1], 5.0e-6);
+      assertEquals(valueXpYp, optimum.getValue(),    7.0e-12);
+      assertTrue(optimizer.getEvaluations() > 60);
+      assertTrue(optimizer.getEvaluations() < 90);
+
+  }
+
+  @Test
+  public void testRosenbrock()
+    throws FunctionEvaluationException, ConvergenceException {
+
+    Rosenbrock rosenbrock = new Rosenbrock();
+    NelderMead optimizer = new NelderMead();
+    optimizer.setConvergenceChecker(new SimpleScalarValueChecker(-1, 1.0e-3));
+    optimizer.setMaxIterations(100);
+    optimizer.setStartConfiguration(new double[][] {
+            { -1.2,  1.0 }, { 0.9, 1.2 } , {  3.5, -2.3 }
+    });
+    RealPointValuePair optimum =
+        optimizer.optimize(rosenbrock, GoalType.MINIMIZE, new double[] { -1.2, 1.0 });
+
+    assertEquals(rosenbrock.getCount(), optimizer.getEvaluations());
+    assertTrue(optimizer.getEvaluations() > 40);
+    assertTrue(optimizer.getEvaluations() < 50);
+    assertTrue(optimum.getValue() < 8.0e-4);
+
+  }
+
+  @Test
+  public void testPowell()
+    throws FunctionEvaluationException, ConvergenceException {
+
+    Powell powell = new Powell();
+    NelderMead optimizer = new NelderMead();
+    optimizer.setConvergenceChecker(new SimpleScalarValueChecker(-1.0, 1.0e-3));
+    optimizer.setMaxIterations(200);
+    RealPointValuePair optimum =
+      optimizer.optimize(powell, GoalType.MINIMIZE, new double[] { 3.0, -1.0, 0.0, 1.0 });
+    assertEquals(powell.getCount(), optimizer.getEvaluations());
+    assertTrue(optimizer.getEvaluations() > 110);
+    assertTrue(optimizer.getEvaluations() < 130);
+    assertTrue(optimum.getValue() < 2.0e-3);
+
+  }
+
+  @Test
+  public void testLeastSquares1()
+  throws FunctionEvaluationException, ConvergenceException {
+
+      final RealMatrix factors =
+          new Array2DRowRealMatrix(new double[][] {
+              { 1.0, 0.0 },
+              { 0.0, 1.0 }
+          }, false);
+      LeastSquaresConverter ls = new LeastSquaresConverter(new MultivariateVectorialFunction() {
+          public double[] value(double[] variables) {
+              return factors.operate(variables);
+          }
+      }, new double[] { 2.0, -3.0 });
+      NelderMead optimizer = new NelderMead();
+      optimizer.setConvergenceChecker(new SimpleScalarValueChecker(-1.0, 1.0e-6));
+      optimizer.setMaxIterations(200);
+      RealPointValuePair optimum =
+          optimizer.optimize(ls, GoalType.MINIMIZE, new double[] { 10.0, 10.0 });
+      assertEquals( 2.0, optimum.getPointRef()[0], 3.0e-5);
+      assertEquals(-3.0, optimum.getPointRef()[1], 4.0e-4);
+      assertTrue(optimizer.getEvaluations() > 60);
+      assertTrue(optimizer.getEvaluations() < 80);
+      assertTrue(optimum.getValue() < 1.0e-6);
+  }
+
+  @Test
+  public void testLeastSquares2()
+  throws FunctionEvaluationException, ConvergenceException {
+
+      final RealMatrix factors =
+          new Array2DRowRealMatrix(new double[][] {
+              { 1.0, 0.0 },
+              { 0.0, 1.0 }
+          }, false);
+      LeastSquaresConverter ls = new LeastSquaresConverter(new MultivariateVectorialFunction() {
+          public double[] value(double[] variables) {
+              return factors.operate(variables);
+          }
+      }, new double[] { 2.0, -3.0 }, new double[] { 10.0, 0.1 });
+      NelderMead optimizer = new NelderMead();
+      optimizer.setConvergenceChecker(new SimpleScalarValueChecker(-1.0, 1.0e-6));
+      optimizer.setMaxIterations(200);
+      RealPointValuePair optimum =
+          optimizer.optimize(ls, GoalType.MINIMIZE, new double[] { 10.0, 10.0 });
+      assertEquals( 2.0, optimum.getPointRef()[0], 5.0e-5);
+      assertEquals(-3.0, optimum.getPointRef()[1], 8.0e-4);
+      assertTrue(optimizer.getEvaluations() > 60);
+      assertTrue(optimizer.getEvaluations() < 80);
+      assertTrue(optimum.getValue() < 1.0e-6);
+  }
+
+  @Test
+  public void testLeastSquares3()
+  throws FunctionEvaluationException, ConvergenceException {
+
+      final RealMatrix factors =
+          new Array2DRowRealMatrix(new double[][] {
+              { 1.0, 0.0 },
+              { 0.0, 1.0 }
+          }, false);
+      LeastSquaresConverter ls = new LeastSquaresConverter(new MultivariateVectorialFunction() {
+          public double[] value(double[] variables) {
+              return factors.operate(variables);
+          }
+      }, new double[] { 2.0, -3.0 }, new Array2DRowRealMatrix(new double [][] {
+          { 1.0, 1.2 }, { 1.2, 2.0 }
+      }));
+      NelderMead optimizer = new NelderMead();
+      optimizer.setConvergenceChecker(new SimpleScalarValueChecker(-1.0, 1.0e-6));
+      optimizer.setMaxIterations(200);
+      RealPointValuePair optimum =
+          optimizer.optimize(ls, GoalType.MINIMIZE, new double[] { 10.0, 10.0 });
+      assertEquals( 2.0, optimum.getPointRef()[0], 2.0e-3);
+      assertEquals(-3.0, optimum.getPointRef()[1], 8.0e-4);
+      assertTrue(optimizer.getEvaluations() > 60);
+      assertTrue(optimizer.getEvaluations() < 80);
+      assertTrue(optimum.getValue() < 1.0e-6);
+  }
+
+  @Test(expected = MaxIterationsExceededException.class)
+  public void testMaxIterations() throws MathException {
+      try {
+          Powell powell = new Powell();
+          NelderMead optimizer = new NelderMead();
+          optimizer.setConvergenceChecker(new SimpleScalarValueChecker(-1.0, 1.0e-3));
+          optimizer.setMaxIterations(20);
+          optimizer.optimize(powell, GoalType.MINIMIZE, new double[] { 3.0, -1.0, 0.0, 1.0 });
+      } catch (OptimizationException oe) {
+          if (oe.getCause() instanceof ConvergenceException) {
+              throw (ConvergenceException) oe.getCause();
+          }
+          throw oe;
+      }
+  }
+
+  @Test(expected = MaxEvaluationsExceededException.class)
+  public void testMaxEvaluations() throws MathException {
+      try {
+          Powell powell = new Powell();
+          NelderMead optimizer = new NelderMead();
+          optimizer.setConvergenceChecker(new SimpleRealPointChecker(-1.0, 1.0e-3));
+          optimizer.setMaxEvaluations(20);
+          optimizer.optimize(powell, GoalType.MINIMIZE, new double[] { 3.0, -1.0, 0.0, 1.0 });
+      } catch (FunctionEvaluationException fee) {
+          if (fee.getCause() instanceof ConvergenceException) {
+              throw (ConvergenceException) fee.getCause();
+          }
+          throw fee;
+      }
+  }
+
+  private static class Rosenbrock implements MultivariateRealFunction {
+
+      private int count;
+
+      public Rosenbrock() {
+          count = 0;
+      }
+
+      public double value(double[] x) {
+          ++count;
+          double a = x[1] - x[0] * x[0];
+          double b = 1.0 - x[0];
+          return 100 * a * a + b * b;
+      }
+
+      public int getCount() {
+          return count;
+      }
+
+  }
+
+  private static class Powell implements MultivariateRealFunction {
+
+      private int count;
+
+      public Powell() {
+          count = 0;
+      }
+
+      public double value(double[] x) {
+          ++count;
+          double a = x[0] + 10 * x[1];
+          double b = x[2] - x[3];
+          double c = x[1] - 2 * x[2];
+          double d = x[0] - x[3];
+          return a * a + 5 * b * b + c * c * c * c + 10 * d * d * d * d;
+      }
+
+      public int getCount() {
+          return count;
+      }
+
+  }
+
+}
diff --git a/src/test/java/org/apache/commons/math/optimization/direct/PowellOptimizerTest.java b/src/test/java/org/apache/commons/math/optimization/direct/PowellOptimizerTest.java
new file mode 100644
index 0000000..dfbde93
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/optimization/direct/PowellOptimizerTest.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.optimization.direct;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.analysis.DifferentiableMultivariateRealFunction;
+import org.apache.commons.math.analysis.MultivariateRealFunction;
+import org.apache.commons.math.analysis.MultivariateVectorialFunction;
+import org.apache.commons.math.analysis.SumSincFunction;
+import org.apache.commons.math.optimization.GoalType;
+import org.apache.commons.math.optimization.RealPointValuePair;
+import org.apache.commons.math.optimization.SimpleScalarValueChecker;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Test for {@link PowellOptimizer}.
+ */
+public class PowellOptimizerTest {
+
+    @Test
+    public void testSumSinc() throws MathException {
+        final DifferentiableMultivariateRealFunction func = new SumSincFunction(-1);
+
+        int dim = 2;
+        final double[] minPoint = new double[dim];
+        for (int i = 0; i < dim; i++) {
+            minPoint[i] = 0;
+        }
+
+        double[] init = new double[dim];
+
+        // Initial is minimum.
+        for (int i = 0; i < dim; i++) {
+            init[i] = minPoint[i];
+        }
+        // doTest(func, minPoint, init,  GoalType.MINIMIZE, 1e-5, 1e-9, 1e-7);
+
+        // Initial is far from minimum.
+        for (int i = 0; i < dim; i++) {
+            init[i] = minPoint[i] + 3;
+        }
+        doTest(func, minPoint, init,  GoalType.MINIMIZE, 1e-5, 1e-9, 1e-7);
+    }
+
+    @Test
+    public void testQuadratic() throws MathException {
+        final DifferentiableMultivariateRealFunction func = new DifferentiableMultivariateRealFunction() {
+                public double value(double[] x) {
+                    final double a = x[0] - 1;
+                    final double b = x[1] - 1;
+                    return a * a + b * b + 1;
+                }
+                public MultivariateRealFunction partialDerivative(int k) {
+                    return null; // Not used
+                }
+                public MultivariateVectorialFunction gradient() {
+                    return null; // Not used
+                }
+            };
+
+        int dim = 2;
+        final double[] minPoint = new double[dim];
+        for (int i = 0; i < dim; i++) {
+            minPoint[i] = 1;
+        }
+
+        double[] init = new double[dim];
+
+        // Initial is minimum.
+        for (int i = 0; i < dim; i++) {
+            init[i] = minPoint[i];
+        }
+        doTest(func, minPoint, init,  GoalType.MINIMIZE, 1e-5, 1e-9, 1e-8);
+
+        // Initial is far from minimum.
+        for (int i = 0; i < dim; i++) {
+            init[i] = minPoint[i] - 20;
+        }
+        doTest(func, minPoint, init, GoalType.MINIMIZE, 1e-5, 1e-9, 1e-8);
+    }
+
+    @Test
+    public void testMaximizeQuadratic() throws MathException {
+        final DifferentiableMultivariateRealFunction func = new DifferentiableMultivariateRealFunction() {
+                public double value(double[] x) {
+                    final double a = x[0] - 1;
+                    final double b = x[1] - 1;
+                    return -a * a - b * b + 1;
+                }
+                public MultivariateRealFunction partialDerivative(int k) {
+                    return null;  // Not used
+                }
+                public MultivariateVectorialFunction gradient() {
+                    return null;  // Not used
+                }
+            };
+
+        int dim = 2;
+        final double[] maxPoint = new double[dim];
+        for (int i = 0; i < dim; i++) {
+            maxPoint[i] = 1;
+        }
+
+        double[] init = new double[dim];
+
+        // Initial is minimum.
+        for (int i = 0; i < dim; i++) {
+            init[i] = maxPoint[i];
+        }
+        doTest(func, maxPoint, init,  GoalType.MAXIMIZE, 1e-5, 1e-9, 1e-8);
+
+        // Initial is far from minimum.
+        for (int i = 0; i < dim; i++) {
+            init[i] = maxPoint[i] - 20;
+        }
+        doTest(func, maxPoint, init, GoalType.MAXIMIZE, 1e-5, 1e-9, 1e-8);
+    }
+
+    /**
+     * @param func Function to optimize.
+     * @param optimum Expected optimum.
+     * @param init Starting point.
+     * @param goal Minimization or maximization.
+     * @param xTol Tolerance (relative error on the objective function) for
+     * "Brent" line search algorithm used by "Powell".
+     * @param fTol Tolerance (relative error on the objective function) for
+     * "Powell" algorithm.
+     * @param pointTol Tolerance for checking that the optimum is correct.
+     */
+    private void doTest(DifferentiableMultivariateRealFunction func,
+                        double[] optimum,
+                        double[] init,
+                        GoalType goal,
+                        double xTol,
+                        double fTol,
+                        double pointTol)
+        throws MathException {
+        final PowellOptimizer optim = new PowellOptimizer(xTol);
+        optim.setConvergenceChecker(new SimpleScalarValueChecker(fTol, -1));
+
+        final RealPointValuePair result = optim.optimize(func, goal, init);
+        final double[] found = result.getPoint();
+
+        for (int i = 0, dim = optimum.length; i < dim; i++) {
+            Assert.assertEquals(optimum[i], found[i], pointTol);
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/optimization/fitting/CurveFitterTest.java b/src/test/java/org/apache/commons/math/optimization/fitting/CurveFitterTest.java
new file mode 100644
index 0000000..5604e86
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/optimization/fitting/CurveFitterTest.java
@@ -0,0 +1,156 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.commons.math.optimization.fitting;
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.optimization.OptimizationException;
+import org.apache.commons.math.optimization.general.LevenbergMarquardtOptimizer;
+import org.apache.commons.math.util.FastMath;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class CurveFitterTest {
+
+    @Test
+    public void testMath303()
+        throws OptimizationException, FunctionEvaluationException {
+
+        LevenbergMarquardtOptimizer optimizer = new LevenbergMarquardtOptimizer();
+        CurveFitter fitter = new CurveFitter(optimizer);
+        fitter.addObservedPoint(2.805d, 0.6934785852953367d);
+        fitter.addObservedPoint(2.74333333333333d, 0.6306772025518496d);
+        fitter.addObservedPoint(1.655d, 0.9474675497289684);
+        fitter.addObservedPoint(1.725d, 0.9013594835804194d);
+
+        ParametricRealFunction sif = new SimpleInverseFunction();
+
+        double[] initialguess1 = new double[1];
+        initialguess1[0] = 1.0d;
+        Assert.assertEquals(1, fitter.fit(sif, initialguess1).length);
+
+        double[] initialguess2 = new double[2];
+        initialguess2[0] = 1.0d;
+        initialguess2[1] = .5d;
+        Assert.assertEquals(2, fitter.fit(sif, initialguess2).length);
+
+    }
+
+    @Test
+    public void testMath304()
+        throws OptimizationException, FunctionEvaluationException {
+
+        LevenbergMarquardtOptimizer optimizer = new LevenbergMarquardtOptimizer();
+        CurveFitter fitter = new CurveFitter(optimizer);
+        fitter.addObservedPoint(2.805d, 0.6934785852953367d);
+        fitter.addObservedPoint(2.74333333333333d, 0.6306772025518496d);
+        fitter.addObservedPoint(1.655d, 0.9474675497289684);
+        fitter.addObservedPoint(1.725d, 0.9013594835804194d);
+
+        ParametricRealFunction sif = new SimpleInverseFunction();
+
+        double[] initialguess1 = new double[1];
+        initialguess1[0] = 1.0d;
+        Assert.assertEquals(1.6357215104109237, fitter.fit(sif, initialguess1)[0], 1.0e-14);
+
+        double[] initialguess2 = new double[1];
+        initialguess2[0] = 10.0d;
+        Assert.assertEquals(1.6357215104109237, fitter.fit(sif, initialguess1)[0], 1.0e-14);
+
+    }
+
+    @Test
+    public void testMath372()
+    throws OptimizationException, FunctionEvaluationException {
+        LevenbergMarquardtOptimizer optimizer = new LevenbergMarquardtOptimizer();
+        CurveFitter curveFitter = new CurveFitter(optimizer);
+
+        curveFitter.addObservedPoint( 15,  4443);
+        curveFitter.addObservedPoint( 31,  8493);
+        curveFitter.addObservedPoint( 62, 17586);
+        curveFitter.addObservedPoint(125, 30582);
+        curveFitter.addObservedPoint(250, 45087);
+        curveFitter.addObservedPoint(500, 50683);
+
+        ParametricRealFunction f = new ParametricRealFunction() {
+
+            public double value(double x, double[] parameters) {
+
+                double a = parameters[0];
+                double b = parameters[1];
+                double c = parameters[2];
+                double d = parameters[3];
+
+                return d + ((a - d) / (1 + FastMath.pow(x / c, b)));
+            }
+
+            public double[] gradient(double x, double[] parameters) {
+
+                double a = parameters[0];
+                double b = parameters[1];
+                double c = parameters[2];
+                double d = parameters[3];
+
+                double[] gradients = new double[4];
+                double den = 1 + FastMath.pow(x / c, b);
+
+                // derivative with respect to a
+                gradients[0] = 1 / den;
+
+                // derivative with respect to b
+                // in the reported (invalid) issue, there was a sign error here
+                gradients[1] = -((a - d) * FastMath.pow(x / c, b) * FastMath.log(x / c)) / (den * den);
+
+                // derivative with respect to c
+                gradients[2] = (b * FastMath.pow(x / c, b - 1) * (x / (c * c)) * (a - d)) / (den * den);
+
+                // derivative with respect to d
+                gradients[3] = 1 - (1 / den);
+
+                return gradients;
+
+            }
+        };
+
+        double[] initialGuess = new double[] { 1500, 0.95, 65, 35000 };
+        double[] estimatedParameters = curveFitter.fit(f, initialGuess);
+
+        Assert.assertEquals( 2411.00, estimatedParameters[0], 500.00);
+        Assert.assertEquals(    1.62, estimatedParameters[1],   0.04);
+        Assert.assertEquals(  111.22, estimatedParameters[2],   0.30);
+        Assert.assertEquals(55347.47, estimatedParameters[3], 300.00);
+        Assert.assertTrue(optimizer.getRMS() < 600.0);
+
+    }
+
+    private static class SimpleInverseFunction implements ParametricRealFunction {
+
+        public double value(double x, double[] parameters) {
+            return parameters[0] / x + (parameters.length < 2 ? 0 : parameters[1]);
+        }
+
+        public double[] gradient(double x, double[] doubles) {
+            double[] gradientVector = new double[doubles.length];
+            gradientVector[0] = 1 / x;
+            if (doubles.length >= 2) {
+                gradientVector[1] = 1;
+            }
+            return gradientVector;
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/optimization/fitting/GaussianFitterTest.java b/src/test/java/org/apache/commons/math/optimization/fitting/GaussianFitterTest.java
new file mode 100644
index 0000000..3dea090
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/optimization/fitting/GaussianFitterTest.java
@@ -0,0 +1,321 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.Assert.assertEquals;
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.optimization.OptimizationException;
+import org.apache.commons.math.optimization.general.
+       LevenbergMarquardtOptimizer;
+import org.junit.Test;
+
+/**
+ * Tests {@link GaussianFitter}.
+ *
+ * @since 2.2
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ */
+public class GaussianFitterTest {
+    /** Good data. */
+    protected static final double[][] DATASET1 = new double[][] {
+        {4.0254623,  531026.0},
+        {4.02804905, 664002.0},
+        {4.02934242, 787079.0},
+        {4.03128248, 984167.0},
+        {4.03386923, 1294546.0},
+        {4.03580929, 1560230.0},
+        {4.03839603, 1887233.0},
+        {4.0396894,  2113240.0},
+        {4.04162946, 2375211.0},
+        {4.04421621, 2687152.0},
+        {4.04550958, 2862644.0},
+        {4.04744964, 3078898.0},
+        {4.05003639, 3327238.0},
+        {4.05132976, 3461228.0},
+        {4.05326982, 3580526.0},
+        {4.05585657, 3576946.0},
+        {4.05779662, 3439750.0},
+        {4.06038337, 3220296.0},
+        {4.06167674, 3070073.0},
+        {4.0636168,  2877648.0},
+        {4.06620355, 2595848.0},
+        {4.06749692, 2390157.0},
+        {4.06943698, 2175960.0},
+        {4.07202373, 1895104.0},
+        {4.0733171,  1687576.0},
+        {4.07525716, 1447024.0},
+        {4.0778439,  1130879.0},
+        {4.07978396, 904900.0},
+        {4.08237071, 717104.0},
+        {4.08366408, 620014.0}
+    };
+    /** Poor data: right of peak not symmetric with left of peak. */
+    protected static final double[][] DATASET2 = new double[][] {
+        {-20.15,   1523.0},
+        {-19.65,   1566.0},
+        {-19.15,   1592.0},
+        {-18.65,   1927.0},
+        {-18.15,   3089.0},
+        {-17.65,   6068.0},
+        {-17.15,  14239.0},
+        {-16.65,  34124.0},
+        {-16.15,  64097.0},
+        {-15.65, 110352.0},
+        {-15.15, 164742.0},
+        {-14.65, 209499.0},
+        {-14.15, 267274.0},
+        {-13.65, 283290.0},
+        {-13.15, 275363.0},
+        {-12.65, 258014.0},
+        {-12.15, 225000.0},
+        {-11.65, 200000.0},
+        {-11.15, 190000.0},
+        {-10.65, 185000.0},
+        {-10.15, 180000.0},
+        { -9.65, 179000.0},
+        { -9.15, 178000.0},
+        { -8.65, 177000.0},
+        { -8.15, 176000.0},
+        { -7.65, 175000.0},
+        { -7.15, 174000.0},
+        { -6.65, 173000.0},
+        { -6.15, 172000.0},
+        { -5.65, 171000.0},
+        { -5.15, 170000.0}
+    };
+    /** Poor data: long tails. */
+    protected static final double[][] DATASET3 = new double[][] {
+        {-90.15,   1513.0},
+        {-80.15,   1514.0},
+        {-70.15,   1513.0},
+        {-60.15,   1514.0},
+        {-50.15,   1513.0},
+        {-40.15,   1514.0},
+        {-30.15,   1513.0},
+        {-20.15,   1523.0},
+        {-19.65,   1566.0},
+        {-19.15,   1592.0},
+        {-18.65,   1927.0},
+        {-18.15,   3089.0},
+        {-17.65,   6068.0},
+        {-17.15,  14239.0},
+        {-16.65,  34124.0},
+        {-16.15,  64097.0},
+        {-15.65, 110352.0},
+        {-15.15, 164742.0},
+        {-14.65, 209499.0},
+        {-14.15, 267274.0},
+        {-13.65, 283290.0},
+        {-13.15, 275363.0},
+        {-12.65, 258014.0},
+        {-12.15, 214073.0},
+        {-11.65, 182244.0},
+        {-11.15, 136419.0},
+        {-10.65,  97823.0},
+        {-10.15,  58930.0},
+        { -9.65,  35404.0},
+        { -9.15,  16120.0},
+        { -8.65,   9823.0},
+        { -8.15,   5064.0},
+        { -7.65,   2575.0},
+        { -7.15,   1642.0},
+        { -6.65,   1101.0},
+        { -6.15,    812.0},
+        { -5.65,    690.0},
+        { -5.15,    565.0},
+        {  5.15,    564.0},
+        { 15.15,    565.0},
+        { 25.15,    564.0},
+        { 35.15,    565.0},
+        { 45.15,    564.0},
+        { 55.15,    565.0},
+        { 65.15,    564.0},
+        { 75.15,    565.0}
+    };
+    /** Poor data: right of peak is missing. */
+    protected static final double[][] DATASET4 = new double[][] {
+        {-20.15,   1523.0},
+        {-19.65,   1566.0},
+        {-19.15,   1592.0},
+        {-18.65,   1927.0},
+        {-18.15,   3089.0},
+        {-17.65,   6068.0},
+        {-17.15,  14239.0},
+        {-16.65,  34124.0},
+        {-16.15,  64097.0},
+        {-15.65, 110352.0},
+        {-15.15, 164742.0},
+        {-14.65, 209499.0},
+        {-14.15, 267274.0},
+        {-13.65, 283290.0}
+    };
+    /** Good data, but few points. */
+    protected static final double[][] DATASET5 = new double[][] {
+        {4.0254623,  531026.0},
+        {4.03128248, 984167.0},
+        {4.03839603, 1887233.0},
+        {4.04421621, 2687152.0},
+        {4.05132976, 3461228.0},
+        {4.05326982, 3580526.0},
+        {4.05779662, 3439750.0},
+        {4.0636168,  2877648.0},
+        {4.06943698, 2175960.0},
+        {4.07525716, 1447024.0},
+        {4.08237071, 717104.0},
+        {4.08366408, 620014.0}
+    };
+
+    /**
+     * Basic.
+     *
+     * @throws OptimizationException in the event of a test case error
+     * @throws FunctionEvaluationException in the event of a test case error
+     */
+    @Test
+    public void testFit01()
+    throws OptimizationException, FunctionEvaluationException {
+        GaussianFitter fitter = new GaussianFitter(new LevenbergMarquardtOptimizer());
+        addDatasetToGaussianFitter(DATASET1, fitter);
+        GaussianFunction fitFunction = fitter.fit();
+        assertEquals(99200.86969833552, fitFunction.getA(), 1e-4);
+        assertEquals(3410515.285208688, fitFunction.getB(), 1e-4);
+        assertEquals(4.054928275302832, fitFunction.getC(), 1e-4);
+        assertEquals(0.014609868872574, fitFunction.getD(), 1e-4);
+    }
+
+    /**
+     * Zero points is not enough observed points.
+     *
+     * @throws OptimizationException in the event of a test case error
+     * @throws FunctionEvaluationException in the event of a test case error
+     */
+    @Test(expected=IllegalArgumentException.class)
+    public void testFit02()
+    throws OptimizationException, FunctionEvaluationException {
+        GaussianFitter fitter = new GaussianFitter(new LevenbergMarquardtOptimizer());
+        fitter.fit();
+    }
+
+    /**
+     * Two points is not enough observed points.
+     *
+     * @throws OptimizationException in the event of a test case error
+     * @throws FunctionEvaluationException in the event of a test case error
+     */
+    @Test(expected=IllegalArgumentException.class)
+    public void testFit03()
+    throws OptimizationException, FunctionEvaluationException {
+        GaussianFitter fitter = new GaussianFitter(new LevenbergMarquardtOptimizer());
+        addDatasetToGaussianFitter(new double[][] {
+            {4.0254623,  531026.0},
+            {4.02804905, 664002.0}},
+            fitter);
+        fitter.fit();
+    }
+
+    /**
+     * Poor data: right of peak not symmetric with left of peak.
+     *
+     * @throws OptimizationException in the event of a test case error
+     * @throws FunctionEvaluationException in the event of a test case error
+     */
+    @Test
+    public void testFit04()
+    throws OptimizationException, FunctionEvaluationException {
+        GaussianFitter fitter = new GaussianFitter(new LevenbergMarquardtOptimizer());
+        addDatasetToGaussianFitter(DATASET2, fitter);
+        GaussianFunction fitFunction = fitter.fit();
+        assertEquals(-256534.689445631, fitFunction.getA(), 1e-4);
+        assertEquals(481328.2181530679, fitFunction.getB(), 1e-4);
+        assertEquals(-10.5217226891099, fitFunction.getC(), 1e-4);
+        assertEquals(-7.64248239366800, fitFunction.getD(), 1e-4);
+    }
+
+    /**
+     * Poor data: long tails.
+     *
+     * @throws OptimizationException in the event of a test case error
+     * @throws FunctionEvaluationException in the event of a test case error
+     */
+    @Test
+    public void testFit05()
+    throws OptimizationException, FunctionEvaluationException {
+        GaussianFitter fitter = new GaussianFitter(new LevenbergMarquardtOptimizer());
+        addDatasetToGaussianFitter(DATASET3, fitter);
+        GaussianFunction fitFunction = fitter.fit();
+        assertEquals(491.6310079258938, fitFunction.getA(), 1e-4);
+        assertEquals(283508.6800413632, fitFunction.getB(), 1e-4);
+        assertEquals(-13.2966857238057, fitFunction.getC(), 1e-4);
+        assertEquals(1.725590356962981, fitFunction.getD(), 1e-4);
+    }
+
+    /**
+     * Poor data: right of peak is missing.
+     *
+     * @throws OptimizationException in the event of a test case error
+     * @throws FunctionEvaluationException in the event of a test case error
+     */
+    @Test
+    public void testFit06()
+    throws OptimizationException, FunctionEvaluationException {
+        GaussianFitter fitter = new GaussianFitter(new LevenbergMarquardtOptimizer());
+        addDatasetToGaussianFitter(DATASET4, fitter);
+        GaussianFunction fitFunction = fitter.fit();
+        assertEquals(530.3649792355617, fitFunction.getA(), 1e-4);
+        assertEquals(284517.0835567514, fitFunction.getB(), 1e-4);
+        assertEquals(-13.5355534565105, fitFunction.getC(), 1e-4);
+        assertEquals(1.512353018625465, fitFunction.getD(), 1e-4);
+    }
+
+    /**
+     * Basic with smaller dataset.
+     *
+     * @throws OptimizationException in the event of a test case error
+     * @throws FunctionEvaluationException in the event of a test case error
+     */
+    @Test
+    public void testFit07()
+    throws OptimizationException, FunctionEvaluationException {
+        GaussianFitter fitter = new GaussianFitter(new LevenbergMarquardtOptimizer());
+        addDatasetToGaussianFitter(DATASET5, fitter);
+        GaussianFunction fitFunction = fitter.fit();
+        assertEquals(176748.1400947575, fitFunction.getA(), 1e-4);
+        assertEquals(3361537.018813906, fitFunction.getB(), 1e-4);
+        assertEquals(4.054949992747176, fitFunction.getC(), 1e-4);
+        assertEquals(0.014192380137002, fitFunction.getD(), 1e-4);
+    }
+
+    /**
+     * Adds the specified points to specified <code>GaussianFitter</code>
+     * instance.
+     *
+     * @param points data points where first dimension is a point index and
+     *        second dimension is an array of length two representing the point
+     *        with the first value corresponding to X and the second value
+     *        corresponding to Y
+     * @param fitter fitter to which the points in <code>points</code> should be
+     *        added as observed points
+     */
+    protected static void addDatasetToGaussianFitter(double[][] points,
+                                                     GaussianFitter fitter) {
+        for (int i = 0; i < points.length; i++) {
+            fitter.addObservedPoint(points[i][0], points[i][1]);
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/optimization/fitting/HarmonicFitterTest.java b/src/test/java/org/apache/commons/math/optimization/fitting/HarmonicFitterTest.java
new file mode 100644
index 0000000..1aa4637
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/optimization/fitting/HarmonicFitterTest.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 static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Random;
+
+import org.apache.commons.math.optimization.OptimizationException;
+import org.apache.commons.math.optimization.general.LevenbergMarquardtOptimizer;
+import org.apache.commons.math.util.FastMath;
+import org.apache.commons.math.util.MathUtils;
+import org.junit.Test;
+
+public class HarmonicFitterTest {
+
+    @Test
+    public void testNoError() throws OptimizationException {
+        HarmonicFunction f = new HarmonicFunction(0.2, 3.4, 4.1);
+
+        HarmonicFitter fitter =
+            new HarmonicFitter(new LevenbergMarquardtOptimizer());
+        for (double x = 0.0; x < 1.3; x += 0.01) {
+            fitter.addObservedPoint(1.0, x, f.value(x));
+        }
+
+        HarmonicFunction fitted = fitter.fit();
+        assertEquals(f.getAmplitude(), fitted.getAmplitude(), 1.0e-13);
+        assertEquals(f.getPulsation(), fitted.getPulsation(), 1.0e-13);
+        assertEquals(f.getPhase(),     MathUtils.normalizeAngle(fitted.getPhase(), f.getPhase()), 1.0e-13);
+
+        for (double x = -1.0; x < 1.0; x += 0.01) {
+            assertTrue(FastMath.abs(f.value(x) - fitted.value(x)) < 1.0e-13);
+        }
+
+    }
+
+    @Test
+    public void test1PercentError() throws OptimizationException {
+        Random randomizer = new Random(64925784252l);
+        HarmonicFunction f = new HarmonicFunction(0.2, 3.4, 4.1);
+
+        HarmonicFitter fitter =
+            new HarmonicFitter(new LevenbergMarquardtOptimizer());
+        for (double x = 0.0; x < 10.0; x += 0.1) {
+            fitter.addObservedPoint(1.0, x,
+                                   f.value(x) + 0.01 * randomizer.nextGaussian());
+        }
+
+        HarmonicFunction fitted = fitter.fit();
+        assertEquals(f.getAmplitude(), fitted.getAmplitude(), 7.6e-4);
+        assertEquals(f.getPulsation(), fitted.getPulsation(), 2.7e-3);
+        assertEquals(f.getPhase(),     MathUtils.normalizeAngle(fitted.getPhase(), f.getPhase()), 1.3e-2);
+
+    }
+
+    @Test
+    public void testInitialGuess() throws OptimizationException {
+        Random randomizer = new Random(45314242l);
+        HarmonicFunction f = new HarmonicFunction(0.2, 3.4, 4.1);
+
+        HarmonicFitter fitter =
+            new HarmonicFitter(new LevenbergMarquardtOptimizer(), new double[] { 0.15, 3.6, 4.5 });
+        for (double x = 0.0; x < 10.0; x += 0.1) {
+            fitter.addObservedPoint(1.0, x,
+                                   f.value(x) + 0.01 * randomizer.nextGaussian());
+        }
+
+        HarmonicFunction fitted = fitter.fit();
+        assertEquals(f.getAmplitude(), fitted.getAmplitude(), 1.2e-3);
+        assertEquals(f.getPulsation(), fitted.getPulsation(), 3.3e-3);
+        assertEquals(f.getPhase(),     MathUtils.normalizeAngle(fitted.getPhase(), f.getPhase()), 1.7e-2);
+
+    }
+
+    @Test
+    public void testUnsorted() throws OptimizationException {
+        Random randomizer = new Random(64925784252l);
+        HarmonicFunction f = new HarmonicFunction(0.2, 3.4, 4.1);
+
+        HarmonicFitter fitter =
+            new HarmonicFitter(new LevenbergMarquardtOptimizer());
+
+        // build a regularly spaced array of measurements
+        int size = 100;
+        double[] xTab = new double[size];
+        double[] yTab = new double[size];
+        for (int i = 0; i < size; ++i) {
+            xTab[i] = 0.1 * i;
+            yTab[i] = f.value(xTab[i]) + 0.01 * randomizer.nextGaussian();
+        }
+
+        // shake it
+        for (int i = 0; i < size; ++i) {
+            int i1 = randomizer.nextInt(size);
+            int i2 = randomizer.nextInt(size);
+            double xTmp = xTab[i1];
+            double yTmp = yTab[i1];
+            xTab[i1] = xTab[i2];
+            yTab[i1] = yTab[i2];
+            xTab[i2] = xTmp;
+            yTab[i2] = yTmp;
+        }
+
+        // pass it to the fitter
+        for (int i = 0; i < size; ++i) {
+            fitter.addObservedPoint(1.0, xTab[i], yTab[i]);
+        }
+
+        HarmonicFunction fitted = fitter.fit();
+        assertEquals(f.getAmplitude(), fitted.getAmplitude(), 7.6e-4);
+        assertEquals(f.getPulsation(), fitted.getPulsation(), 3.5e-3);
+        assertEquals(f.getPhase(),     MathUtils.normalizeAngle(fitted.getPhase(), f.getPhase()), 1.5e-2);
+
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/optimization/fitting/ParametricGaussianFunctionTest.java b/src/test/java/org/apache/commons/math/optimization/fitting/ParametricGaussianFunctionTest.java
new file mode 100644
index 0000000..868a893
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/optimization/fitting/ParametricGaussianFunctionTest.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.optimization.fitting;
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.exception.ZeroException;
+import org.apache.commons.math.optimization.OptimizationException;
+import org.apache.commons.math.optimization.fitting.CurveFitter;
+import org.apache.commons.math.optimization.general.
+       LevenbergMarquardtOptimizer;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Tests {@link ParametricGaussianFunction}.
+ *
+ * @since 2.2
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ */
+public class ParametricGaussianFunctionTest {
+    /** Dataset 1 used by some test cases. */
+    protected static final double[][] DATASET1 = new double[][] {
+        {4.0254623,  531026.0},
+        {4.02804905, 664002.0},
+        {4.02934242, 787079.0},
+        {4.03128248, 984167.0},
+        {4.03386923, 1294546.0},
+        {4.03580929, 1560230.0},
+        {4.03839603, 1887233.0},
+        {4.0396894,  2113240.0},
+        {4.04162946, 2375211.0},
+        {4.04421621, 2687152.0},
+        {4.04550958, 2862644.0},
+        {4.04744964, 3078898.0},
+        {4.05003639, 3327238.0},
+        {4.05132976, 3461228.0},
+        {4.05326982, 3580526.0},
+        {4.05585657, 3576946.0},
+        {4.05779662, 3439750.0},
+        {4.06038337, 3220296.0},
+        {4.06167674, 3070073.0},
+        {4.0636168,  2877648.0},
+        {4.06620355, 2595848.0},
+        {4.06749692, 2390157.0},
+        {4.06943698, 2175960.0},
+        {4.07202373, 1895104.0},
+        {4.0733171,  1687576.0},
+        {4.07525716, 1447024.0},
+        {4.0778439,  1130879.0},
+        {4.07978396, 904900.0},
+        {4.08237071, 717104.0},
+        {4.08366408, 620014.0}
+    };
+
+    /**
+     * Using not-so-good initial parameters.
+     *
+     * @throws OptimizationException in the event of a test case error
+     * @throws FunctionEvaluationException in the event of a test case error
+     */
+    @Test
+    public void testFit01()
+    throws OptimizationException, FunctionEvaluationException {
+        CurveFitter fitter = new CurveFitter(new LevenbergMarquardtOptimizer());
+        addDatasetToCurveFitter(DATASET1, fitter);
+        double[] parameters = fitter.fit(new ParametricGaussianFunction(),
+                                         new double[] {8.64753e3, 3.483323e6, 4.06322, 1.946857e-2});
+        assertEquals(99200.94715858076, parameters[0], 1e-4);
+        assertEquals(3410515.221897707, parameters[1], 1e-4);
+        assertEquals(4.054928275257894, parameters[2], 1e-4);
+        assertEquals(0.014609868499860, parameters[3], 1e-4);
+    }
+
+    /**
+     * Using eye-balled guesses for initial parameters.
+     *
+     * @throws OptimizationException in the event of a test case error
+     * @throws FunctionEvaluationException in the event of a test case error
+     */
+    @Test
+    public void testFit02()
+    throws OptimizationException, FunctionEvaluationException {
+        CurveFitter fitter = new CurveFitter(new LevenbergMarquardtOptimizer());
+        addDatasetToCurveFitter(DATASET1, fitter);
+        double[] parameters = fitter.fit(new ParametricGaussianFunction(),
+                                         new double[] {500000.0, 3500000.0, 4.055, 0.025479654});
+        assertEquals(99200.81836264656, parameters[0], 1e-4);
+        assertEquals(3410515.327151986, parameters[1], 1e-4);
+        assertEquals(4.054928275377392, parameters[2], 1e-4);
+        assertEquals(0.014609869119806, parameters[3], 1e-4);
+    }
+
+    /**
+     * The parameters array is null.
+     *
+     * @throws FunctionEvaluationException in the event of a test case error
+     */
+    @Test(expected=IllegalArgumentException.class)
+    public void testValue01() throws FunctionEvaluationException {
+        ParametricGaussianFunction f = new ParametricGaussianFunction();
+        f.value(0.0, null);
+    }
+
+    /**
+     * The parameters array length is not 4.
+     *
+     * @throws FunctionEvaluationException in the event of a test case error
+     */
+    @Test(expected=IllegalArgumentException.class)
+    public void testValue02() throws FunctionEvaluationException {
+        ParametricGaussianFunction f = new ParametricGaussianFunction();
+        f.value(0.0, new double[] {0.0, 1.0});
+    }
+
+    /**
+     * The parameters d is 0.
+     *
+     * @throws FunctionEvaluationException in the event of a test case error
+     */
+    @Test(expected=ZeroException.class)
+    public void testValue03() throws FunctionEvaluationException {
+        ParametricGaussianFunction f = new ParametricGaussianFunction();
+        f.value(0.0, new double[] {0.0, 1.0, 1.0, 0.0});
+    }
+
+    /**
+     * Adds the specified points to specified <code>CurveFitter</code> instance.
+     *
+     * @param points data points where first dimension is a point index and
+     *        second dimension is an array of length two representing the point
+     *        with the first value corresponding to X and the second value
+     *        corresponding to Y
+     * @param fitter fitter to which the points in <code>points</code> should be
+     *        added as observed points
+     */
+    protected static void addDatasetToCurveFitter(double[][] points,
+                                                  CurveFitter fitter) {
+        for (int i = 0; i < points.length; i++) {
+            fitter.addObservedPoint(points[i][0], points[i][1]);
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/optimization/fitting/PolynomialFitterTest.java b/src/test/java/org/apache/commons/math/optimization/fitting/PolynomialFitterTest.java
new file mode 100644
index 0000000..fe85640
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/optimization/fitting/PolynomialFitterTest.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.fitting;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Random;
+
+import org.apache.commons.math.analysis.polynomials.PolynomialFunction;
+import org.apache.commons.math.optimization.DifferentiableMultivariateVectorialOptimizer;
+import org.apache.commons.math.optimization.OptimizationException;
+import org.apache.commons.math.optimization.general.GaussNewtonOptimizer;
+import org.apache.commons.math.optimization.general.LevenbergMarquardtOptimizer;
+import org.apache.commons.math.util.FastMath;
+import org.junit.Test;
+
+public class PolynomialFitterTest {
+
+    @Test
+    public void testNoError() throws OptimizationException {
+        Random randomizer = new Random(64925784252l);
+        for (int degree = 1; degree < 10; ++degree) {
+            PolynomialFunction p = buildRandomPolynomial(degree, randomizer);
+
+            PolynomialFitter fitter =
+                new PolynomialFitter(degree, new LevenbergMarquardtOptimizer());
+            for (int i = 0; i <= degree; ++i) {
+                fitter.addObservedPoint(1.0, i, p.value(i));
+            }
+
+            PolynomialFunction fitted = fitter.fit();
+
+            for (double x = -1.0; x < 1.0; x += 0.01) {
+                double error = FastMath.abs(p.value(x) - fitted.value(x)) /
+                               (1.0 + FastMath.abs(p.value(x)));
+                assertEquals(0.0, error, 1.0e-6);
+            }
+
+        }
+
+    }
+
+    @Test
+    public void testSmallError() throws OptimizationException {
+        Random randomizer = new Random(53882150042l);
+        double maxError = 0;
+        for (int degree = 0; degree < 10; ++degree) {
+            PolynomialFunction p = buildRandomPolynomial(degree, randomizer);
+
+            PolynomialFitter fitter =
+                new PolynomialFitter(degree, new LevenbergMarquardtOptimizer());
+            for (double x = -1.0; x < 1.0; x += 0.01) {
+                fitter.addObservedPoint(1.0, x,
+                                        p.value(x) + 0.1 * randomizer.nextGaussian());
+            }
+
+            PolynomialFunction fitted = fitter.fit();
+
+            for (double x = -1.0; x < 1.0; x += 0.01) {
+                double error = FastMath.abs(p.value(x) - fitted.value(x)) /
+                              (1.0 + FastMath.abs(p.value(x)));
+                maxError = FastMath.max(maxError, error);
+                assertTrue(FastMath.abs(error) < 0.1);
+            }
+        }
+        assertTrue(maxError > 0.01);
+
+    }
+
+    @Test
+    public void testRedundantSolvable() {
+        // Levenberg-Marquardt should handle redundant information gracefully
+        checkUnsolvableProblem(new LevenbergMarquardtOptimizer(), true);
+    }
+
+    @Test
+    public void testRedundantUnsolvable() {
+        // Gauss-Newton should not be able to solve redundant information
+        DifferentiableMultivariateVectorialOptimizer optimizer =
+            new GaussNewtonOptimizer(true);
+        checkUnsolvableProblem(optimizer, false);
+    }
+
+    private void checkUnsolvableProblem(DifferentiableMultivariateVectorialOptimizer optimizer,
+                                        boolean solvable) {
+        Random randomizer = new Random(1248788532l);
+        for (int degree = 0; degree < 10; ++degree) {
+            PolynomialFunction p = buildRandomPolynomial(degree, randomizer);
+
+            PolynomialFitter fitter = new PolynomialFitter(degree, optimizer);
+
+            // reusing the same point over and over again does not bring
+            // information, the problem cannot be solved in this case for
+            // degrees greater than 1 (but one point is sufficient for
+            // degree 0)
+            for (double x = -1.0; x < 1.0; x += 0.01) {
+                fitter.addObservedPoint(1.0, 0.0, p.value(0.0));
+            }
+
+            try {
+                fitter.fit();
+                assertTrue(solvable || (degree == 0));
+            } catch(OptimizationException e) {
+                assertTrue((! solvable) && (degree > 0));
+            }
+
+        }
+
+    }
+
+    private PolynomialFunction buildRandomPolynomial(int degree, Random randomizer) {
+        final double[] coefficients = new double[degree + 1];
+        for (int i = 0; i <= degree; ++i) {
+            coefficients[i] = randomizer.nextGaussian();
+        }
+        return new PolynomialFunction(coefficients);
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/optimization/general/GaussNewtonOptimizerTest.java b/src/test/java/org/apache/commons/math/optimization/general/GaussNewtonOptimizerTest.java
new file mode 100644
index 0000000..0a326ac
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/optimization/general/GaussNewtonOptimizerTest.java
@@ -0,0 +1,572 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.awt.geom.Point2D;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import junit.framework.TestCase;
+
+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.linear.BlockRealMatrix;
+import org.apache.commons.math.linear.RealMatrix;
+import org.apache.commons.math.optimization.OptimizationException;
+import org.apache.commons.math.optimization.SimpleVectorialPointChecker;
+import org.apache.commons.math.optimization.SimpleVectorialValueChecker;
+import org.apache.commons.math.optimization.VectorialPointValuePair;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * <p>Some of the unit tests are re-implementations of the MINPACK <a
+ * href="http://www.netlib.org/minpack/ex/file17">file17</a> and <a
+ * href="http://www.netlib.org/minpack/ex/file22">file22</a> test files.
+ * 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>
+
+ * @author Argonne National Laboratory. MINPACK project. March 1980 (original fortran minpack tests)
+ * @author Burton S. Garbow (original fortran minpack tests)
+ * @author Kenneth E. Hillstrom (original fortran minpack tests)
+ * @author Jorge J. More (original fortran minpack tests)
+ * @author Luc Maisonobe (non-minpack tests and minpack tests Java translation)
+ */
+public class GaussNewtonOptimizerTest
+extends TestCase {
+
+    public GaussNewtonOptimizerTest(String name) {
+        super(name);
+    }
+
+    public void testTrivial() throws FunctionEvaluationException, OptimizationException {
+        LinearProblem problem =
+            new LinearProblem(new double[][] { { 2 } }, new double[] { 3 });
+        GaussNewtonOptimizer optimizer = new GaussNewtonOptimizer(true);
+        optimizer.setMaxIterations(100);
+        optimizer.setConvergenceChecker(new SimpleVectorialValueChecker(1.0e-6, 1.0e-6));
+        VectorialPointValuePair optimum =
+            optimizer.optimize(problem, problem.target, new double[] { 1 }, new double[] { 0 });
+        assertEquals(0, optimizer.getRMS(), 1.0e-10);
+        assertEquals(1.5, optimum.getPoint()[0], 1.0e-10);
+        assertEquals(3.0, optimum.getValue()[0], 1.0e-10);
+    }
+
+    public void testColumnsPermutation() throws FunctionEvaluationException, OptimizationException {
+
+        LinearProblem problem =
+            new LinearProblem(new double[][] { { 1.0, -1.0 }, { 0.0, 2.0 }, { 1.0, -2.0 } },
+                              new double[] { 4.0, 6.0, 1.0 });
+
+        GaussNewtonOptimizer optimizer = new GaussNewtonOptimizer(true);
+        optimizer.setMaxIterations(100);
+        optimizer.setConvergenceChecker(new SimpleVectorialValueChecker(1.0e-6, 1.0e-6));
+        VectorialPointValuePair optimum =
+            optimizer.optimize(problem, problem.target, new double[] { 1, 1, 1 }, new double[] { 0, 0 });
+        assertEquals(0, optimizer.getRMS(), 1.0e-10);
+        assertEquals(7.0, optimum.getPoint()[0], 1.0e-10);
+        assertEquals(3.0, optimum.getPoint()[1], 1.0e-10);
+        assertEquals(4.0, optimum.getValue()[0], 1.0e-10);
+        assertEquals(6.0, optimum.getValue()[1], 1.0e-10);
+        assertEquals(1.0, optimum.getValue()[2], 1.0e-10);
+
+    }
+
+    public void testNoDependency() throws FunctionEvaluationException, OptimizationException {
+        LinearProblem problem = new LinearProblem(new double[][] {
+                { 2, 0, 0, 0, 0, 0 },
+                { 0, 2, 0, 0, 0, 0 },
+                { 0, 0, 2, 0, 0, 0 },
+                { 0, 0, 0, 2, 0, 0 },
+                { 0, 0, 0, 0, 2, 0 },
+                { 0, 0, 0, 0, 0, 2 }
+        }, new double[] { 0.0, 1.1, 2.2, 3.3, 4.4, 5.5 });
+        GaussNewtonOptimizer optimizer = new GaussNewtonOptimizer(true);
+        optimizer.setMaxIterations(100);
+        optimizer.setConvergenceChecker(new SimpleVectorialValueChecker(1.0e-6, 1.0e-6));
+        VectorialPointValuePair optimum =
+            optimizer.optimize(problem, problem.target, new double[] { 1, 1, 1, 1, 1, 1 },
+                               new double[] { 0, 0, 0, 0, 0, 0 });
+        assertEquals(0, optimizer.getRMS(), 1.0e-10);
+        for (int i = 0; i < problem.target.length; ++i) {
+            assertEquals(0.55 * i, optimum.getPoint()[i], 1.0e-10);
+        }
+    }
+
+    public void testOneSet() throws FunctionEvaluationException, OptimizationException {
+
+        LinearProblem problem = new LinearProblem(new double[][] {
+                {  1,  0, 0 },
+                { -1,  1, 0 },
+                {  0, -1, 1 }
+        }, new double[] { 1, 1, 1});
+        GaussNewtonOptimizer optimizer = new GaussNewtonOptimizer(true);
+        optimizer.setMaxIterations(100);
+        optimizer.setConvergenceChecker(new SimpleVectorialValueChecker(1.0e-6, 1.0e-6));
+        VectorialPointValuePair optimum =
+            optimizer.optimize(problem, problem.target, new double[] { 1, 1, 1 }, new double[] { 0, 0, 0 });
+        assertEquals(0, optimizer.getRMS(), 1.0e-10);
+        assertEquals(1.0, optimum.getPoint()[0], 1.0e-10);
+        assertEquals(2.0, optimum.getPoint()[1], 1.0e-10);
+        assertEquals(3.0, optimum.getPoint()[2], 1.0e-10);
+
+    }
+
+    public void testTwoSets() throws FunctionEvaluationException, OptimizationException {
+        double epsilon = 1.0e-7;
+        LinearProblem problem = new LinearProblem(new double[][] {
+                {  2,  1,   0,  4,       0, 0 },
+                { -4, -2,   3, -7,       0, 0 },
+                {  4,  1,  -2,  8,       0, 0 },
+                {  0, -3, -12, -1,       0, 0 },
+                {  0,  0,   0,  0, epsilon, 1 },
+                {  0,  0,   0,  0,       1, 1 }
+        }, new double[] { 2, -9, 2, 2, 1 + epsilon * epsilon, 2});
+
+        GaussNewtonOptimizer optimizer = new GaussNewtonOptimizer(true);
+        optimizer.setMaxIterations(100);
+        optimizer.setConvergenceChecker(new SimpleVectorialValueChecker(1.0e-6, 1.0e-6));
+        VectorialPointValuePair optimum =
+            optimizer.optimize(problem, problem.target, new double[] { 1, 1, 1, 1, 1, 1 },
+                               new double[] { 0, 0, 0, 0, 0, 0 });
+        assertEquals(0, optimizer.getRMS(), 1.0e-10);
+        assertEquals( 3.0, optimum.getPoint()[0], 1.0e-10);
+        assertEquals( 4.0, optimum.getPoint()[1], 1.0e-10);
+        assertEquals(-1.0, optimum.getPoint()[2], 1.0e-10);
+        assertEquals(-2.0, optimum.getPoint()[3], 1.0e-10);
+        assertEquals( 1.0 + epsilon, optimum.getPoint()[4], 1.0e-10);
+        assertEquals( 1.0 - epsilon, optimum.getPoint()[5], 1.0e-10);
+
+    }
+
+    public void testNonInversible() throws Exception {
+
+        LinearProblem problem = new LinearProblem(new double[][] {
+                {  1, 2, -3 },
+                {  2, 1,  3 },
+                { -3, 0, -9 }
+        }, new double[] { 1, 1, 1 });
+        GaussNewtonOptimizer optimizer = new GaussNewtonOptimizer(true);
+        optimizer.setMaxIterations(100);
+        optimizer.setConvergenceChecker(new SimpleVectorialValueChecker(1.0e-6, 1.0e-6));
+        try {
+            optimizer.optimize(problem, problem.target, new double[] { 1, 1, 1 }, new double[] { 0, 0, 0 });
+            fail("an exception should have been caught");
+        } catch (OptimizationException ee) {
+            // expected behavior
+        }
+    }
+
+    public void testIllConditioned() throws FunctionEvaluationException, OptimizationException {
+        LinearProblem problem1 = new LinearProblem(new double[][] {
+                { 10.0, 7.0,  8.0,  7.0 },
+                {  7.0, 5.0,  6.0,  5.0 },
+                {  8.0, 6.0, 10.0,  9.0 },
+                {  7.0, 5.0,  9.0, 10.0 }
+        }, new double[] { 32, 23, 33, 31 });
+        GaussNewtonOptimizer optimizer = new GaussNewtonOptimizer(true);
+        optimizer.setMaxIterations(100);
+        optimizer.setConvergenceChecker(new SimpleVectorialValueChecker(1.0e-6, 1.0e-6));
+        VectorialPointValuePair optimum1 =
+            optimizer.optimize(problem1, problem1.target, new double[] { 1, 1, 1, 1 },
+                               new double[] { 0, 1, 2, 3 });
+        assertEquals(0, optimizer.getRMS(), 1.0e-10);
+        assertEquals(1.0, optimum1.getPoint()[0], 1.0e-10);
+        assertEquals(1.0, optimum1.getPoint()[1], 1.0e-10);
+        assertEquals(1.0, optimum1.getPoint()[2], 1.0e-10);
+        assertEquals(1.0, optimum1.getPoint()[3], 1.0e-10);
+
+        LinearProblem problem2 = new LinearProblem(new double[][] {
+                { 10.00, 7.00, 8.10, 7.20 },
+                {  7.08, 5.04, 6.00, 5.00 },
+                {  8.00, 5.98, 9.89, 9.00 },
+                {  6.99, 4.99, 9.00, 9.98 }
+        }, new double[] { 32, 23, 33, 31 });
+        VectorialPointValuePair optimum2 =
+            optimizer.optimize(problem2, problem2.target, new double[] { 1, 1, 1, 1 },
+                               new double[] { 0, 1, 2, 3 });
+        assertEquals(0, optimizer.getRMS(), 1.0e-10);
+        assertEquals(-81.0, optimum2.getPoint()[0], 1.0e-8);
+        assertEquals(137.0, optimum2.getPoint()[1], 1.0e-8);
+        assertEquals(-34.0, optimum2.getPoint()[2], 1.0e-8);
+        assertEquals( 22.0, optimum2.getPoint()[3], 1.0e-8);
+
+    }
+
+    public void testMoreEstimatedParametersSimple() throws Exception {
+
+        LinearProblem problem = new LinearProblem(new double[][] {
+                { 3.0, 2.0,  0.0, 0.0 },
+                { 0.0, 1.0, -1.0, 1.0 },
+                { 2.0, 0.0,  1.0, 0.0 }
+        }, new double[] { 7.0, 3.0, 5.0 });
+
+        GaussNewtonOptimizer optimizer = new GaussNewtonOptimizer(true);
+        optimizer.setMaxIterations(100);
+        optimizer.setConvergenceChecker(new SimpleVectorialValueChecker(1.0e-6, 1.0e-6));
+        try {
+            optimizer.optimize(problem, problem.target, new double[] { 1, 1, 1 },
+                               new double[] { 7, 6, 5, 4 });
+            fail("an exception should have been caught");
+        } catch (OptimizationException ee) {
+            // expected behavior
+        }
+
+    }
+
+    public void testMoreEstimatedParametersUnsorted() throws Exception {
+        LinearProblem problem = new LinearProblem(new double[][] {
+                 { 1.0, 1.0,  0.0,  0.0, 0.0,  0.0 },
+                 { 0.0, 0.0,  1.0,  1.0, 1.0,  0.0 },
+                 { 0.0, 0.0,  0.0,  0.0, 1.0, -1.0 },
+                 { 0.0, 0.0, -1.0,  1.0, 0.0,  1.0 },
+                 { 0.0, 0.0,  0.0, -1.0, 1.0,  0.0 }
+        }, new double[] { 3.0, 12.0, -1.0, 7.0, 1.0 });
+        GaussNewtonOptimizer optimizer = new GaussNewtonOptimizer(true);
+        optimizer.setMaxIterations(100);
+        optimizer.setConvergenceChecker(new SimpleVectorialValueChecker(1.0e-6, 1.0e-6));
+        try {
+            optimizer.optimize(problem, problem.target, new double[] { 1, 1, 1, 1, 1 },
+                               new double[] { 2, 2, 2, 2, 2, 2 });
+            fail("an exception should have been caught");
+        } catch (OptimizationException ee) {
+            // expected behavior
+        }
+    }
+
+    public void testRedundantEquations() throws FunctionEvaluationException, OptimizationException {
+        LinearProblem problem = new LinearProblem(new double[][] {
+                { 1.0,  1.0 },
+                { 1.0, -1.0 },
+                { 1.0,  3.0 }
+        }, new double[] { 3.0, 1.0, 5.0 });
+
+        GaussNewtonOptimizer optimizer = new GaussNewtonOptimizer(true);
+        optimizer.setMaxIterations(100);
+        optimizer.setConvergenceChecker(new SimpleVectorialValueChecker(1.0e-6, 1.0e-6));
+        VectorialPointValuePair optimum =
+            optimizer.optimize(problem, problem.target, new double[] { 1, 1, 1 },
+                               new double[] { 1, 1 });
+        assertEquals(0, optimizer.getRMS(), 1.0e-10);
+        assertEquals(2.0, optimum.getPoint()[0], 1.0e-8);
+        assertEquals(1.0, optimum.getPoint()[1], 1.0e-8);
+
+    }
+
+    public void testInconsistentEquations() throws FunctionEvaluationException, OptimizationException {
+        LinearProblem problem = new LinearProblem(new double[][] {
+                { 1.0,  1.0 },
+                { 1.0, -1.0 },
+                { 1.0,  3.0 }
+        }, new double[] { 3.0, 1.0, 4.0 });
+
+        GaussNewtonOptimizer optimizer = new GaussNewtonOptimizer(true);
+        optimizer.setMaxIterations(100);
+        optimizer.setConvergenceChecker(new SimpleVectorialValueChecker(1.0e-6, 1.0e-6));
+        optimizer.optimize(problem, problem.target, new double[] { 1, 1, 1 }, new double[] { 1, 1 });
+        assertTrue(optimizer.getRMS() > 0.1);
+
+    }
+
+    public void testInconsistentSizes() throws FunctionEvaluationException, OptimizationException {
+        LinearProblem problem =
+            new LinearProblem(new double[][] { { 1, 0 }, { 0, 1 } }, new double[] { -1, 1 });
+        GaussNewtonOptimizer optimizer = new GaussNewtonOptimizer(true);
+        optimizer.setMaxIterations(100);
+        optimizer.setConvergenceChecker(new SimpleVectorialValueChecker(1.0e-6, 1.0e-6));
+
+        VectorialPointValuePair optimum =
+            optimizer.optimize(problem, problem.target, new double[] { 1, 1 }, new double[] { 0, 0 });
+        assertEquals(0, optimizer.getRMS(), 1.0e-10);
+        assertEquals(-1, optimum.getPoint()[0], 1.0e-10);
+        assertEquals(+1, optimum.getPoint()[1], 1.0e-10);
+
+        try {
+            optimizer.optimize(problem, problem.target,
+                               new double[] { 1 },
+                               new double[] { 0, 0 });
+            fail("an exception should have been thrown");
+        } catch (OptimizationException oe) {
+            // expected behavior
+        }
+
+        try {
+            optimizer.optimize(problem, new double[] { 1 },
+                               new double[] { 1 },
+                               new double[] { 0, 0 });
+            fail("an exception should have been thrown");
+        } catch (FunctionEvaluationException oe) {
+            // expected behavior
+        }
+
+    }
+
+    public void testMaxEvaluations() throws Exception {
+        Circle circle = new Circle();
+        circle.addPoint( 30.0,  68.0);
+        circle.addPoint( 50.0,  -6.0);
+        circle.addPoint(110.0, -20.0);
+        circle.addPoint( 35.0,  15.0);
+        circle.addPoint( 45.0,  97.0);
+        GaussNewtonOptimizer optimizer = new GaussNewtonOptimizer(true);
+        optimizer.setMaxIterations(100);
+        optimizer.setConvergenceChecker(new SimpleVectorialPointChecker(1.0e-30, 1.0e-30));
+        try {
+            optimizer.optimize(circle, new double[] { 0, 0, 0, 0, 0 },
+                               new double[] { 1, 1, 1, 1, 1 },
+                               new double[] { 98.680, 47.345 });
+            fail("an exception should have been caught");
+        } catch (OptimizationException ee) {
+            // expected behavior
+        }
+    }
+
+    public void testCircleFitting() throws FunctionEvaluationException, OptimizationException {
+        Circle circle = new Circle();
+        circle.addPoint( 30.0,  68.0);
+        circle.addPoint( 50.0,  -6.0);
+        circle.addPoint(110.0, -20.0);
+        circle.addPoint( 35.0,  15.0);
+        circle.addPoint( 45.0,  97.0);
+        GaussNewtonOptimizer optimizer = new GaussNewtonOptimizer(true);
+        optimizer.setMaxIterations(100);
+        optimizer.setConvergenceChecker(new SimpleVectorialValueChecker(1.0e-13, 1.0e-13));
+        VectorialPointValuePair optimum =
+            optimizer.optimize(circle, new double[] { 0, 0, 0, 0, 0 },
+                               new double[] { 1, 1, 1, 1, 1 },
+                               new double[] { 98.680, 47.345 });
+        assertEquals(1.768262623567235,  FastMath.sqrt(circle.getN()) * optimizer.getRMS(),  1.0e-10);
+        Point2D.Double center = new Point2D.Double(optimum.getPointRef()[0], optimum.getPointRef()[1]);
+        assertEquals(69.96016175359975, circle.getRadius(center), 1.0e-10);
+        assertEquals(96.07590209601095, center.x, 1.0e-10);
+        assertEquals(48.135167894714,   center.y, 1.0e-10);
+    }
+
+    public void testCircleFittingBadInit() throws FunctionEvaluationException, OptimizationException {
+        Circle circle = new Circle();
+        double[][] points = new double[][] {
+                {-0.312967,  0.072366}, {-0.339248,  0.132965}, {-0.379780,  0.202724},
+                {-0.390426,  0.260487}, {-0.361212,  0.328325}, {-0.346039,  0.392619},
+                {-0.280579,  0.444306}, {-0.216035,  0.470009}, {-0.149127,  0.493832},
+                {-0.075133,  0.483271}, {-0.007759,  0.452680}, { 0.060071,  0.410235},
+                { 0.103037,  0.341076}, { 0.118438,  0.273884}, { 0.131293,  0.192201},
+                { 0.115869,  0.129797}, { 0.072223,  0.058396}, { 0.022884,  0.000718},
+                {-0.053355, -0.020405}, {-0.123584, -0.032451}, {-0.216248, -0.032862},
+                {-0.278592, -0.005008}, {-0.337655,  0.056658}, {-0.385899,  0.112526},
+                {-0.405517,  0.186957}, {-0.415374,  0.262071}, {-0.387482,  0.343398},
+                {-0.347322,  0.397943}, {-0.287623,  0.458425}, {-0.223502,  0.475513},
+                {-0.135352,  0.478186}, {-0.061221,  0.483371}, { 0.003711,  0.422737},
+                { 0.065054,  0.375830}, { 0.108108,  0.297099}, { 0.123882,  0.222850},
+                { 0.117729,  0.134382}, { 0.085195,  0.056820}, { 0.029800, -0.019138},
+                {-0.027520, -0.072374}, {-0.102268, -0.091555}, {-0.200299, -0.106578},
+                {-0.292731, -0.091473}, {-0.356288, -0.051108}, {-0.420561,  0.014926},
+                {-0.471036,  0.074716}, {-0.488638,  0.182508}, {-0.485990,  0.254068},
+                {-0.463943,  0.338438}, {-0.406453,  0.404704}, {-0.334287,  0.466119},
+                {-0.254244,  0.503188}, {-0.161548,  0.495769}, {-0.075733,  0.495560},
+                { 0.001375,  0.434937}, { 0.082787,  0.385806}, { 0.115490,  0.323807},
+                { 0.141089,  0.223450}, { 0.138693,  0.131703}, { 0.126415,  0.049174},
+                { 0.066518, -0.010217}, {-0.005184, -0.070647}, {-0.080985, -0.103635},
+                {-0.177377, -0.116887}, {-0.260628, -0.100258}, {-0.335756, -0.056251},
+                {-0.405195, -0.000895}, {-0.444937,  0.085456}, {-0.484357,  0.175597},
+                {-0.472453,  0.248681}, {-0.438580,  0.347463}, {-0.402304,  0.422428},
+                {-0.326777,  0.479438}, {-0.247797,  0.505581}, {-0.152676,  0.519380},
+                {-0.071754,  0.516264}, { 0.015942,  0.472802}, { 0.076608,  0.419077},
+                { 0.127673,  0.330264}, { 0.159951,  0.262150}, { 0.153530,  0.172681},
+                { 0.140653,  0.089229}, { 0.078666,  0.024981}, { 0.023807, -0.037022},
+                {-0.048837, -0.077056}, {-0.127729, -0.075338}, {-0.221271, -0.067526}
+        };
+        double[] target = new double[points.length];
+        Arrays.fill(target, 0.0);
+        double[] weights = new double[points.length];
+        Arrays.fill(weights, 2.0);
+        for (int i = 0; i < points.length; ++i) {
+            circle.addPoint(points[i][0], points[i][1]);
+        }
+        GaussNewtonOptimizer optimizer = new GaussNewtonOptimizer(true);
+        optimizer.setMaxIterations(100);
+        optimizer.setConvergenceChecker(new SimpleVectorialValueChecker(1.0e-6, 1.0e-6));
+        try {
+            optimizer.optimize(circle, target, weights, new double[] { -12, -12 });
+            fail("an exception should have been caught");
+        } catch (OptimizationException ee) {
+            // expected behavior
+        }
+
+        VectorialPointValuePair optimum =
+            optimizer.optimize(circle, target, weights, new double[] { 0, 0 });
+        assertEquals(-0.1517383071957963, optimum.getPointRef()[0], 1.0e-6);
+        assertEquals(0.2074999736353867,  optimum.getPointRef()[1], 1.0e-6);
+        assertEquals(0.04268731682389561, optimizer.getRMS(),       1.0e-8);
+
+    }
+
+    private static class LinearProblem implements DifferentiableMultivariateVectorialFunction, Serializable {
+
+        private static final long serialVersionUID = -8804268799379350190L;
+        final RealMatrix factors;
+        final double[] target;
+        public LinearProblem(double[][] factors, double[] target) {
+            this.factors = new BlockRealMatrix(factors);
+            this.target  = target;
+        }
+
+        public double[] value(double[] variables) {
+            return factors.operate(variables);
+        }
+
+        public MultivariateMatrixFunction jacobian() {
+            return new MultivariateMatrixFunction() {
+                private static final long serialVersionUID = -8387467946663627585L;
+                public double[][] value(double[] point) {
+                    return factors.getData();
+                }
+            };
+        }
+
+    }
+
+    private static class Circle implements DifferentiableMultivariateVectorialFunction, Serializable {
+
+        private static final long serialVersionUID = -7165774454925027042L;
+        private ArrayList<Point2D.Double> points;
+
+        public Circle() {
+            points  = new ArrayList<Point2D.Double>();
+        }
+
+        public void addPoint(double px, double py) {
+            points.add(new Point2D.Double(px, py));
+        }
+
+        public int getN() {
+            return points.size();
+        }
+
+        public double getRadius(Point2D.Double center) {
+            double r = 0;
+            for (Point2D.Double point : points) {
+                r += point.distance(center);
+            }
+            return r / points.size();
+        }
+
+        private double[][] jacobian(double[] variables) {
+
+            int n = points.size();
+            Point2D.Double center = new Point2D.Double(variables[0], variables[1]);
+
+            // gradient of the optimal radius
+            double dRdX = 0;
+            double dRdY = 0;
+            for (Point2D.Double pk : points) {
+                double dk = pk.distance(center);
+                dRdX += (center.x - pk.x) / dk;
+                dRdY += (center.y - pk.y) / dk;
+            }
+            dRdX /= n;
+            dRdY /= n;
+
+            // jacobian of the radius residuals
+            double[][] jacobian = new double[n][2];
+            for (int i = 0; i < n; ++i) {
+                Point2D.Double pi = points.get(i);
+                double di   = pi.distance(center);
+                jacobian[i][0] = (center.x - pi.x) / di - dRdX;
+                jacobian[i][1] = (center.y - pi.y) / di - dRdY;
+           }
+
+            return jacobian;
+
+        }
+
+        public double[] value(double[] variables) {
+
+            Point2D.Double center = new Point2D.Double(variables[0], variables[1]);
+            double radius = getRadius(center);
+
+            double[] residuals = new double[points.size()];
+            for (int i = 0; i < residuals.length; ++i) {
+                residuals[i] = points.get(i).distance(center) - radius;
+            }
+
+            return residuals;
+
+        }
+
+        public MultivariateMatrixFunction jacobian() {
+            return new MultivariateMatrixFunction() {
+                private static final long serialVersionUID = -4340046230875165095L;
+                public double[][] value(double[] point) {
+                    return jacobian(point);
+                }
+            };
+        }
+
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/optimization/general/LevenbergMarquardtOptimizerTest.java b/src/test/java/org/apache/commons/math/optimization/general/LevenbergMarquardtOptimizerTest.java
new file mode 100644
index 0000000..7c50432
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/optimization/general/LevenbergMarquardtOptimizerTest.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.optimization.general;
+
+import java.awt.geom.Point2D;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+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.linear.BlockRealMatrix;
+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.VectorialPointValuePair;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * <p>Some of the unit tests are re-implementations of the MINPACK <a
+ * href="http://www.netlib.org/minpack/ex/file17">file17</a> and <a
+ * href="http://www.netlib.org/minpack/ex/file22">file22</a> test files.
+ * 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>
+
+ * @author Argonne National Laboratory. MINPACK project. March 1980 (original fortran minpack tests)
+ * @author Burton S. Garbow (original fortran minpack tests)
+ * @author Kenneth E. Hillstrom (original fortran minpack tests)
+ * @author Jorge J. More (original fortran minpack tests)
+ * @author Luc Maisonobe (non-minpack tests and minpack tests Java translation)
+ */
+public class LevenbergMarquardtOptimizerTest
+  extends TestCase {
+
+    public LevenbergMarquardtOptimizerTest(String name) {
+        super(name);
+    }
+
+    public void testTrivial() throws Exception {
+        LinearProblem problem =
+            new LinearProblem(new double[][] { { 2 } }, new double[] { 3 });
+        LevenbergMarquardtOptimizer optimizer = new LevenbergMarquardtOptimizer();
+        VectorialPointValuePair optimum =
+            optimizer.optimize(problem, problem.target, new double[] { 1 }, new double[] { 0 });
+        assertEquals(0, optimizer.getRMS(), 1.0e-10);
+        try {
+            optimizer.guessParametersErrors();
+            fail("an exception should have been thrown");
+        } catch (OptimizationException ee) {
+            // expected behavior
+        }
+        assertEquals(1.5, optimum.getPoint()[0], 1.0e-10);
+        assertEquals(3.0, optimum.getValue()[0], 1.0e-10);
+    }
+
+    public void testQRColumnsPermutation() throws Exception {
+
+        LinearProblem problem =
+            new LinearProblem(new double[][] { { 1.0, -1.0 }, { 0.0, 2.0 }, { 1.0, -2.0 } },
+                              new double[] { 4.0, 6.0, 1.0 });
+
+        LevenbergMarquardtOptimizer optimizer = new LevenbergMarquardtOptimizer();
+        VectorialPointValuePair optimum =
+            optimizer.optimize(problem, problem.target, new double[] { 1, 1, 1 }, new double[] { 0, 0 });
+        assertEquals(0, optimizer.getRMS(), 1.0e-10);
+        assertEquals(7.0, optimum.getPoint()[0], 1.0e-10);
+        assertEquals(3.0, optimum.getPoint()[1], 1.0e-10);
+        assertEquals(4.0, optimum.getValue()[0], 1.0e-10);
+        assertEquals(6.0, optimum.getValue()[1], 1.0e-10);
+        assertEquals(1.0, optimum.getValue()[2], 1.0e-10);
+
+    }
+
+    public void testNoDependency() throws Exception {
+        LinearProblem problem = new LinearProblem(new double[][] {
+                { 2, 0, 0, 0, 0, 0 },
+                { 0, 2, 0, 0, 0, 0 },
+                { 0, 0, 2, 0, 0, 0 },
+                { 0, 0, 0, 2, 0, 0 },
+                { 0, 0, 0, 0, 2, 0 },
+                { 0, 0, 0, 0, 0, 2 }
+        }, new double[] { 0.0, 1.1, 2.2, 3.3, 4.4, 5.5 });
+        LevenbergMarquardtOptimizer optimizer = new LevenbergMarquardtOptimizer();
+        VectorialPointValuePair optimum =
+            optimizer.optimize(problem, problem.target, new double[] { 1, 1, 1, 1, 1, 1 },
+                               new double[] { 0, 0, 0, 0, 0, 0 });
+        assertEquals(0, optimizer.getRMS(), 1.0e-10);
+        for (int i = 0; i < problem.target.length; ++i) {
+            assertEquals(0.55 * i, optimum.getPoint()[i], 1.0e-10);
+        }
+    }
+
+    public void testOneSet() throws Exception {
+
+        LinearProblem problem = new LinearProblem(new double[][] {
+                {  1,  0, 0 },
+                { -1,  1, 0 },
+                {  0, -1, 1 }
+        }, new double[] { 1, 1, 1});
+        LevenbergMarquardtOptimizer optimizer = new LevenbergMarquardtOptimizer();
+        VectorialPointValuePair optimum =
+            optimizer.optimize(problem, problem.target, new double[] { 1, 1, 1 }, new double[] { 0, 0, 0 });
+        assertEquals(0, optimizer.getRMS(), 1.0e-10);
+        assertEquals(1.0, optimum.getPoint()[0], 1.0e-10);
+        assertEquals(2.0, optimum.getPoint()[1], 1.0e-10);
+        assertEquals(3.0, optimum.getPoint()[2], 1.0e-10);
+
+    }
+
+    public void testTwoSets() throws Exception {
+        double epsilon = 1.0e-7;
+        LinearProblem problem = new LinearProblem(new double[][] {
+                {  2,  1,   0,  4,       0, 0 },
+                { -4, -2,   3, -7,       0, 0 },
+                {  4,  1,  -2,  8,       0, 0 },
+                {  0, -3, -12, -1,       0, 0 },
+                {  0,  0,   0,  0, epsilon, 1 },
+                {  0,  0,   0,  0,       1, 1 }
+        }, new double[] { 2, -9, 2, 2, 1 + epsilon * epsilon, 2});
+
+        LevenbergMarquardtOptimizer optimizer = new LevenbergMarquardtOptimizer();
+        VectorialPointValuePair optimum =
+            optimizer.optimize(problem, problem.target, new double[] { 1, 1, 1, 1, 1, 1 },
+                               new double[] { 0, 0, 0, 0, 0, 0 });
+        assertEquals(0, optimizer.getRMS(), 1.0e-10);
+        assertEquals( 3.0, optimum.getPoint()[0], 1.0e-10);
+        assertEquals( 4.0, optimum.getPoint()[1], 1.0e-10);
+        assertEquals(-1.0, optimum.getPoint()[2], 1.0e-10);
+        assertEquals(-2.0, optimum.getPoint()[3], 1.0e-10);
+        assertEquals( 1.0 + epsilon, optimum.getPoint()[4], 1.0e-10);
+        assertEquals( 1.0 - epsilon, optimum.getPoint()[5], 1.0e-10);
+
+    }
+
+    public void testNonInversible() throws Exception {
+
+        LinearProblem problem = new LinearProblem(new double[][] {
+                {  1, 2, -3 },
+                {  2, 1,  3 },
+                { -3, 0, -9 }
+        }, new double[] { 1, 1, 1 });
+
+        LevenbergMarquardtOptimizer optimizer = new LevenbergMarquardtOptimizer();
+        optimizer.optimize(problem, problem.target, new double[] { 1, 1, 1 }, new double[] { 0, 0, 0 });
+        assertTrue(FastMath.sqrt(problem.target.length) * optimizer.getRMS() > 0.6);
+        try {
+            optimizer.getCovariances();
+            fail("an exception should have been thrown");
+        } catch (OptimizationException ee) {
+            // expected behavior
+        }
+
+    }
+
+    public void testIllConditioned() throws Exception {
+        LinearProblem problem1 = new LinearProblem(new double[][] {
+                { 10.0, 7.0,  8.0,  7.0 },
+                {  7.0, 5.0,  6.0,  5.0 },
+                {  8.0, 6.0, 10.0,  9.0 },
+                {  7.0, 5.0,  9.0, 10.0 }
+        }, new double[] { 32, 23, 33, 31 });
+        LevenbergMarquardtOptimizer optimizer = new LevenbergMarquardtOptimizer();
+        VectorialPointValuePair optimum1 =
+            optimizer.optimize(problem1, problem1.target, new double[] { 1, 1, 1, 1 },
+                               new double[] { 0, 1, 2, 3 });
+        assertEquals(0, optimizer.getRMS(), 1.0e-10);
+        assertEquals(1.0, optimum1.getPoint()[0], 1.0e-10);
+        assertEquals(1.0, optimum1.getPoint()[1], 1.0e-10);
+        assertEquals(1.0, optimum1.getPoint()[2], 1.0e-10);
+        assertEquals(1.0, optimum1.getPoint()[3], 1.0e-10);
+
+        LinearProblem problem2 = new LinearProblem(new double[][] {
+                { 10.00, 7.00, 8.10, 7.20 },
+                {  7.08, 5.04, 6.00, 5.00 },
+                {  8.00, 5.98, 9.89, 9.00 },
+                {  6.99, 4.99, 9.00, 9.98 }
+        }, new double[] { 32, 23, 33, 31 });
+        VectorialPointValuePair optimum2 =
+            optimizer.optimize(problem2, problem2.target, new double[] { 1, 1, 1, 1 },
+                               new double[] { 0, 1, 2, 3 });
+        assertEquals(0, optimizer.getRMS(), 1.0e-10);
+        assertEquals(-81.0, optimum2.getPoint()[0], 1.0e-8);
+        assertEquals(137.0, optimum2.getPoint()[1], 1.0e-8);
+        assertEquals(-34.0, optimum2.getPoint()[2], 1.0e-8);
+        assertEquals( 22.0, optimum2.getPoint()[3], 1.0e-8);
+
+    }
+
+    public void testMoreEstimatedParametersSimple() throws Exception {
+
+        LinearProblem problem = new LinearProblem(new double[][] {
+                { 3.0, 2.0,  0.0, 0.0 },
+                { 0.0, 1.0, -1.0, 1.0 },
+                { 2.0, 0.0,  1.0, 0.0 }
+        }, new double[] { 7.0, 3.0, 5.0 });
+
+        LevenbergMarquardtOptimizer optimizer = new LevenbergMarquardtOptimizer();
+        optimizer.optimize(problem, problem.target, new double[] { 1, 1, 1 },
+                new double[] { 7, 6, 5, 4 });
+        assertEquals(0, optimizer.getRMS(), 1.0e-10);
+
+    }
+
+    public void testMoreEstimatedParametersUnsorted() throws Exception {
+        LinearProblem problem = new LinearProblem(new double[][] {
+                { 1.0, 1.0,  0.0,  0.0, 0.0,  0.0 },
+                { 0.0, 0.0,  1.0,  1.0, 1.0,  0.0 },
+                { 0.0, 0.0,  0.0,  0.0, 1.0, -1.0 },
+                { 0.0, 0.0, -1.0,  1.0, 0.0,  1.0 },
+                { 0.0, 0.0,  0.0, -1.0, 1.0,  0.0 }
+       }, new double[] { 3.0, 12.0, -1.0, 7.0, 1.0 });
+
+        LevenbergMarquardtOptimizer optimizer = new LevenbergMarquardtOptimizer();
+        VectorialPointValuePair optimum =
+            optimizer.optimize(problem, problem.target, new double[] { 1, 1, 1, 1, 1 },
+                               new double[] { 2, 2, 2, 2, 2, 2 });
+        assertEquals(0, optimizer.getRMS(), 1.0e-10);
+        assertEquals(3.0, optimum.getPointRef()[2], 1.0e-10);
+        assertEquals(4.0, optimum.getPointRef()[3], 1.0e-10);
+        assertEquals(5.0, optimum.getPointRef()[4], 1.0e-10);
+        assertEquals(6.0, optimum.getPointRef()[5], 1.0e-10);
+
+    }
+
+    public void testRedundantEquations() throws Exception {
+        LinearProblem problem = new LinearProblem(new double[][] {
+                { 1.0,  1.0 },
+                { 1.0, -1.0 },
+                { 1.0,  3.0 }
+        }, new double[] { 3.0, 1.0, 5.0 });
+
+        LevenbergMarquardtOptimizer optimizer = new LevenbergMarquardtOptimizer();
+        VectorialPointValuePair optimum =
+            optimizer.optimize(problem, problem.target, new double[] { 1, 1, 1 },
+                               new double[] { 1, 1 });
+        assertEquals(0, optimizer.getRMS(), 1.0e-10);
+        assertEquals(2.0, optimum.getPointRef()[0], 1.0e-10);
+        assertEquals(1.0, optimum.getPointRef()[1], 1.0e-10);
+
+    }
+
+    public void testInconsistentEquations() throws FunctionEvaluationException, OptimizationException {
+        LinearProblem problem = new LinearProblem(new double[][] {
+                { 1.0,  1.0 },
+                { 1.0, -1.0 },
+                { 1.0,  3.0 }
+        }, new double[] { 3.0, 1.0, 4.0 });
+
+        LevenbergMarquardtOptimizer optimizer = new LevenbergMarquardtOptimizer();
+        optimizer.optimize(problem, problem.target, new double[] { 1, 1, 1 }, new double[] { 1, 1 });
+        assertTrue(optimizer.getRMS() > 0.1);
+
+    }
+
+    public void testInconsistentSizes() throws FunctionEvaluationException, OptimizationException {
+        LinearProblem problem =
+            new LinearProblem(new double[][] { { 1, 0 }, { 0, 1 } }, new double[] { -1, 1 });
+        LevenbergMarquardtOptimizer optimizer = new LevenbergMarquardtOptimizer();
+
+        VectorialPointValuePair optimum =
+            optimizer.optimize(problem, problem.target, new double[] { 1, 1 }, new double[] { 0, 0 });
+        assertEquals(0, optimizer.getRMS(), 1.0e-10);
+        assertEquals(-1, optimum.getPoint()[0], 1.0e-10);
+        assertEquals(+1, optimum.getPoint()[1], 1.0e-10);
+
+        try {
+            optimizer.optimize(problem, problem.target,
+                               new double[] { 1 },
+                               new double[] { 0, 0 });
+            fail("an exception should have been thrown");
+        } catch (OptimizationException oe) {
+            // expected behavior
+        }
+
+        try {
+            optimizer.optimize(problem, new double[] { 1 },
+                               new double[] { 1 },
+                               new double[] { 0, 0 });
+            fail("an exception should have been thrown");
+        } catch (FunctionEvaluationException oe) {
+            // expected behavior
+        }
+
+    }
+
+    public void testControlParameters() {
+        Circle circle = new Circle();
+        circle.addPoint( 30.0,  68.0);
+        circle.addPoint( 50.0,  -6.0);
+        circle.addPoint(110.0, -20.0);
+        circle.addPoint( 35.0,  15.0);
+        circle.addPoint( 45.0,  97.0);
+        checkEstimate(circle, 0.1, 10, 1.0e-14, 1.0e-16, 1.0e-10, false);
+        checkEstimate(circle, 0.1, 10, 1.0e-15, 1.0e-17, 1.0e-10, true);
+        checkEstimate(circle, 0.1,  5, 1.0e-15, 1.0e-16, 1.0e-10, true);
+        circle.addPoint(300, -300);
+        checkEstimate(circle, 0.1, 20, 1.0e-18, 1.0e-16, 1.0e-10, true);
+    }
+
+    private void checkEstimate(DifferentiableMultivariateVectorialFunction problem,
+                               double initialStepBoundFactor, int maxCostEval,
+                               double costRelativeTolerance, double parRelativeTolerance,
+                               double orthoTolerance, boolean shouldFail) {
+        try {
+            LevenbergMarquardtOptimizer optimizer = new LevenbergMarquardtOptimizer();
+            optimizer.setInitialStepBoundFactor(initialStepBoundFactor);
+            optimizer.setMaxIterations(maxCostEval);
+            optimizer.setCostRelativeTolerance(costRelativeTolerance);
+            optimizer.setParRelativeTolerance(parRelativeTolerance);
+            optimizer.setOrthoTolerance(orthoTolerance);
+            optimizer.optimize(problem, new double[] { 0, 0, 0, 0, 0 }, new double[] { 1, 1, 1, 1, 1 },
+                               new double[] { 98.680, 47.345 });
+            assertTrue(! shouldFail);
+        } catch (OptimizationException ee) {
+            assertTrue(shouldFail);
+        } catch (FunctionEvaluationException ee) {
+            assertTrue(shouldFail);
+        }
+    }
+
+    public void testCircleFitting() throws Exception {
+        Circle circle = new Circle();
+        circle.addPoint( 30.0,  68.0);
+        circle.addPoint( 50.0,  -6.0);
+        circle.addPoint(110.0, -20.0);
+        circle.addPoint( 35.0,  15.0);
+        circle.addPoint( 45.0,  97.0);
+        LevenbergMarquardtOptimizer optimizer = new LevenbergMarquardtOptimizer();
+        VectorialPointValuePair optimum =
+            optimizer.optimize(circle, new double[] { 0, 0, 0, 0, 0 }, new double[] { 1, 1, 1, 1, 1 },
+                               new double[] { 98.680, 47.345 });
+        assertTrue(optimizer.getEvaluations() < 10);
+        assertTrue(optimizer.getJacobianEvaluations() < 10);
+        double rms = optimizer.getRMS();
+        assertEquals(1.768262623567235,  FastMath.sqrt(circle.getN()) * rms,  1.0e-10);
+        Point2D.Double center = new Point2D.Double(optimum.getPointRef()[0], optimum.getPointRef()[1]);
+        assertEquals(69.96016176931406, circle.getRadius(center), 1.0e-10);
+        assertEquals(96.07590211815305, center.x,      1.0e-10);
+        assertEquals(48.13516790438953, center.y,      1.0e-10);
+        double[][] cov = optimizer.getCovariances();
+        assertEquals(1.839, cov[0][0], 0.001);
+        assertEquals(0.731, cov[0][1], 0.001);
+        assertEquals(cov[0][1], cov[1][0], 1.0e-14);
+        assertEquals(0.786, cov[1][1], 0.001);
+        double[] errors = optimizer.guessParametersErrors();
+        assertEquals(1.384, errors[0], 0.001);
+        assertEquals(0.905, errors[1], 0.001);
+
+        // add perfect measurements and check errors are reduced
+        double  r = circle.getRadius(center);
+        for (double d= 0; d < 2 * FastMath.PI; d += 0.01) {
+            circle.addPoint(center.x + r * FastMath.cos(d), center.y + r * FastMath.sin(d));
+        }
+        double[] target = new double[circle.getN()];
+        Arrays.fill(target, 0.0);
+        double[] weights = new double[circle.getN()];
+        Arrays.fill(weights, 2.0);
+        optimizer.optimize(circle, target, weights, new double[] { 98.680, 47.345 });
+        cov = optimizer.getCovariances();
+        assertEquals(0.0016, cov[0][0], 0.001);
+        assertEquals(3.2e-7, cov[0][1], 1.0e-9);
+        assertEquals(cov[0][1], cov[1][0], 1.0e-14);
+        assertEquals(0.0016, cov[1][1], 0.001);
+        errors = optimizer.guessParametersErrors();
+        assertEquals(0.004, errors[0], 0.001);
+        assertEquals(0.004, errors[1], 0.001);
+
+    }
+
+    public void testCircleFittingBadInit() throws FunctionEvaluationException, OptimizationException {
+        Circle circle = new Circle();
+        double[][] points = new double[][] {
+                {-0.312967,  0.072366}, {-0.339248,  0.132965}, {-0.379780,  0.202724},
+                {-0.390426,  0.260487}, {-0.361212,  0.328325}, {-0.346039,  0.392619},
+                {-0.280579,  0.444306}, {-0.216035,  0.470009}, {-0.149127,  0.493832},
+                {-0.075133,  0.483271}, {-0.007759,  0.452680}, { 0.060071,  0.410235},
+                { 0.103037,  0.341076}, { 0.118438,  0.273884}, { 0.131293,  0.192201},
+                { 0.115869,  0.129797}, { 0.072223,  0.058396}, { 0.022884,  0.000718},
+                {-0.053355, -0.020405}, {-0.123584, -0.032451}, {-0.216248, -0.032862},
+                {-0.278592, -0.005008}, {-0.337655,  0.056658}, {-0.385899,  0.112526},
+                {-0.405517,  0.186957}, {-0.415374,  0.262071}, {-0.387482,  0.343398},
+                {-0.347322,  0.397943}, {-0.287623,  0.458425}, {-0.223502,  0.475513},
+                {-0.135352,  0.478186}, {-0.061221,  0.483371}, { 0.003711,  0.422737},
+                { 0.065054,  0.375830}, { 0.108108,  0.297099}, { 0.123882,  0.222850},
+                { 0.117729,  0.134382}, { 0.085195,  0.056820}, { 0.029800, -0.019138},
+                {-0.027520, -0.072374}, {-0.102268, -0.091555}, {-0.200299, -0.106578},
+                {-0.292731, -0.091473}, {-0.356288, -0.051108}, {-0.420561,  0.014926},
+                {-0.471036,  0.074716}, {-0.488638,  0.182508}, {-0.485990,  0.254068},
+                {-0.463943,  0.338438}, {-0.406453,  0.404704}, {-0.334287,  0.466119},
+                {-0.254244,  0.503188}, {-0.161548,  0.495769}, {-0.075733,  0.495560},
+                { 0.001375,  0.434937}, { 0.082787,  0.385806}, { 0.115490,  0.323807},
+                { 0.141089,  0.223450}, { 0.138693,  0.131703}, { 0.126415,  0.049174},
+                { 0.066518, -0.010217}, {-0.005184, -0.070647}, {-0.080985, -0.103635},
+                {-0.177377, -0.116887}, {-0.260628, -0.100258}, {-0.335756, -0.056251},
+                {-0.405195, -0.000895}, {-0.444937,  0.085456}, {-0.484357,  0.175597},
+                {-0.472453,  0.248681}, {-0.438580,  0.347463}, {-0.402304,  0.422428},
+                {-0.326777,  0.479438}, {-0.247797,  0.505581}, {-0.152676,  0.519380},
+                {-0.071754,  0.516264}, { 0.015942,  0.472802}, { 0.076608,  0.419077},
+                { 0.127673,  0.330264}, { 0.159951,  0.262150}, { 0.153530,  0.172681},
+                { 0.140653,  0.089229}, { 0.078666,  0.024981}, { 0.023807, -0.037022},
+                {-0.048837, -0.077056}, {-0.127729, -0.075338}, {-0.221271, -0.067526}
+        };
+        double[] target = new double[points.length];
+        Arrays.fill(target, 0.0);
+        double[] weights = new double[points.length];
+        Arrays.fill(weights, 2.0);
+        for (int i = 0; i < points.length; ++i) {
+            circle.addPoint(points[i][0], points[i][1]);
+        }
+        LevenbergMarquardtOptimizer optimizer = new LevenbergMarquardtOptimizer();
+        optimizer.setConvergenceChecker(new SimpleVectorialValueChecker(1.0e-8, 1.0e-8));
+        VectorialPointValuePair optimum =
+            optimizer.optimize(circle, target, weights, new double[] { -12, -12 });
+        Point2D.Double center = new Point2D.Double(optimum.getPointRef()[0], optimum.getPointRef()[1]);
+        assertTrue(optimizer.getEvaluations() < 25);
+        assertTrue(optimizer.getJacobianEvaluations() < 20);
+        assertEquals( 0.043, optimizer.getRMS(), 1.0e-3);
+        assertEquals( 0.292235,  circle.getRadius(center), 1.0e-6);
+        assertEquals(-0.151738,  center.x,      1.0e-6);
+        assertEquals( 0.2075001, center.y,      1.0e-6);
+    }
+
+    public void testMath199() throws FunctionEvaluationException {
+        try {
+            QuadraticProblem problem = new QuadraticProblem();
+            problem.addPoint (0, -3.182591015485607);
+            problem.addPoint (1, -2.5581184967730577);
+            problem.addPoint (2, -2.1488478161387325);
+            problem.addPoint (3, -1.9122489313410047);
+            problem.addPoint (4, 1.7785661310051026);
+            LevenbergMarquardtOptimizer optimizer = new LevenbergMarquardtOptimizer();
+            optimizer.setQRRankingThreshold(0);
+            optimizer.optimize(problem,
+                               new double[] { 0, 0, 0, 0, 0 },
+                               new double[] { 0.0, 4.4e-323, 1.0, 4.4e-323, 0.0 },
+                               new double[] { 0, 0, 0 });
+            fail("an exception should have been thrown");
+        } catch (OptimizationException ee) {
+            // expected behavior
+        }
+
+    }
+
+    private static class LinearProblem implements DifferentiableMultivariateVectorialFunction, Serializable {
+
+        private static final long serialVersionUID = 703247177355019415L;
+        final RealMatrix factors;
+        final double[] target;
+        public LinearProblem(double[][] factors, double[] target) {
+            this.factors = new BlockRealMatrix(factors);
+            this.target  = target;
+        }
+
+        public double[] value(double[] variables) {
+            return factors.operate(variables);
+        }
+
+        public MultivariateMatrixFunction jacobian() {
+            return new MultivariateMatrixFunction() {
+                private static final long serialVersionUID = 556396458721526234L;
+                public double[][] value(double[] point) {
+                    return factors.getData();
+                }
+            };
+        }
+
+    }
+
+    private static class Circle implements DifferentiableMultivariateVectorialFunction, Serializable {
+
+        private static final long serialVersionUID = -4711170319243817874L;
+
+        private ArrayList<Point2D.Double> points;
+
+        public Circle() {
+            points  = new ArrayList<Point2D.Double>();
+        }
+
+        public void addPoint(double px, double py) {
+            points.add(new Point2D.Double(px, py));
+        }
+
+        public int getN() {
+            return points.size();
+        }
+
+        public double getRadius(Point2D.Double center) {
+            double r = 0;
+            for (Point2D.Double point : points) {
+                r += point.distance(center);
+            }
+            return r / points.size();
+        }
+
+        private double[][] jacobian(double[] point) {
+
+            int n = points.size();
+            Point2D.Double center = new Point2D.Double(point[0], point[1]);
+
+            // gradient of the optimal radius
+            double dRdX = 0;
+            double dRdY = 0;
+            for (Point2D.Double pk : points) {
+                double dk = pk.distance(center);
+                dRdX += (center.x - pk.x) / dk;
+                dRdY += (center.y - pk.y) / dk;
+            }
+            dRdX /= n;
+            dRdY /= n;
+
+            // jacobian of the radius residuals
+            double[][] jacobian = new double[n][2];
+            for (int i = 0; i < n; ++i) {
+                Point2D.Double pi = points.get(i);
+                double di   = pi.distance(center);
+                jacobian[i][0] = (center.x - pi.x) / di - dRdX;
+                jacobian[i][1] = (center.y - pi.y) / di - dRdY;
+            }
+
+            return jacobian;
+
+        }
+
+        public double[] value(double[] variables)
+        throws FunctionEvaluationException, IllegalArgumentException {
+
+            Point2D.Double center = new Point2D.Double(variables[0], variables[1]);
+            double radius = getRadius(center);
+
+            double[] residuals = new double[points.size()];
+            for (int i = 0; i < residuals.length; ++i) {
+                residuals[i] = points.get(i).distance(center) - radius;
+            }
+
+            return residuals;
+
+        }
+
+        public MultivariateMatrixFunction jacobian() {
+            return new MultivariateMatrixFunction() {
+                private static final long serialVersionUID = -4340046230875165095L;
+                public double[][] value(double[] point) {
+                    return jacobian(point);
+                }
+            };
+        }
+
+    }
+
+    private static class QuadraticProblem implements DifferentiableMultivariateVectorialFunction, Serializable {
+
+        private static final long serialVersionUID = 7072187082052755854L;
+        private List<Double> x;
+        private List<Double> y;
+
+        public QuadraticProblem() {
+            x = new ArrayList<Double>();
+            y = new ArrayList<Double>();
+        }
+
+        public void addPoint(double x, double y) {
+            this.x.add(x);
+            this.y.add(y);
+        }
+
+        private double[][] jacobian(double[] variables) {
+            double[][] jacobian = new double[x.size()][3];
+            for (int i = 0; i < jacobian.length; ++i) {
+                jacobian[i][0] = x.get(i) * x.get(i);
+                jacobian[i][1] = x.get(i);
+                jacobian[i][2] = 1.0;
+            }
+            return jacobian;
+        }
+
+        public double[] value(double[] variables) {
+            double[] values = new double[x.size()];
+            for (int i = 0; i < values.length; ++i) {
+                values[i] = (variables[0] * x.get(i) + variables[1]) * x.get(i) + variables[2];
+            }
+            return values;
+        }
+
+        public MultivariateMatrixFunction jacobian() {
+            return new MultivariateMatrixFunction() {
+                private static final long serialVersionUID = -8673650298627399464L;
+                public double[][] value(double[] point) {
+                    return jacobian(point);
+                }
+            };
+        }
+
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/optimization/general/MinpackTest.java b/src/test/java/org/apache/commons/math/optimization/general/MinpackTest.java
new file mode 100644
index 0000000..fe3d5bf
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/optimization/general/MinpackTest.java
@@ -0,0 +1,1531 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.io.Serializable;
+import java.util.Arrays;
+
+import junit.framework.TestCase;
+
+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.OptimizationException;
+import org.apache.commons.math.optimization.VectorialPointValuePair;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * <p>Some of the unit tests are re-implementations of the MINPACK <a
+ * href="http://www.netlib.org/minpack/ex/file17">file17</a> and <a
+ * href="http://www.netlib.org/minpack/ex/file22">file22</a> test files.
+ * 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>
+
+ * @author Argonne National Laboratory. MINPACK project. March 1980 (original fortran minpack tests)
+ * @author Burton S. Garbow (original fortran minpack tests)
+ * @author Kenneth E. Hillstrom (original fortran minpack tests)
+ * @author Jorge J. More (original fortran minpack tests)
+ * @author Luc Maisonobe (non-minpack tests and minpack tests Java translation)
+ */
+public class MinpackTest extends TestCase {
+
+  public MinpackTest(String name) {
+    super(name);
+  }
+
+  public void testMinpackLinearFullRank() {
+    minpackTest(new LinearFullRankFunction(10, 5, 1.0,
+                                           5.0, 2.23606797749979), false);
+    minpackTest(new LinearFullRankFunction(50, 5, 1.0,
+                                           8.06225774829855, 6.70820393249937), false);
+  }
+
+  public void testMinpackLinearRank1() {
+    minpackTest(new LinearRank1Function(10, 5, 1.0,
+                                        291.521868819476, 1.4638501094228), false);
+    minpackTest(new LinearRank1Function(50, 5, 1.0,
+                                        3101.60039334535, 3.48263016573496), false);
+  }
+
+  public void testMinpackLinearRank1ZeroColsAndRows() {
+    minpackTest(new LinearRank1ZeroColsAndRowsFunction(10, 5, 1.0), false);
+    minpackTest(new LinearRank1ZeroColsAndRowsFunction(50, 5, 1.0), false);
+  }
+
+  public void testMinpackRosenbrok() {
+    minpackTest(new RosenbrockFunction(new double[] { -1.2, 1.0 },
+                                       FastMath.sqrt(24.2)), false);
+    minpackTest(new RosenbrockFunction(new double[] { -12.0, 10.0 },
+                                       FastMath.sqrt(1795769.0)), false);
+    minpackTest(new RosenbrockFunction(new double[] { -120.0, 100.0 },
+                                       11.0 * FastMath.sqrt(169000121.0)), false);
+  }
+
+  public void testMinpackHelicalValley() {
+    minpackTest(new HelicalValleyFunction(new double[] { -1.0, 0.0, 0.0 },
+                                          50.0), false);
+    minpackTest(new HelicalValleyFunction(new double[] { -10.0, 0.0, 0.0 },
+                                          102.95630140987), false);
+    minpackTest(new HelicalValleyFunction(new double[] { -100.0, 0.0, 0.0},
+                                          991.261822123701), false);
+  }
+
+  public void testMinpackPowellSingular() {
+    minpackTest(new PowellSingularFunction(new double[] { 3.0, -1.0, 0.0, 1.0 },
+                                           14.6628782986152), false);
+    minpackTest(new PowellSingularFunction(new double[] { 30.0, -10.0, 0.0, 10.0 },
+                                           1270.9838708654), false);
+    minpackTest(new PowellSingularFunction(new double[] { 300.0, -100.0, 0.0, 100.0 },
+                                           126887.903284750), false);
+  }
+
+  public void testMinpackFreudensteinRoth() {
+    minpackTest(new FreudensteinRothFunction(new double[] { 0.5, -2.0 },
+                                             20.0124960961895, 6.99887517584575,
+                                             new double[] {
+                                               11.4124844654993,
+                                               -0.896827913731509
+                                             }), false);
+    minpackTest(new FreudensteinRothFunction(new double[] { 5.0, -20.0 },
+                                             12432.833948863, 6.9988751744895,
+                                             new double[] {
+                                                11.41300466147456,
+                                                -0.896796038685959
+                                             }), false);
+    minpackTest(new FreudensteinRothFunction(new double[] { 50.0, -200.0 },
+                                             11426454.595762, 6.99887517242903,
+                                             new double[] {
+                                                 11.412781785788564,
+                                                 -0.8968051074920405
+                                             }), false);
+  }
+
+  public void testMinpackBard() {
+    minpackTest(new BardFunction(1.0, 6.45613629515967, 0.0906359603390466,
+                                 new double[] {
+                                   0.0824105765758334,
+                                   1.1330366534715,
+                                   2.34369463894115
+                                 }), false);
+    minpackTest(new BardFunction(10.0, 36.1418531596785, 4.17476870138539,
+                                 new double[] {
+                                   0.840666673818329,
+                                   -158848033.259565,
+                                   -164378671.653535
+                                 }), false);
+    minpackTest(new BardFunction(100.0, 384.114678637399, 4.17476870135969,
+                                 new double[] {
+                                   0.840666673867645,
+                                   -158946167.205518,
+                                   -164464906.857771
+                                 }), false);
+  }
+
+  public void testMinpackKowalikOsborne() {
+    minpackTest(new KowalikOsborneFunction(new double[] { 0.25, 0.39, 0.415, 0.39 },
+                                           0.0728915102882945,
+                                           0.017535837721129,
+                                           new double[] {
+                                             0.192807810476249,
+                                             0.191262653354071,
+                                             0.123052801046931,
+                                             0.136053221150517
+                                           }), false);
+    minpackTest(new KowalikOsborneFunction(new double[] { 2.5, 3.9, 4.15, 3.9 },
+                                           2.97937007555202,
+                                           0.032052192917937,
+                                           new double[] {
+                                             728675.473768287,
+                                             -14.0758803129393,
+                                             -32977797.7841797,
+                                             -20571594.1977912
+                                           }), false);
+    minpackTest(new KowalikOsborneFunction(new double[] { 25.0, 39.0, 41.5, 39.0 },
+                                           29.9590617016037,
+                                           0.0175364017658228,
+                                           new double[] {
+                                             0.192948328597594,
+                                             0.188053165007911,
+                                             0.122430604321144,
+                                             0.134575665392506
+                                           }), false);
+  }
+
+  public void testMinpackMeyer() {
+    minpackTest(new MeyerFunction(new double[] { 0.02, 4000.0, 250.0 },
+                                  41153.4665543031, 9.37794514651874,
+                                  new double[] {
+                                    0.00560963647102661,
+                                    6181.34634628659,
+                                    345.223634624144
+                                  }), false);
+    minpackTest(new MeyerFunction(new double[] { 0.2, 40000.0, 2500.0 },
+                                  4168216.89130846, 792.917871779501,
+                                  new double[] {
+                                    1.42367074157994e-11,
+                                    33695.7133432541,
+                                    901.268527953801
+                                  }), true);
+  }
+
+  public void testMinpackWatson() {
+
+    minpackTest(new WatsonFunction(6, 0.0,
+                                   5.47722557505166, 0.0478295939097601,
+                                   new double[] {
+                                     -0.0157249615083782, 1.01243488232965,
+                                     -0.232991722387673,  1.26043101102818,
+                                     -1.51373031394421,   0.99299727291842
+                                   }), false);
+    minpackTest(new WatsonFunction(6, 10.0,
+                                   6433.12578950026, 0.0478295939096951,
+                                   new double[] {
+                                     -0.0157251901386677, 1.01243485860105,
+                                     -0.232991545843829,  1.26042932089163,
+                                     -1.51372776706575,   0.99299573426328
+                                   }), false);
+    minpackTest(new WatsonFunction(6, 100.0,
+                                   674256.040605213, 0.047829593911544,
+                                   new double[] {
+                                    -0.0157247019712586, 1.01243490925658,
+                                    -0.232991922761641,  1.26043292929555,
+                                    -1.51373320452707,   0.99299901922322
+                                   }), false);
+
+    minpackTest(new WatsonFunction(9, 0.0,
+                                   5.47722557505166, 0.00118311459212420,
+                                   new double[] {
+                                    -0.153070644166722e-4, 0.999789703934597,
+                                     0.0147639634910978,   0.146342330145992,
+                                     1.00082109454817,    -2.61773112070507,
+                                     4.10440313943354,    -3.14361226236241,
+                                     1.05262640378759
+                                   }), false);
+    minpackTest(new WatsonFunction(9, 10.0,
+                                   12088.127069307, 0.00118311459212513,
+                                   new double[] {
+                                   -0.153071334849279e-4, 0.999789703941234,
+                                    0.0147639629786217,   0.146342334818836,
+                                    1.00082107321386,    -2.61773107084722,
+                                    4.10440307655564,    -3.14361222178686,
+                                    1.05262639322589
+                                   }), false);
+    minpackTest(new WatsonFunction(9, 100.0,
+                                   1269109.29043834, 0.00118311459212384,
+                                   new double[] {
+                                    -0.153069523352176e-4, 0.999789703958371,
+                                     0.0147639625185392,   0.146342341096326,
+                                     1.00082104729164,    -2.61773101573645,
+                                     4.10440301427286,    -3.14361218602503,
+                                     1.05262638516774
+                                   }), false);
+
+    minpackTest(new WatsonFunction(12, 0.0,
+                                   5.47722557505166, 0.217310402535861e-4,
+                                   new double[] {
+                                    -0.660266001396382e-8, 1.00000164411833,
+                                    -0.000563932146980154, 0.347820540050756,
+                                    -0.156731500244233,    1.05281515825593,
+                                    -3.24727109519451,     7.2884347837505,
+                                   -10.271848098614,       9.07411353715783,
+                                    -4.54137541918194,     1.01201187975044
+                                   }), false);
+    minpackTest(new WatsonFunction(12, 10.0,
+                                   19220.7589790951, 0.217310402518509e-4,
+                                   new double[] {
+                                    -0.663710223017410e-8, 1.00000164411787,
+                                    -0.000563932208347327, 0.347820540486998,
+                                    -0.156731503955652,    1.05281517654573,
+                                    -3.2472711515214,      7.28843489430665,
+                                   -10.2718482369638,      9.07411364383733,
+                                    -4.54137546533666,     1.01201188830857
+                                   }), false);
+    minpackTest(new WatsonFunction(12, 100.0,
+                                   2018918.04462367, 0.217310402539845e-4,
+                                   new double[] {
+                                    -0.663806046485249e-8, 1.00000164411786,
+                                    -0.000563932210324959, 0.347820540503588,
+                                    -0.156731504091375,    1.05281517718031,
+                                    -3.24727115337025,     7.28843489775302,
+                                   -10.2718482410813,      9.07411364688464,
+                                    -4.54137546660822,     1.0120118885369
+                                   }), false);
+
+  }
+
+  public void testMinpackBox3Dimensional() {
+    minpackTest(new Box3DimensionalFunction(10, new double[] { 0.0, 10.0, 20.0 },
+                                            32.1115837449572), false);
+  }
+
+  public void testMinpackJennrichSampson() {
+    minpackTest(new JennrichSampsonFunction(10, new double[] { 0.3, 0.4 },
+                                            64.5856498144943, 11.1517793413499,
+                                            new double[] {
+ //                                            0.2578330049, 0.257829976764542
+                                               0.2578199266368004, 0.25782997676455244
+                                            }), false);
+  }
+
+  public void testMinpackBrownDennis() {
+    minpackTest(new BrownDennisFunction(20,
+                                        new double[] { 25.0, 5.0, -5.0, -1.0 },
+                                        2815.43839161816, 292.954288244866,
+                                        new double[] {
+                                         -11.59125141003, 13.2024883984741,
+                                         -0.403574643314272, 0.236736269844604
+                                        }), false);
+    minpackTest(new BrownDennisFunction(20,
+                                        new double[] { 250.0, 50.0, -50.0, -10.0 },
+                                        555073.354173069, 292.954270581415,
+                                        new double[] {
+                                         -11.5959274272203, 13.2041866926242,
+                                         -0.403417362841545, 0.236771143410386
+                                       }), false);
+    minpackTest(new BrownDennisFunction(20,
+                                        new double[] { 2500.0, 500.0, -500.0, -100.0 },
+                                        61211252.2338581, 292.954306151134,
+                                        new double[] {
+                                         -11.5902596937374, 13.2020628854665,
+                                         -0.403688070279258, 0.236665033746463
+                                        }), false);
+  }
+
+  public void testMinpackChebyquad() {
+    minpackTest(new ChebyquadFunction(1, 8, 1.0,
+                                      1.88623796907732, 1.88623796907732,
+                                      new double[] { 0.5 }), false);
+    minpackTest(new ChebyquadFunction(1, 8, 10.0,
+                                      5383344372.34005, 1.88424820499951,
+                                      new double[] { 0.9817314924684 }), false);
+    minpackTest(new ChebyquadFunction(1, 8, 100.0,
+                                      0.118088726698392e19, 1.88424820499347,
+                                      new double[] { 0.9817314852934 }), false);
+    minpackTest(new ChebyquadFunction(8, 8, 1.0,
+                                      0.196513862833975, 0.0593032355046727,
+                                      new double[] {
+                                        0.0431536648587336, 0.193091637843267,
+                                        0.266328593812698,  0.499999334628884,
+                                        0.500000665371116,  0.733671406187302,
+                                        0.806908362156733,  0.956846335141266
+                                      }), false);
+    minpackTest(new ChebyquadFunction(9, 9, 1.0,
+                                      0.16994993465202, 0.0,
+                                      new double[] {
+                                        0.0442053461357828, 0.199490672309881,
+                                        0.23561910847106,   0.416046907892598,
+                                        0.5,                0.583953092107402,
+                                        0.764380891528940,  0.800509327690119,
+                                        0.955794653864217
+                                      }), false);
+    minpackTest(new ChebyquadFunction(10, 10, 1.0,
+                                      0.183747831178711, 0.0806471004038253,
+                                      new double[] {
+                                        0.0596202671753563, 0.166708783805937,
+                                        0.239171018813509,  0.398885290346268,
+                                        0.398883667870681,  0.601116332129320,
+                                        0.60111470965373,   0.760828981186491,
+                                        0.833291216194063,  0.940379732824644
+                                      }), false);
+  }
+
+  public void testMinpackBrownAlmostLinear() {
+    minpackTest(new BrownAlmostLinearFunction(10, 0.5,
+                                              16.5302162063499, 0.0,
+                                              new double[] {
+                                                0.979430303349862, 0.979430303349862,
+                                                0.979430303349862, 0.979430303349862,
+                                                0.979430303349862, 0.979430303349862,
+                                                0.979430303349862, 0.979430303349862,
+                                                0.979430303349862, 1.20569696650138
+                                              }), false);
+    minpackTest(new BrownAlmostLinearFunction(10, 5.0,
+                                              9765624.00089211, 0.0,
+                                              new double[] {
+                                               0.979430303349865, 0.979430303349865,
+                                               0.979430303349865, 0.979430303349865,
+                                               0.979430303349865, 0.979430303349865,
+                                               0.979430303349865, 0.979430303349865,
+                                               0.979430303349865, 1.20569696650135
+                                              }), false);
+    minpackTest(new BrownAlmostLinearFunction(10, 50.0,
+                                              0.9765625e17, 0.0,
+                                              new double[] {
+                                                1.0, 1.0, 1.0, 1.0, 1.0,
+                                                1.0, 1.0, 1.0, 1.0, 1.0
+                                              }), false);
+    minpackTest(new BrownAlmostLinearFunction(30, 0.5,
+                                              83.476044467848, 0.0,
+                                              new double[] {
+                                                0.997754216442807, 0.997754216442807,
+                                                0.997754216442807, 0.997754216442807,
+                                                0.997754216442807, 0.997754216442807,
+                                                0.997754216442807, 0.997754216442807,
+                                                0.997754216442807, 0.997754216442807,
+                                                0.997754216442807, 0.997754216442807,
+                                                0.997754216442807, 0.997754216442807,
+                                                0.997754216442807, 0.997754216442807,
+                                                0.997754216442807, 0.997754216442807,
+                                                0.997754216442807, 0.997754216442807,
+                                                0.997754216442807, 0.997754216442807,
+                                                0.997754216442807, 0.997754216442807,
+                                                0.997754216442807, 0.997754216442807,
+                                                0.997754216442807, 0.997754216442807,
+                                                0.997754216442807, 1.06737350671578
+                                              }), false);
+    minpackTest(new BrownAlmostLinearFunction(40, 0.5,
+                                              128.026364472323, 0.0,
+                                              new double[] {
+                                                1.00000000000002, 1.00000000000002,
+                                                1.00000000000002, 1.00000000000002,
+                                                1.00000000000002, 1.00000000000002,
+                                                1.00000000000002, 1.00000000000002,
+                                                1.00000000000002, 1.00000000000002,
+                                                1.00000000000002, 1.00000000000002,
+                                                1.00000000000002, 1.00000000000002,
+                                                1.00000000000002, 1.00000000000002,
+                                                1.00000000000002, 1.00000000000002,
+                                                1.00000000000002, 1.00000000000002,
+                                                1.00000000000002, 1.00000000000002,
+                                                1.00000000000002, 1.00000000000002,
+                                                1.00000000000002, 1.00000000000002,
+                                                1.00000000000002, 1.00000000000002,
+                                                1.00000000000002, 1.00000000000002,
+                                                1.00000000000002, 1.00000000000002,
+                                                1.00000000000002, 1.00000000000002,
+                                                0.999999999999121
+                                              }), false);
+    }
+
+  public void testMinpackOsborne1() {
+      minpackTest(new Osborne1Function(new double[] { 0.5, 1.5, -1.0, 0.01, 0.02, },
+                                       0.937564021037838, 0.00739249260904843,
+                                       new double[] {
+                                         0.375410049244025, 1.93584654543108,
+                                        -1.46468676748716, 0.0128675339110439,
+                                         0.0221227011813076
+                                       }), false);
+    }
+
+  public void testMinpackOsborne2() {
+
+    minpackTest(new Osborne2Function(new double[] {
+                                       1.3, 0.65, 0.65, 0.7, 0.6,
+                                       3.0, 5.0, 7.0, 2.0, 4.5, 5.5
+                                     },
+                                     1.44686540984712, 0.20034404483314,
+                                     new double[] {
+                                       1.30997663810096,  0.43155248076,
+                                       0.633661261602859, 0.599428560991695,
+                                       0.754179768272449, 0.904300082378518,
+                                       1.36579949521007, 4.82373199748107,
+                                       2.39868475104871, 4.56887554791452,
+                                       5.67534206273052
+                                     }), false);
+  }
+
+  private void minpackTest(MinpackFunction function, boolean exceptionExpected) {
+      LevenbergMarquardtOptimizer optimizer = new LevenbergMarquardtOptimizer();
+      optimizer.setMaxIterations(100 * (function.getN() + 1));
+      optimizer.setCostRelativeTolerance(FastMath.sqrt(2.22044604926e-16));
+      optimizer.setParRelativeTolerance(FastMath.sqrt(2.22044604926e-16));
+      optimizer.setOrthoTolerance(2.22044604926e-16);
+//      assertTrue(function.checkTheoreticalStartCost(optimizer.getRMS()));
+      try {
+          VectorialPointValuePair optimum =
+              optimizer.optimize(function,
+                                 function.getTarget(), function.getWeight(),
+                                 function.getStartPoint());
+          assertFalse(exceptionExpected);
+          function.checkTheoreticalMinCost(optimizer.getRMS());
+          function.checkTheoreticalMinParams(optimum);
+      } catch (OptimizationException lsse) {
+          assertTrue(exceptionExpected);
+      } catch (FunctionEvaluationException fe) {
+          assertTrue(exceptionExpected);
+      }
+  }
+
+  private static abstract class MinpackFunction
+      implements DifferentiableMultivariateVectorialFunction, Serializable {
+
+      private static final long serialVersionUID = -6209760235478794233L;
+      protected int      n;
+      protected int      m;
+      protected double[] startParams;
+      protected double   theoreticalMinCost;
+      protected double[] theoreticalMinParams;
+      protected double   costAccuracy;
+      protected double   paramsAccuracy;
+
+      protected MinpackFunction(int m, double[] startParams,
+                                double theoreticalMinCost, double[] theoreticalMinParams) {
+          this.m = m;
+          this.n = startParams.length;
+          this.startParams          = startParams.clone();
+          this.theoreticalMinCost   = theoreticalMinCost;
+          this.theoreticalMinParams = theoreticalMinParams;
+          this.costAccuracy         = 1.0e-8;
+          this.paramsAccuracy       = 1.0e-5;
+      }
+
+      protected static double[] buildArray(int n, double x) {
+          double[] array = new double[n];
+          Arrays.fill(array, x);
+          return array;
+      }
+
+      public double[] getTarget() {
+          return buildArray(m, 0.0);
+      }
+
+      public double[] getWeight() {
+          return buildArray(m, 1.0);
+      }
+
+      public double[] getStartPoint() {
+          return startParams.clone();
+      }
+
+      protected void setCostAccuracy(double costAccuracy) {
+          this.costAccuracy = costAccuracy;
+      }
+
+      protected void setParamsAccuracy(double paramsAccuracy) {
+          this.paramsAccuracy = paramsAccuracy;
+      }
+
+      public int getN() {
+          return startParams.length;
+      }
+
+      public void checkTheoreticalMinCost(double rms) {
+          double threshold = costAccuracy * (1.0 + theoreticalMinCost);
+          assertEquals(theoreticalMinCost, FastMath.sqrt(m) * rms, threshold);
+      }
+
+      public void checkTheoreticalMinParams(VectorialPointValuePair optimum) {
+          double[] params = optimum.getPointRef();
+          if (theoreticalMinParams != null) {
+              for (int i = 0; i < theoreticalMinParams.length; ++i) {
+                  double mi = theoreticalMinParams[i];
+                  double vi = params[i];
+                  assertEquals(mi, vi, paramsAccuracy * (1.0 + FastMath.abs(mi)));
+              }
+          }
+      }
+
+      public MultivariateMatrixFunction jacobian() {
+          return new MultivariateMatrixFunction() {
+            private static final long serialVersionUID = -2435076097232923678L;
+            public double[][] value(double[] point) {
+                  return jacobian(point);
+              }
+          };
+      }
+
+      public abstract double[][] jacobian(double[] variables);
+
+      public abstract double[] value(double[] variables);
+
+  }
+
+  private static class LinearFullRankFunction extends MinpackFunction {
+
+    private static final long serialVersionUID = -9030323226268039536L;
+
+    public LinearFullRankFunction(int m, int n, double x0,
+                                  double theoreticalStartCost,
+                                  double theoreticalMinCost) {
+      super(m, buildArray(n, x0), theoreticalMinCost,
+            buildArray(n, -1.0));
+    }
+
+    @Override
+    public double[][] jacobian(double[] variables) {
+      double t = 2.0 / m;
+      double[][] jacobian = new double[m][];
+      for (int i = 0; i < m; ++i) {
+        jacobian[i] = new double[n];
+        for (int j = 0; j < n; ++j) {
+          jacobian[i][j] = (i == j) ? (1 - t) : -t;
+        }
+      }
+      return jacobian;
+    }
+
+    @Override
+    public double[] value(double[] variables) {
+      double sum = 0;
+      for (int i = 0; i < n; ++i) {
+        sum += variables[i];
+      }
+      double t  = 1 + 2 * sum / m;
+      double[] f = new double[m];
+      for (int i = 0; i < n; ++i) {
+        f[i] = variables[i] - t;
+      }
+      Arrays.fill(f, n, m, -t);
+      return f;
+    }
+
+  }
+
+  private static class LinearRank1Function extends MinpackFunction {
+
+    private static final long serialVersionUID = 8494863245104608300L;
+
+    public LinearRank1Function(int m, int n, double x0,
+                                  double theoreticalStartCost,
+                                  double theoreticalMinCost) {
+      super(m, buildArray(n, x0), theoreticalMinCost, null);
+    }
+
+    @Override
+    public double[][] jacobian(double[] variables) {
+      double[][] jacobian = new double[m][];
+      for (int i = 0; i < m; ++i) {
+        jacobian[i] = new double[n];
+        for (int j = 0; j < n; ++j) {
+          jacobian[i][j] = (i + 1) * (j + 1);
+        }
+      }
+      return jacobian;
+    }
+
+    @Override
+    public double[] value(double[] variables) {
+      double[] f = new double[m];
+      double sum = 0;
+      for (int i = 0; i < n; ++i) {
+        sum += (i + 1) * variables[i];
+      }
+      for (int i = 0; i < m; ++i) {
+        f[i] = (i + 1) * sum - 1;
+      }
+      return f;
+    }
+
+  }
+
+  private static class LinearRank1ZeroColsAndRowsFunction extends MinpackFunction {
+
+    private static final long serialVersionUID = -3316653043091995018L;
+
+    public LinearRank1ZeroColsAndRowsFunction(int m, int n, double x0) {
+      super(m, buildArray(n, x0),
+            FastMath.sqrt((m * (m + 3) - 6) / (2.0 * (2 * m - 3))),
+            null);
+    }
+
+    @Override
+    public double[][] jacobian(double[] variables) {
+      double[][] jacobian = new double[m][];
+      for (int i = 0; i < m; ++i) {
+        jacobian[i] = new double[n];
+        jacobian[i][0] = 0;
+        for (int j = 1; j < (n - 1); ++j) {
+          if (i == 0) {
+            jacobian[i][j] = 0;
+          } else if (i != (m - 1)) {
+            jacobian[i][j] = i * (j + 1);
+          } else {
+            jacobian[i][j] = 0;
+          }
+        }
+        jacobian[i][n - 1] = 0;
+      }
+      return jacobian;
+    }
+
+    @Override
+    public double[] value(double[] variables) {
+      double[] f = new double[m];
+      double sum = 0;
+      for (int i = 1; i < (n - 1); ++i) {
+        sum += (i + 1) * variables[i];
+      }
+      for (int i = 0; i < (m - 1); ++i) {
+        f[i] = i * sum - 1;
+      }
+      f[m - 1] = -1;
+      return f;
+    }
+
+  }
+
+  private static class RosenbrockFunction extends MinpackFunction {
+
+    private static final long serialVersionUID = 2893438180956569134L;
+
+    public RosenbrockFunction(double[] startParams, double theoreticalStartCost) {
+      super(2, startParams, 0.0, buildArray(2, 1.0));
+    }
+
+    @Override
+    public double[][] jacobian(double[] variables) {
+      double x1 = variables[0];
+      return new double[][] { { -20 * x1, 10 }, { -1, 0 } };
+    }
+
+    @Override
+    public double[] value(double[] variables) {
+      double x1 = variables[0];
+      double x2 = variables[1];
+      return new double[] { 10 * (x2 - x1 * x1), 1 - x1 };
+    }
+
+  }
+
+  private static class HelicalValleyFunction extends MinpackFunction {
+
+    private static final long serialVersionUID = 220613787843200102L;
+
+    public HelicalValleyFunction(double[] startParams,
+                                 double theoreticalStartCost) {
+      super(3, startParams, 0.0, new double[] { 1.0, 0.0, 0.0 });
+    }
+
+    @Override
+    public double[][] jacobian(double[] variables) {
+      double x1 = variables[0];
+      double x2 = variables[1];
+      double tmpSquare = x1 * x1 + x2 * x2;
+      double tmp1 = twoPi * tmpSquare;
+      double tmp2 = FastMath.sqrt(tmpSquare);
+      return new double[][] {
+        {  100 * x2 / tmp1, -100 * x1 / tmp1, 10 },
+        { 10 * x1 / tmp2, 10 * x2 / tmp2, 0 },
+        { 0, 0, 1 }
+      };
+    }
+
+    @Override
+    public double[] value(double[] variables) {
+      double x1 = variables[0];
+      double x2 = variables[1];
+      double x3 = variables[2];
+      double tmp1;
+      if (x1 == 0) {
+        tmp1 = (x2 >= 0) ? 0.25 : -0.25;
+      } else {
+        tmp1 = FastMath.atan(x2 / x1) / twoPi;
+        if (x1 < 0) {
+          tmp1 += 0.5;
+        }
+      }
+      double tmp2 = FastMath.sqrt(x1 * x1 + x2 * x2);
+      return new double[] {
+        10.0 * (x3 - 10 * tmp1),
+        10.0 * (tmp2 - 1),
+        x3
+      };
+    }
+
+    private static final double twoPi = 2.0 * FastMath.PI;
+
+  }
+
+  private static class PowellSingularFunction extends MinpackFunction {
+
+    private static final long serialVersionUID = 7298364171208142405L;
+
+    public PowellSingularFunction(double[] startParams,
+                                  double theoreticalStartCost) {
+      super(4, startParams, 0.0, buildArray(4, 0.0));
+    }
+
+    @Override
+    public double[][] jacobian(double[] variables) {
+      double x1 = variables[0];
+      double x2 = variables[1];
+      double x3 = variables[2];
+      double x4 = variables[3];
+      return new double[][] {
+        { 1, 10, 0, 0 },
+        { 0, 0, sqrt5, -sqrt5 },
+        { 0, 2 * (x2 - 2 * x3), -4 * (x2 - 2 * x3), 0 },
+        { 2 * sqrt10 * (x1 - x4), 0, 0, -2 * sqrt10 * (x1 - x4) }
+      };
+    }
+
+    @Override
+    public double[] value(double[] variables) {
+      double x1 = variables[0];
+      double x2 = variables[1];
+      double x3 = variables[2];
+      double x4 = variables[3];
+      return new double[] {
+        x1 + 10 * x2,
+        sqrt5 * (x3 - x4),
+        (x2 - 2 * x3) * (x2 - 2 * x3),
+        sqrt10 * (x1 - x4) * (x1 - x4)
+      };
+    }
+
+    private static final double sqrt5  = FastMath.sqrt( 5.0);
+    private static final double sqrt10 = FastMath.sqrt(10.0);
+
+  }
+
+  private static class FreudensteinRothFunction extends MinpackFunction {
+
+    private static final long serialVersionUID = 2892404999344244214L;
+
+    public FreudensteinRothFunction(double[] startParams,
+                                    double theoreticalStartCost,
+                                    double theoreticalMinCost,
+                                    double[] theoreticalMinParams) {
+      super(2, startParams, theoreticalMinCost,
+            theoreticalMinParams);
+    }
+
+    @Override
+    public double[][] jacobian(double[] variables) {
+      double x2 = variables[1];
+      return new double[][] {
+        { 1, x2 * (10 - 3 * x2) -  2 },
+        { 1, x2 * ( 2 + 3 * x2) - 14, }
+      };
+    }
+
+    @Override
+    public double[] value(double[] variables) {
+      double x1 = variables[0];
+      double x2 = variables[1];
+      return new double[] {
+       -13.0 + x1 + ((5.0 - x2) * x2 -  2.0) * x2,
+       -29.0 + x1 + ((1.0 + x2) * x2 - 14.0) * x2
+      };
+    }
+
+  }
+
+  private static class BardFunction extends MinpackFunction {
+
+    private static final long serialVersionUID = 5990442612572087668L;
+
+    public BardFunction(double x0,
+                        double theoreticalStartCost,
+                        double theoreticalMinCost,
+                        double[] theoreticalMinParams) {
+      super(15, buildArray(3, x0), theoreticalMinCost,
+            theoreticalMinParams);
+    }
+
+    @Override
+    public double[][] jacobian(double[] variables) {
+      double   x2 = variables[1];
+      double   x3 = variables[2];
+      double[][] jacobian = new double[m][];
+      for (int i = 0; i < m; ++i) {
+        double tmp1 = i  + 1;
+        double tmp2 = 15 - i;
+        double tmp3 = (i <= 7) ? tmp1 : tmp2;
+        double tmp4 = x2 * tmp2 + x3 * tmp3;
+        tmp4 *= tmp4;
+        jacobian[i] = new double[] { -1, tmp1 * tmp2 / tmp4, tmp1 * tmp3 / tmp4 };
+      }
+      return jacobian;
+    }
+
+    @Override
+    public double[] value(double[] variables) {
+      double   x1 = variables[0];
+      double   x2 = variables[1];
+      double   x3 = variables[2];
+      double[] f = new double[m];
+      for (int i = 0; i < m; ++i) {
+        double tmp1 = i + 1;
+        double tmp2 = 15 - i;
+        double tmp3 = (i <= 7) ? tmp1 : tmp2;
+        f[i] = y[i] - (x1 + tmp1 / (x2 * tmp2 + x3 * tmp3));
+      }
+      return f;
+    }
+
+    private static final double[] y = {
+      0.14, 0.18, 0.22, 0.25, 0.29,
+      0.32, 0.35, 0.39, 0.37, 0.58,
+      0.73, 0.96, 1.34, 2.10, 4.39
+    };
+
+  }
+
+  private static class KowalikOsborneFunction extends MinpackFunction {
+
+    private static final long serialVersionUID = -4867445739880495801L;
+
+    public KowalikOsborneFunction(double[] startParams,
+                                  double theoreticalStartCost,
+                                  double theoreticalMinCost,
+                                  double[] theoreticalMinParams) {
+      super(11, startParams, theoreticalMinCost,
+            theoreticalMinParams);
+      if (theoreticalStartCost > 20.0) {
+        setCostAccuracy(2.0e-4);
+        setParamsAccuracy(5.0e-3);
+      }
+    }
+
+    @Override
+    public double[][] jacobian(double[] variables) {
+      double   x1 = variables[0];
+      double   x2 = variables[1];
+      double   x3 = variables[2];
+      double   x4 = variables[3];
+      double[][] jacobian = new double[m][];
+      for (int i = 0; i < m; ++i) {
+        double tmp = v[i] * (v[i] + x3) + x4;
+        double j1  = -v[i] * (v[i] + x2) / tmp;
+        double j2  = -v[i] * x1 / tmp;
+        double j3  = j1 * j2;
+        double j4  = j3 / v[i];
+        jacobian[i] = new double[] { j1, j2, j3, j4 };
+      }
+      return jacobian;
+    }
+
+    @Override
+    public double[] value(double[] variables) {
+      double x1 = variables[0];
+      double x2 = variables[1];
+      double x3 = variables[2];
+      double x4 = variables[3];
+      double[] f = new double[m];
+      for (int i = 0; i < m; ++i) {
+        f[i] = y[i] - x1 * (v[i] * (v[i] + x2)) / (v[i] * (v[i] + x3) + x4);
+      }
+      return f;
+    }
+
+    private static final double[] v = {
+      4.0, 2.0, 1.0, 0.5, 0.25, 0.167, 0.125, 0.1, 0.0833, 0.0714, 0.0625
+    };
+
+    private static final double[] y = {
+      0.1957, 0.1947, 0.1735, 0.1600, 0.0844, 0.0627,
+      0.0456, 0.0342, 0.0323, 0.0235, 0.0246
+    };
+
+  }
+
+  private static class MeyerFunction extends MinpackFunction {
+
+    private static final long serialVersionUID = -838060619150131027L;
+
+    public MeyerFunction(double[] startParams,
+                         double theoreticalStartCost,
+                         double theoreticalMinCost,
+                         double[] theoreticalMinParams) {
+      super(16, startParams, theoreticalMinCost,
+            theoreticalMinParams);
+      if (theoreticalStartCost > 1.0e6) {
+        setCostAccuracy(7.0e-3);
+        setParamsAccuracy(2.0e-2);
+      }
+    }
+
+    @Override
+    public double[][] jacobian(double[] variables) {
+      double   x1 = variables[0];
+      double   x2 = variables[1];
+      double   x3 = variables[2];
+      double[][] jacobian = new double[m][];
+      for (int i = 0; i < m; ++i) {
+        double temp = 5.0 * (i + 1) + 45.0 + x3;
+        double tmp1 = x2 / temp;
+        double tmp2 = FastMath.exp(tmp1);
+        double tmp3 = x1 * tmp2 / temp;
+        jacobian[i] = new double[] { tmp2, tmp3, -tmp1 * tmp3 };
+      }
+      return jacobian;
+    }
+
+    @Override
+    public double[] value(double[] variables) {
+      double x1 = variables[0];
+      double x2 = variables[1];
+      double x3 = variables[2];
+      double[] f = new double[m];
+      for (int i = 0; i < m; ++i) {
+        f[i] = x1 * FastMath.exp(x2 / (5.0 * (i + 1) + 45.0 + x3)) - y[i];
+      }
+     return f;
+    }
+
+    private static final double[] y = {
+      34780.0, 28610.0, 23650.0, 19630.0,
+      16370.0, 13720.0, 11540.0,  9744.0,
+       8261.0,  7030.0,  6005.0,  5147.0,
+       4427.0,  3820.0,  3307.0,  2872.0
+    };
+
+  }
+
+  private static class WatsonFunction extends MinpackFunction {
+
+    private static final long serialVersionUID = -9034759294980218927L;
+
+    public WatsonFunction(int n, double x0,
+                          double theoreticalStartCost,
+                          double theoreticalMinCost,
+                          double[] theoreticalMinParams) {
+      super(31, buildArray(n, x0), theoreticalMinCost,
+            theoreticalMinParams);
+    }
+
+    @Override
+    public double[][] jacobian(double[] variables) {
+
+      double[][] jacobian = new double[m][];
+
+      for (int i = 0; i < (m - 2); ++i) {
+        double div = (i + 1) / 29.0;
+        double s2  = 0.0;
+        double dx  = 1.0;
+        for (int j = 0; j < n; ++j) {
+          s2 += dx * variables[j];
+          dx *= div;
+        }
+        double temp= 2 * div * s2;
+        dx = 1.0 / div;
+        jacobian[i] = new double[n];
+        for (int j = 0; j < n; ++j) {
+          jacobian[i][j] = dx * (j - temp);
+          dx *= div;
+        }
+      }
+
+      jacobian[m - 2]    = new double[n];
+      jacobian[m - 2][0] = 1;
+
+      jacobian[m - 1]   = new double[n];
+      jacobian[m - 1][0]= -2 * variables[0];
+      jacobian[m - 1][1]= 1;
+
+      return jacobian;
+
+    }
+
+    @Override
+    public double[] value(double[] variables) {
+     double[] f = new double[m];
+     for (int i = 0; i < (m - 2); ++i) {
+       double div = (i + 1) / 29.0;
+       double s1 = 0;
+       double dx = 1;
+       for (int j = 1; j < n; ++j) {
+         s1 += j * dx * variables[j];
+         dx *= div;
+       }
+       double s2 =0;
+       dx =1;
+       for (int j = 0; j < n; ++j) {
+         s2 += dx * variables[j];
+         dx *= div;
+       }
+       f[i] = s1 - s2 * s2 - 1;
+     }
+
+     double x1 = variables[0];
+     double x2 = variables[1];
+     f[m - 2] = x1;
+     f[m - 1] = x2 - x1 * x1 - 1;
+
+     return f;
+
+    }
+
+  }
+
+  private static class Box3DimensionalFunction extends MinpackFunction {
+
+    private static final long serialVersionUID = 5511403858142574493L;
+
+    public Box3DimensionalFunction(int m, double[] startParams,
+                                   double theoreticalStartCost) {
+      super(m, startParams, 0.0,
+            new double[] { 1.0, 10.0, 1.0 });
+   }
+
+    @Override
+    public double[][] jacobian(double[] variables) {
+      double   x1 = variables[0];
+      double   x2 = variables[1];
+      double[][] jacobian = new double[m][];
+      for (int i = 0; i < m; ++i) {
+        double tmp = (i + 1) / 10.0;
+        jacobian[i] = new double[] {
+          -tmp * FastMath.exp(-tmp * x1),
+           tmp * FastMath.exp(-tmp * x2),
+          FastMath.exp(-i - 1) - FastMath.exp(-tmp)
+        };
+      }
+      return jacobian;
+    }
+
+    @Override
+    public double[] value(double[] variables) {
+      double x1 = variables[0];
+      double x2 = variables[1];
+      double x3 = variables[2];
+      double[] f = new double[m];
+      for (int i = 0; i < m; ++i) {
+        double tmp = (i + 1) / 10.0;
+        f[i] = FastMath.exp(-tmp * x1) - FastMath.exp(-tmp * x2)
+             + (FastMath.exp(-i - 1) - FastMath.exp(-tmp)) * x3;
+      }
+      return f;
+    }
+
+  }
+
+  private static class JennrichSampsonFunction extends MinpackFunction {
+
+    private static final long serialVersionUID = -2489165190443352947L;
+
+    public JennrichSampsonFunction(int m, double[] startParams,
+                                   double theoreticalStartCost,
+                                   double theoreticalMinCost,
+                                   double[] theoreticalMinParams) {
+      super(m, startParams, theoreticalMinCost,
+            theoreticalMinParams);
+    }
+
+    @Override
+    public double[][] jacobian(double[] variables) {
+      double   x1 = variables[0];
+      double   x2 = variables[1];
+      double[][] jacobian = new double[m][];
+      for (int i = 0; i < m; ++i) {
+        double t = i + 1;
+        jacobian[i] = new double[] { -t * FastMath.exp(t * x1), -t * FastMath.exp(t * x2) };
+      }
+      return jacobian;
+    }
+
+    @Override
+    public double[] value(double[] variables) {
+      double x1 = variables[0];
+      double x2 = variables[1];
+      double[] f = new double[m];
+      for (int i = 0; i < m; ++i) {
+        double temp = i + 1;
+        f[i] = 2 + 2 * temp - FastMath.exp(temp * x1) - FastMath.exp(temp * x2);
+      }
+      return f;
+    }
+
+  }
+
+  private static class BrownDennisFunction extends MinpackFunction {
+
+    private static final long serialVersionUID = 8340018645694243910L;
+
+    public BrownDennisFunction(int m, double[] startParams,
+                               double theoreticalStartCost,
+                               double theoreticalMinCost,
+                               double[] theoreticalMinParams) {
+      super(m, startParams, theoreticalMinCost,
+            theoreticalMinParams);
+      setCostAccuracy(2.5e-8);
+    }
+
+    @Override
+    public double[][] jacobian(double[] variables) {
+      double   x1 = variables[0];
+      double   x2 = variables[1];
+      double   x3 = variables[2];
+      double   x4 = variables[3];
+      double[][] jacobian = new double[m][];
+      for (int i = 0; i < m; ++i) {
+        double temp = (i + 1) / 5.0;
+        double ti   = FastMath.sin(temp);
+        double tmp1 = x1 + temp * x2 - FastMath.exp(temp);
+        double tmp2 = x3 + ti   * x4 - FastMath.cos(temp);
+        jacobian[i] = new double[] {
+          2 * tmp1, 2 * temp * tmp1, 2 * tmp2, 2 * ti * tmp2
+        };
+      }
+      return jacobian;
+    }
+
+    @Override
+    public double[] value(double[] variables) {
+      double x1 = variables[0];
+      double x2 = variables[1];
+      double x3 = variables[2];
+      double x4 = variables[3];
+      double[] f = new double[m];
+      for (int i = 0; i < m; ++i) {
+        double temp = (i + 1) / 5.0;
+        double tmp1 = x1 + temp * x2 - FastMath.exp(temp);
+        double tmp2 = x3 + FastMath.sin(temp) * x4 - FastMath.cos(temp);
+        f[i] = tmp1 * tmp1 + tmp2 * tmp2;
+      }
+      return f;
+    }
+
+  }
+
+  private static class ChebyquadFunction extends MinpackFunction {
+
+    private static final long serialVersionUID = -2394877275028008594L;
+
+    private static double[] buildChebyquadArray(int n, double factor) {
+      double[] array = new double[n];
+      double inv = factor / (n + 1);
+      for (int i = 0; i < n; ++i) {
+        array[i] = (i + 1) * inv;
+      }
+      return array;
+    }
+
+    public ChebyquadFunction(int n, int m, double factor,
+                             double theoreticalStartCost,
+                             double theoreticalMinCost,
+                             double[] theoreticalMinParams) {
+      super(m, buildChebyquadArray(n, factor), theoreticalMinCost,
+            theoreticalMinParams);
+    }
+
+    @Override
+    public double[][] jacobian(double[] variables) {
+
+      double[][] jacobian = new double[m][];
+      for (int i = 0; i < m; ++i) {
+        jacobian[i] = new double[n];
+      }
+
+      double dx = 1.0 / n;
+      for (int j = 0; j < n; ++j) {
+        double tmp1 = 1;
+        double tmp2 = 2 * variables[j] - 1;
+        double temp = 2 * tmp2;
+        double tmp3 = 0;
+        double tmp4 = 2;
+        for (int i = 0; i < m; ++i) {
+          jacobian[i][j] = dx * tmp4;
+          double ti = 4 * tmp2 + temp * tmp4 - tmp3;
+          tmp3 = tmp4;
+          tmp4 = ti;
+          ti   = temp * tmp2 - tmp1;
+          tmp1 = tmp2;
+          tmp2 = ti;
+        }
+      }
+
+      return jacobian;
+
+    }
+
+    @Override
+    public double[] value(double[] variables) {
+
+      double[] f = new double[m];
+
+      for (int j = 0; j < n; ++j) {
+        double tmp1 = 1;
+        double tmp2 = 2 * variables[j] - 1;
+        double temp = 2 * tmp2;
+        for (int i = 0; i < m; ++i) {
+          f[i] += tmp2;
+          double ti = temp * tmp2 - tmp1;
+          tmp1 = tmp2;
+          tmp2 = ti;
+        }
+      }
+
+      double dx = 1.0 / n;
+      boolean iev = false;
+      for (int i = 0; i < m; ++i) {
+        f[i] *= dx;
+        if (iev) {
+          f[i] += 1.0 / (i * (i + 2));
+        }
+        iev = ! iev;
+      }
+
+      return f;
+
+    }
+
+  }
+
+  private static class BrownAlmostLinearFunction extends MinpackFunction {
+
+    private static final long serialVersionUID = 8239594490466964725L;
+
+    public BrownAlmostLinearFunction(int m, double factor,
+                                     double theoreticalStartCost,
+                                     double theoreticalMinCost,
+                                     double[] theoreticalMinParams) {
+      super(m, buildArray(m, factor), theoreticalMinCost,
+            theoreticalMinParams);
+    }
+
+    @Override
+    public double[][] jacobian(double[] variables) {
+      double[][] jacobian = new double[m][];
+      for (int i = 0; i < m; ++i) {
+        jacobian[i] = new double[n];
+      }
+
+      double prod = 1;
+      for (int j = 0; j < n; ++j) {
+        prod *= variables[j];
+        for (int i = 0; i < n; ++i) {
+          jacobian[i][j] = 1;
+        }
+        jacobian[j][j] = 2;
+      }
+
+      for (int j = 0; j < n; ++j) {
+        double temp = variables[j];
+        if (temp == 0) {
+          temp = 1;
+          prod = 1;
+          for (int k = 0; k < n; ++k) {
+            if (k != j) {
+              prod *= variables[k];
+            }
+          }
+        }
+        jacobian[n - 1][j] = prod / temp;
+      }
+
+      return jacobian;
+
+    }
+
+    @Override
+    public double[] value(double[] variables) {
+      double[] f = new double[m];
+      double sum  = -(n + 1);
+      double prod = 1;
+      for (int j = 0; j < n; ++j) {
+        sum  += variables[j];
+        prod *= variables[j];
+      }
+      for (int i = 0; i < n; ++i) {
+        f[i] = variables[i] + sum;
+      }
+      f[n - 1] = prod - 1;
+      return f;
+    }
+
+  }
+
+  private static class Osborne1Function extends MinpackFunction {
+
+    private static final long serialVersionUID = 4006743521149849494L;
+
+    public Osborne1Function(double[] startParams,
+                            double theoreticalStartCost,
+                            double theoreticalMinCost,
+                            double[] theoreticalMinParams) {
+      super(33, startParams, theoreticalMinCost,
+            theoreticalMinParams);
+    }
+
+    @Override
+    public double[][] jacobian(double[] variables) {
+      double   x2 = variables[1];
+      double   x3 = variables[2];
+      double   x4 = variables[3];
+      double   x5 = variables[4];
+      double[][] jacobian = new double[m][];
+      for (int i = 0; i < m; ++i) {
+        double temp = 10.0 * i;
+        double tmp1 = FastMath.exp(-temp * x4);
+        double tmp2 = FastMath.exp(-temp * x5);
+        jacobian[i] = new double[] {
+          -1, -tmp1, -tmp2, temp * x2 * tmp1, temp * x3 * tmp2
+        };
+      }
+      return jacobian;
+    }
+
+    @Override
+    public double[] value(double[] variables) {
+      double x1 = variables[0];
+      double x2 = variables[1];
+      double x3 = variables[2];
+      double x4 = variables[3];
+      double x5 = variables[4];
+      double[] f = new double[m];
+      for (int i = 0; i < m; ++i) {
+        double temp = 10.0 * i;
+        double tmp1 = FastMath.exp(-temp * x4);
+        double tmp2 = FastMath.exp(-temp * x5);
+        f[i] = y[i] - (x1 + x2 * tmp1 + x3 * tmp2);
+      }
+      return f;
+    }
+
+    private static final double[] y = {
+      0.844, 0.908, 0.932, 0.936, 0.925, 0.908, 0.881, 0.850, 0.818, 0.784, 0.751,
+      0.718, 0.685, 0.658, 0.628, 0.603, 0.580, 0.558, 0.538, 0.522, 0.506, 0.490,
+      0.478, 0.467, 0.457, 0.448, 0.438, 0.431, 0.424, 0.420, 0.414, 0.411, 0.406
+    };
+
+  }
+
+  private static class Osborne2Function extends MinpackFunction {
+
+    private static final long serialVersionUID = -8418268780389858746L;
+
+    public Osborne2Function(double[] startParams,
+                            double theoreticalStartCost,
+                            double theoreticalMinCost,
+                            double[] theoreticalMinParams) {
+      super(65, startParams, theoreticalMinCost,
+            theoreticalMinParams);
+    }
+
+    @Override
+    public double[][] jacobian(double[] variables) {
+      double   x01 = variables[0];
+      double   x02 = variables[1];
+      double   x03 = variables[2];
+      double   x04 = variables[3];
+      double   x05 = variables[4];
+      double   x06 = variables[5];
+      double   x07 = variables[6];
+      double   x08 = variables[7];
+      double   x09 = variables[8];
+      double   x10 = variables[9];
+      double   x11 = variables[10];
+      double[][] jacobian = new double[m][];
+      for (int i = 0; i < m; ++i) {
+        double temp = i / 10.0;
+        double tmp1 = FastMath.exp(-x05 * temp);
+        double tmp2 = FastMath.exp(-x06 * (temp - x09) * (temp - x09));
+        double tmp3 = FastMath.exp(-x07 * (temp - x10) * (temp - x10));
+        double tmp4 = FastMath.exp(-x08 * (temp - x11) * (temp - x11));
+        jacobian[i] = new double[] {
+          -tmp1,
+          -tmp2,
+          -tmp3,
+          -tmp4,
+          temp * x01 * tmp1,
+          x02 * (temp - x09) * (temp - x09) * tmp2,
+          x03 * (temp - x10) * (temp - x10) * tmp3,
+          x04 * (temp - x11) * (temp - x11) * tmp4,
+          -2 * x02 * x06 * (temp - x09) * tmp2,
+          -2 * x03 * x07 * (temp - x10) * tmp3,
+          -2 * x04 * x08 * (temp - x11) * tmp4
+        };
+      }
+      return jacobian;
+    }
+
+    @Override
+    public double[] value(double[] variables) {
+      double x01 = variables[0];
+      double x02 = variables[1];
+      double x03 = variables[2];
+      double x04 = variables[3];
+      double x05 = variables[4];
+      double x06 = variables[5];
+      double x07 = variables[6];
+      double x08 = variables[7];
+      double x09 = variables[8];
+      double x10 = variables[9];
+      double x11 = variables[10];
+      double[] f = new double[m];
+      for (int i = 0; i < m; ++i) {
+        double temp = i / 10.0;
+        double tmp1 = FastMath.exp(-x05 * temp);
+        double tmp2 = FastMath.exp(-x06 * (temp - x09) * (temp - x09));
+        double tmp3 = FastMath.exp(-x07 * (temp - x10) * (temp - x10));
+        double tmp4 = FastMath.exp(-x08 * (temp - x11) * (temp - x11));
+        f[i] = y[i] - (x01 * tmp1 + x02 * tmp2 + x03 * tmp3 + x04 * tmp4);
+      }
+      return f;
+    }
+
+    private static final double[] y = {
+      1.366, 1.191, 1.112, 1.013, 0.991,
+      0.885, 0.831, 0.847, 0.786, 0.725,
+      0.746, 0.679, 0.608, 0.655, 0.616,
+      0.606, 0.602, 0.626, 0.651, 0.724,
+      0.649, 0.649, 0.694, 0.644, 0.624,
+      0.661, 0.612, 0.558, 0.533, 0.495,
+      0.500, 0.423, 0.395, 0.375, 0.372,
+      0.391, 0.396, 0.405, 0.428, 0.429,
+      0.523, 0.562, 0.607, 0.653, 0.672,
+      0.708, 0.633, 0.668, 0.645, 0.632,
+      0.591, 0.559, 0.597, 0.625, 0.739,
+      0.710, 0.729, 0.720, 0.636, 0.581,
+      0.428, 0.292, 0.162, 0.098, 0.054
+    };
+
+  }
+
+}
diff --git a/src/test/java/org/apache/commons/math/optimization/general/NonLinearConjugateGradientOptimizerTest.java b/src/test/java/org/apache/commons/math/optimization/general/NonLinearConjugateGradientOptimizerTest.java
new file mode 100644
index 0000000..cd53293
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/optimization/general/NonLinearConjugateGradientOptimizerTest.java
@@ -0,0 +1,492 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.optimization.general;
+
+import java.awt.geom.Point2D;
+import java.io.Serializable;
+import java.util.ArrayList;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.math.analysis.DifferentiableMultivariateRealFunction;
+import org.apache.commons.math.analysis.MultivariateRealFunction;
+import org.apache.commons.math.analysis.MultivariateVectorialFunction;
+import org.apache.commons.math.analysis.solvers.BrentSolver;
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.linear.BlockRealMatrix;
+import org.apache.commons.math.linear.RealMatrix;
+import org.apache.commons.math.optimization.GoalType;
+import org.apache.commons.math.optimization.RealPointValuePair;
+import org.apache.commons.math.optimization.SimpleScalarValueChecker;
+
+/**
+ * <p>Some of the unit tests are re-implementations of the MINPACK <a
+ * href="http://www.netlib.org/minpack/ex/file17">file17</a> and <a
+ * href="http://www.netlib.org/minpack/ex/file22">file22</a> test files.
+ * 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>
+
+ * @author Argonne National Laboratory. MINPACK project. March 1980 (original fortran minpack tests)
+ * @author Burton S. Garbow (original fortran minpack tests)
+ * @author Kenneth E. Hillstrom (original fortran minpack tests)
+ * @author Jorge J. More (original fortran minpack tests)
+ * @author Luc Maisonobe (non-minpack tests and minpack tests Java translation)
+ */
+public class NonLinearConjugateGradientOptimizerTest
+extends TestCase {
+
+    public NonLinearConjugateGradientOptimizerTest(String name) {
+        super(name);
+    }
+
+    public void testTrivial() throws Exception {
+        LinearProblem problem =
+            new LinearProblem(new double[][] { { 2 } }, new double[] { 3 });
+        NonLinearConjugateGradientOptimizer optimizer =
+            new NonLinearConjugateGradientOptimizer(ConjugateGradientFormula.POLAK_RIBIERE);
+        optimizer.setMaxIterations(100);
+        optimizer.setConvergenceChecker(new SimpleScalarValueChecker(1.0e-6, 1.0e-6));
+        RealPointValuePair optimum =
+            optimizer.optimize(problem, GoalType.MINIMIZE, new double[] { 0 });
+        assertEquals(1.5, optimum.getPoint()[0], 1.0e-10);
+        assertEquals(0.0, optimum.getValue(), 1.0e-10);
+    }
+
+    public void testColumnsPermutation() throws Exception {
+
+        LinearProblem problem =
+            new LinearProblem(new double[][] { { 1.0, -1.0 }, { 0.0, 2.0 }, { 1.0, -2.0 } },
+                              new double[] { 4.0, 6.0, 1.0 });
+
+        NonLinearConjugateGradientOptimizer optimizer =
+            new NonLinearConjugateGradientOptimizer(ConjugateGradientFormula.POLAK_RIBIERE);
+        optimizer.setMaxIterations(100);
+        optimizer.setConvergenceChecker(new SimpleScalarValueChecker(1.0e-6, 1.0e-6));
+        RealPointValuePair optimum =
+            optimizer.optimize(problem, GoalType.MINIMIZE, new double[] { 0, 0 });
+        assertEquals(7.0, optimum.getPoint()[0], 1.0e-10);
+        assertEquals(3.0, optimum.getPoint()[1], 1.0e-10);
+        assertEquals(0.0, optimum.getValue(), 1.0e-10);
+
+    }
+
+    public void testNoDependency() throws Exception {
+        LinearProblem problem = new LinearProblem(new double[][] {
+                { 2, 0, 0, 0, 0, 0 },
+                { 0, 2, 0, 0, 0, 0 },
+                { 0, 0, 2, 0, 0, 0 },
+                { 0, 0, 0, 2, 0, 0 },
+                { 0, 0, 0, 0, 2, 0 },
+                { 0, 0, 0, 0, 0, 2 }
+        }, new double[] { 0.0, 1.1, 2.2, 3.3, 4.4, 5.5 });
+        NonLinearConjugateGradientOptimizer optimizer =
+            new NonLinearConjugateGradientOptimizer(ConjugateGradientFormula.POLAK_RIBIERE);
+        optimizer.setMaxIterations(100);
+        optimizer.setConvergenceChecker(new SimpleScalarValueChecker(1.0e-6, 1.0e-6));
+        RealPointValuePair optimum =
+            optimizer.optimize(problem, GoalType.MINIMIZE, new double[] { 0, 0, 0, 0, 0, 0 });
+        for (int i = 0; i < problem.target.length; ++i) {
+            assertEquals(0.55 * i, optimum.getPoint()[i], 1.0e-10);
+        }
+    }
+
+    public void testOneSet() throws Exception {
+
+        LinearProblem problem = new LinearProblem(new double[][] {
+                {  1,  0, 0 },
+                { -1,  1, 0 },
+                {  0, -1, 1 }
+        }, new double[] { 1, 1, 1});
+        NonLinearConjugateGradientOptimizer optimizer =
+            new NonLinearConjugateGradientOptimizer(ConjugateGradientFormula.POLAK_RIBIERE);
+        optimizer.setMaxIterations(100);
+        optimizer.setConvergenceChecker(new SimpleScalarValueChecker(1.0e-6, 1.0e-6));
+        RealPointValuePair optimum =
+            optimizer.optimize(problem, GoalType.MINIMIZE, new double[] { 0, 0, 0 });
+        assertEquals(1.0, optimum.getPoint()[0], 1.0e-10);
+        assertEquals(2.0, optimum.getPoint()[1], 1.0e-10);
+        assertEquals(3.0, optimum.getPoint()[2], 1.0e-10);
+
+    }
+
+    public void testTwoSets() throws Exception {
+        final double epsilon = 1.0e-7;
+        LinearProblem problem = new LinearProblem(new double[][] {
+                {  2,  1,   0,  4,       0, 0 },
+                { -4, -2,   3, -7,       0, 0 },
+                {  4,  1,  -2,  8,       0, 0 },
+                {  0, -3, -12, -1,       0, 0 },
+                {  0,  0,   0,  0, epsilon, 1 },
+                {  0,  0,   0,  0,       1, 1 }
+        }, new double[] { 2, -9, 2, 2, 1 + epsilon * epsilon, 2});
+
+        NonLinearConjugateGradientOptimizer optimizer =
+            new NonLinearConjugateGradientOptimizer(ConjugateGradientFormula.POLAK_RIBIERE);
+        optimizer.setMaxIterations(100);
+        optimizer.setPreconditioner(new Preconditioner() {
+            public double[] precondition(double[] point, double[] r) {
+                double[] d = r.clone();
+                d[0] /=  72.0;
+                d[1] /=  30.0;
+                d[2] /= 314.0;
+                d[3] /= 260.0;
+                d[4] /= 2 * (1 + epsilon * epsilon);
+                d[5] /= 4.0;
+                return d;
+            }
+        });
+        optimizer.setConvergenceChecker(new SimpleScalarValueChecker(1.0e-13, 1.0e-13));
+
+        RealPointValuePair optimum =
+            optimizer.optimize(problem, GoalType.MINIMIZE, new double[] { 0, 0, 0, 0, 0, 0 });
+        assertEquals( 3.0, optimum.getPoint()[0], 1.0e-10);
+        assertEquals( 4.0, optimum.getPoint()[1], 1.0e-10);
+        assertEquals(-1.0, optimum.getPoint()[2], 1.0e-10);
+        assertEquals(-2.0, optimum.getPoint()[3], 1.0e-10);
+        assertEquals( 1.0 + epsilon, optimum.getPoint()[4], 1.0e-10);
+        assertEquals( 1.0 - epsilon, optimum.getPoint()[5], 1.0e-10);
+
+    }
+
+    public void testNonInversible() throws Exception {
+
+        LinearProblem problem = new LinearProblem(new double[][] {
+                {  1, 2, -3 },
+                {  2, 1,  3 },
+                { -3, 0, -9 }
+        }, new double[] { 1, 1, 1 });
+        NonLinearConjugateGradientOptimizer optimizer =
+            new NonLinearConjugateGradientOptimizer(ConjugateGradientFormula.POLAK_RIBIERE);
+        optimizer.setMaxIterations(100);
+        optimizer.setConvergenceChecker(new SimpleScalarValueChecker(1.0e-6, 1.0e-6));
+        RealPointValuePair optimum =
+                optimizer.optimize(problem, GoalType.MINIMIZE, new double[] { 0, 0, 0 });
+        assertTrue(optimum.getValue() > 0.5);
+    }
+
+    public void testIllConditioned() throws Exception {
+        LinearProblem problem1 = new LinearProblem(new double[][] {
+                { 10.0, 7.0,  8.0,  7.0 },
+                {  7.0, 5.0,  6.0,  5.0 },
+                {  8.0, 6.0, 10.0,  9.0 },
+                {  7.0, 5.0,  9.0, 10.0 }
+        }, new double[] { 32, 23, 33, 31 });
+        NonLinearConjugateGradientOptimizer optimizer =
+            new NonLinearConjugateGradientOptimizer(ConjugateGradientFormula.POLAK_RIBIERE);
+        optimizer.setMaxIterations(100);
+        optimizer.setConvergenceChecker(new SimpleScalarValueChecker(1.0e-13, 1.0e-13));
+        BrentSolver solver = new BrentSolver();
+        solver.setAbsoluteAccuracy(1.0e-15);
+        solver.setRelativeAccuracy(1.0e-15);
+        optimizer.setLineSearchSolver(solver);
+        RealPointValuePair optimum1 =
+            optimizer.optimize(problem1, GoalType.MINIMIZE, new double[] { 0, 1, 2, 3 });
+        assertEquals(1.0, optimum1.getPoint()[0], 1.0e-5);
+        assertEquals(1.0, optimum1.getPoint()[1], 1.0e-5);
+        assertEquals(1.0, optimum1.getPoint()[2], 1.0e-5);
+        assertEquals(1.0, optimum1.getPoint()[3], 1.0e-5);
+
+        LinearProblem problem2 = new LinearProblem(new double[][] {
+                { 10.00, 7.00, 8.10, 7.20 },
+                {  7.08, 5.04, 6.00, 5.00 },
+                {  8.00, 5.98, 9.89, 9.00 },
+                {  6.99, 4.99, 9.00, 9.98 }
+        }, new double[] { 32, 23, 33, 31 });
+        RealPointValuePair optimum2 =
+            optimizer.optimize(problem2, GoalType.MINIMIZE, new double[] { 0, 1, 2, 3 });
+        assertEquals(-81.0, optimum2.getPoint()[0], 1.0e-1);
+        assertEquals(137.0, optimum2.getPoint()[1], 1.0e-1);
+        assertEquals(-34.0, optimum2.getPoint()[2], 1.0e-1);
+        assertEquals( 22.0, optimum2.getPoint()[3], 1.0e-1);
+
+    }
+
+    public void testMoreEstimatedParametersSimple() throws Exception {
+
+        LinearProblem problem = new LinearProblem(new double[][] {
+                { 3.0, 2.0,  0.0, 0.0 },
+                { 0.0, 1.0, -1.0, 1.0 },
+                { 2.0, 0.0,  1.0, 0.0 }
+        }, new double[] { 7.0, 3.0, 5.0 });
+
+        NonLinearConjugateGradientOptimizer optimizer =
+            new NonLinearConjugateGradientOptimizer(ConjugateGradientFormula.POLAK_RIBIERE);
+        optimizer.setMaxIterations(100);
+        optimizer.setConvergenceChecker(new SimpleScalarValueChecker(1.0e-6, 1.0e-6));
+        RealPointValuePair optimum =
+            optimizer.optimize(problem, GoalType.MINIMIZE, new double[] { 7, 6, 5, 4 });
+        assertEquals(0, optimum.getValue(), 1.0e-10);
+
+    }
+
+    public void testMoreEstimatedParametersUnsorted() throws Exception {
+        LinearProblem problem = new LinearProblem(new double[][] {
+                 { 1.0, 1.0,  0.0,  0.0, 0.0,  0.0 },
+                 { 0.0, 0.0,  1.0,  1.0, 1.0,  0.0 },
+                 { 0.0, 0.0,  0.0,  0.0, 1.0, -1.0 },
+                 { 0.0, 0.0, -1.0,  1.0, 0.0,  1.0 },
+                 { 0.0, 0.0,  0.0, -1.0, 1.0,  0.0 }
+        }, new double[] { 3.0, 12.0, -1.0, 7.0, 1.0 });
+        NonLinearConjugateGradientOptimizer optimizer =
+            new NonLinearConjugateGradientOptimizer(ConjugateGradientFormula.POLAK_RIBIERE);
+        optimizer.setMaxIterations(100);
+        optimizer.setConvergenceChecker(new SimpleScalarValueChecker(1.0e-6, 1.0e-6));
+        RealPointValuePair optimum =
+            optimizer.optimize(problem, GoalType.MINIMIZE, new double[] { 2, 2, 2, 2, 2, 2 });
+        assertEquals(0, optimum.getValue(), 1.0e-10);
+    }
+
+    public void testRedundantEquations() throws Exception {
+        LinearProblem problem = new LinearProblem(new double[][] {
+                { 1.0,  1.0 },
+                { 1.0, -1.0 },
+                { 1.0,  3.0 }
+        }, new double[] { 3.0, 1.0, 5.0 });
+
+        NonLinearConjugateGradientOptimizer optimizer =
+            new NonLinearConjugateGradientOptimizer(ConjugateGradientFormula.POLAK_RIBIERE);
+        optimizer.setMaxIterations(100);
+        optimizer.setConvergenceChecker(new SimpleScalarValueChecker(1.0e-6, 1.0e-6));
+        RealPointValuePair optimum =
+            optimizer.optimize(problem, GoalType.MINIMIZE, new double[] { 1, 1 });
+        assertEquals(2.0, optimum.getPoint()[0], 1.0e-8);
+        assertEquals(1.0, optimum.getPoint()[1], 1.0e-8);
+
+    }
+
+    public void testInconsistentEquations() throws Exception {
+        LinearProblem problem = new LinearProblem(new double[][] {
+                { 1.0,  1.0 },
+                { 1.0, -1.0 },
+                { 1.0,  3.0 }
+        }, new double[] { 3.0, 1.0, 4.0 });
+
+        NonLinearConjugateGradientOptimizer optimizer =
+            new NonLinearConjugateGradientOptimizer(ConjugateGradientFormula.POLAK_RIBIERE);
+        optimizer.setMaxIterations(100);
+        optimizer.setConvergenceChecker(new SimpleScalarValueChecker(1.0e-6, 1.0e-6));
+        RealPointValuePair optimum =
+            optimizer.optimize(problem, GoalType.MINIMIZE, new double[] { 1, 1 });
+        assertTrue(optimum.getValue() > 0.1);
+
+    }
+
+    public void testCircleFitting() throws Exception {
+        Circle circle = new Circle();
+        circle.addPoint( 30.0,  68.0);
+        circle.addPoint( 50.0,  -6.0);
+        circle.addPoint(110.0, -20.0);
+        circle.addPoint( 35.0,  15.0);
+        circle.addPoint( 45.0,  97.0);
+        NonLinearConjugateGradientOptimizer optimizer =
+            new NonLinearConjugateGradientOptimizer(ConjugateGradientFormula.POLAK_RIBIERE);
+        optimizer.setMaxIterations(100);
+        optimizer.setConvergenceChecker(new SimpleScalarValueChecker(1.0e-30, 1.0e-30));
+        BrentSolver solver = new BrentSolver();
+        solver.setAbsoluteAccuracy(1.0e-13);
+        solver.setRelativeAccuracy(1.0e-15);
+        optimizer.setLineSearchSolver(solver);
+        RealPointValuePair optimum =
+            optimizer.optimize(circle, GoalType.MINIMIZE, new double[] { 98.680, 47.345 });
+        Point2D.Double center = new Point2D.Double(optimum.getPointRef()[0], optimum.getPointRef()[1]);
+        assertEquals(69.960161753, circle.getRadius(center), 1.0e-8);
+        assertEquals(96.075902096, center.x, 1.0e-8);
+        assertEquals(48.135167894, center.y, 1.0e-8);
+    }
+
+    private static class LinearProblem implements DifferentiableMultivariateRealFunction, Serializable {
+
+        private static final long serialVersionUID = 703247177355019415L;
+        final RealMatrix factors;
+        final double[] target;
+        public LinearProblem(double[][] factors, double[] target) {
+            this.factors = new BlockRealMatrix(factors);
+            this.target  = target;
+        }
+
+        private double[] gradient(double[] point) {
+            double[] r = factors.operate(point);
+            for (int i = 0; i < r.length; ++i) {
+                r[i] -= target[i];
+            }
+            double[] p = factors.transpose().operate(r);
+            for (int i = 0; i < p.length; ++i) {
+                p[i] *= 2;
+            }
+            return p;
+        }
+
+        public double value(double[] variables) throws FunctionEvaluationException {
+            double[] y = factors.operate(variables);
+            double sum = 0;
+            for (int i = 0; i < y.length; ++i) {
+                double ri = y[i] - target[i];
+                sum += ri * ri;
+            }
+            return sum;
+        }
+
+        public MultivariateVectorialFunction gradient() {
+            return new MultivariateVectorialFunction() {
+                private static final long serialVersionUID = 2621997811350805819L;
+                public double[] value(double[] point) {
+                    return gradient(point);
+                }
+            };
+        }
+
+        public MultivariateRealFunction partialDerivative(final int k) {
+            return new MultivariateRealFunction() {
+                private static final long serialVersionUID = -6186178619133562011L;
+                public double value(double[] point) {
+                    return gradient(point)[k];
+                }
+            };
+        }
+
+    }
+
+    private static class Circle implements DifferentiableMultivariateRealFunction, Serializable {
+
+        private static final long serialVersionUID = -4711170319243817874L;
+
+        private ArrayList<Point2D.Double> points;
+
+        public Circle() {
+            points  = new ArrayList<Point2D.Double>();
+        }
+
+        public void addPoint(double px, double py) {
+            points.add(new Point2D.Double(px, py));
+        }
+
+        public double getRadius(Point2D.Double center) {
+            double r = 0;
+            for (Point2D.Double point : points) {
+                r += point.distance(center);
+            }
+            return r / points.size();
+        }
+
+        private double[] gradient(double[] point) {
+
+            // optimal radius
+            Point2D.Double center = new Point2D.Double(point[0], point[1]);
+            double radius = getRadius(center);
+
+            // gradient of the sum of squared residuals
+            double dJdX = 0;
+            double dJdY = 0;
+            for (Point2D.Double pk : points) {
+                double dk = pk.distance(center);
+                dJdX += (center.x - pk.x) * (dk - radius) / dk;
+                dJdY += (center.y - pk.y) * (dk - radius) / dk;
+            }
+            dJdX *= 2;
+            dJdY *= 2;
+
+            return new double[] { dJdX, dJdY };
+
+        }
+
+        public double value(double[] variables)
+                throws IllegalArgumentException, FunctionEvaluationException {
+
+            Point2D.Double center = new Point2D.Double(variables[0], variables[1]);
+            double radius = getRadius(center);
+
+            double sum = 0;
+            for (Point2D.Double point : points) {
+                double di = point.distance(center) - radius;
+                sum += di * di;
+            }
+
+            return sum;
+
+        }
+
+        public MultivariateVectorialFunction gradient() {
+            return new MultivariateVectorialFunction() {
+                private static final long serialVersionUID = 3174909643301201710L;
+                public double[] value(double[] point) {
+                    return gradient(point);
+                }
+            };
+        }
+
+        public MultivariateRealFunction partialDerivative(final int k) {
+            return new MultivariateRealFunction() {
+                private static final long serialVersionUID = 3073956364104833888L;
+                public double value(double[] point) {
+                    return gradient(point)[k];
+                }
+            };
+        }
+
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/optimization/linear/SimplexSolverTest.java b/src/test/java/org/apache/commons/math/optimization/linear/SimplexSolverTest.java
new file mode 100644
index 0000000..5bfbdd5
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/optimization/linear/SimplexSolverTest.java
@@ -0,0 +1,458 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.junit.Assert;
+
+import java.util.ArrayList;
+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;
+import org.junit.Test;
+
+public class SimplexSolverTest {
+
+    @Test
+    public void testMath272() throws OptimizationException {
+        LinearObjectiveFunction f = new LinearObjectiveFunction(new double[] { 2, 2, 1 }, 0);
+        Collection<LinearConstraint> constraints = new ArrayList<LinearConstraint>();
+        constraints.add(new LinearConstraint(new double[] { 1, 1, 0 }, Relationship.GEQ,  1));
+        constraints.add(new LinearConstraint(new double[] { 1, 0, 1 }, Relationship.GEQ,  1));
+        constraints.add(new LinearConstraint(new double[] { 0, 1, 0 }, Relationship.GEQ,  1));
+
+        SimplexSolver solver = new SimplexSolver();
+        RealPointValuePair solution = solver.optimize(f, constraints, GoalType.MINIMIZE, true);
+
+        Assert.assertEquals(0.0, solution.getPoint()[0], .0000001);
+        Assert.assertEquals(1.0, solution.getPoint()[1], .0000001);
+        Assert.assertEquals(1.0, solution.getPoint()[2], .0000001);
+        Assert.assertEquals(3.0, solution.getValue(), .0000001);
+    }
+
+    @Test
+    public void testMath286() throws OptimizationException {
+        LinearObjectiveFunction f = new LinearObjectiveFunction(new double[] { 0.8, 0.2, 0.7, 0.3, 0.6, 0.4 }, 0 );
+        Collection<LinearConstraint> constraints = new ArrayList<LinearConstraint>();
+        constraints.add(new LinearConstraint(new double[] { 1, 0, 1, 0, 1, 0 }, Relationship.EQ, 23.0));
+        constraints.add(new LinearConstraint(new double[] { 0, 1, 0, 1, 0, 1 }, Relationship.EQ, 23.0));
+        constraints.add(new LinearConstraint(new double[] { 1, 0, 0, 0, 0, 0 }, Relationship.GEQ, 10.0));
+        constraints.add(new LinearConstraint(new double[] { 0, 0, 1, 0, 0, 0 }, Relationship.GEQ, 8.0));
+        constraints.add(new LinearConstraint(new double[] { 0, 0, 0, 0, 1, 0 }, Relationship.GEQ, 5.0));
+
+        SimplexSolver solver = new SimplexSolver();
+        RealPointValuePair solution = solver.optimize(f, constraints, GoalType.MAXIMIZE, true);
+
+        Assert.assertEquals(25.8, solution.getValue(), .0000001);
+        Assert.assertEquals(23.0, solution.getPoint()[0] + solution.getPoint()[2] + solution.getPoint()[4], 0.0000001);
+        Assert.assertEquals(23.0, solution.getPoint()[1] + solution.getPoint()[3] + solution.getPoint()[5], 0.0000001);
+        Assert.assertTrue(solution.getPoint()[0] >= 10.0 - 0.0000001);
+        Assert.assertTrue(solution.getPoint()[2] >= 8.0 - 0.0000001);
+        Assert.assertTrue(solution.getPoint()[4] >= 5.0 - 0.0000001);
+    }
+
+    @Test
+    public void testDegeneracy() throws OptimizationException {
+        LinearObjectiveFunction f = new LinearObjectiveFunction(new double[] { 0.8, 0.7 }, 0 );
+        Collection<LinearConstraint> constraints = new ArrayList<LinearConstraint>();
+        constraints.add(new LinearConstraint(new double[] { 1, 1 }, Relationship.LEQ, 18.0));
+        constraints.add(new LinearConstraint(new double[] { 1, 0 }, Relationship.GEQ, 10.0));
+        constraints.add(new LinearConstraint(new double[] { 0, 1 }, Relationship.GEQ, 8.0));
+
+        SimplexSolver solver = new SimplexSolver();
+        RealPointValuePair solution = solver.optimize(f, constraints, GoalType.MAXIMIZE, true);
+        Assert.assertEquals(13.6, solution.getValue(), .0000001);
+    }
+
+    @Test
+    public void testMath288() throws OptimizationException {
+        LinearObjectiveFunction f = new LinearObjectiveFunction(new double[] { 7, 3, 0, 0 }, 0 );
+        Collection<LinearConstraint> constraints = new ArrayList<LinearConstraint>();
+        constraints.add(new LinearConstraint(new double[] { 3, 0, -5, 0 }, Relationship.LEQ, 0.0));
+        constraints.add(new LinearConstraint(new double[] { 2, 0, 0, -5 }, Relationship.LEQ, 0.0));
+        constraints.add(new LinearConstraint(new double[] { 0, 3, 0, -5 }, Relationship.LEQ, 0.0));
+        constraints.add(new LinearConstraint(new double[] { 1, 0, 0, 0 }, Relationship.LEQ, 1.0));
+        constraints.add(new LinearConstraint(new double[] { 0, 1, 0, 0 }, Relationship.LEQ, 1.0));
+
+        SimplexSolver solver = new SimplexSolver();
+        RealPointValuePair solution = solver.optimize(f, constraints, GoalType.MAXIMIZE, true);
+        Assert.assertEquals(10.0, solution.getValue(), .0000001);
+    }
+
+    @Test
+    public void testMath290GEQ() throws OptimizationException {
+        LinearObjectiveFunction f = new LinearObjectiveFunction(new double[] { 1, 5 }, 0 );
+        Collection<LinearConstraint> constraints = new ArrayList<LinearConstraint>();
+        constraints.add(new LinearConstraint(new double[] { 2, 0 }, Relationship.GEQ, -1.0));
+        SimplexSolver solver = new SimplexSolver();
+        RealPointValuePair solution = solver.optimize(f, constraints, GoalType.MINIMIZE, true);
+        Assert.assertEquals(0, solution.getValue(), .0000001);
+        Assert.assertEquals(0, solution.getPoint()[0], .0000001);
+        Assert.assertEquals(0, solution.getPoint()[1], .0000001);
+    }
+
+    @Test(expected=NoFeasibleSolutionException.class)
+    public void testMath290LEQ() throws OptimizationException {
+        LinearObjectiveFunction f = new LinearObjectiveFunction(new double[] { 1, 5 }, 0 );
+        Collection<LinearConstraint> constraints = new ArrayList<LinearConstraint>();
+        constraints.add(new LinearConstraint(new double[] { 2, 0 }, Relationship.LEQ, -1.0));
+        SimplexSolver solver = new SimplexSolver();
+        solver.optimize(f, constraints, GoalType.MINIMIZE, true);
+    }
+
+    @Test
+    public void testMath293() throws OptimizationException {
+      LinearObjectiveFunction f = new LinearObjectiveFunction(new double[] { 0.8, 0.2, 0.7, 0.3, 0.4, 0.6}, 0 );
+      Collection<LinearConstraint> constraints = new ArrayList<LinearConstraint>();
+      constraints.add(new LinearConstraint(new double[] { 1, 0, 1, 0, 1, 0 }, Relationship.EQ, 30.0));
+      constraints.add(new LinearConstraint(new double[] { 0, 1, 0, 1, 0, 1 }, Relationship.EQ, 30.0));
+      constraints.add(new LinearConstraint(new double[] { 0.8, 0.2, 0.0, 0.0, 0.0, 0.0 }, Relationship.GEQ, 10.0));
+      constraints.add(new LinearConstraint(new double[] { 0.0, 0.0, 0.7, 0.3, 0.0, 0.0 }, Relationship.GEQ, 10.0));
+      constraints.add(new LinearConstraint(new double[] { 0.0, 0.0, 0.0, 0.0, 0.4, 0.6 }, Relationship.GEQ, 10.0));
+
+      SimplexSolver solver = new SimplexSolver();
+      RealPointValuePair solution1 = solver.optimize(f, constraints, GoalType.MAXIMIZE, true);
+
+      Assert.assertEquals(15.7143, solution1.getPoint()[0], .0001);
+      Assert.assertEquals(0.0, solution1.getPoint()[1], .0001);
+      Assert.assertEquals(14.2857, solution1.getPoint()[2], .0001);
+      Assert.assertEquals(0.0, solution1.getPoint()[3], .0001);
+      Assert.assertEquals(0.0, solution1.getPoint()[4], .0001);
+      Assert.assertEquals(30.0, solution1.getPoint()[5], .0001);
+      Assert.assertEquals(40.57143, solution1.getValue(), .0001);
+
+      double valA = 0.8 * solution1.getPoint()[0] + 0.2 * solution1.getPoint()[1];
+      double valB = 0.7 * solution1.getPoint()[2] + 0.3 * solution1.getPoint()[3];
+      double valC = 0.4 * solution1.getPoint()[4] + 0.6 * solution1.getPoint()[5];
+
+      f = new LinearObjectiveFunction(new double[] { 0.8, 0.2, 0.7, 0.3, 0.4, 0.6}, 0 );
+      constraints = new ArrayList<LinearConstraint>();
+      constraints.add(new LinearConstraint(new double[] { 1, 0, 1, 0, 1, 0 }, Relationship.EQ, 30.0));
+      constraints.add(new LinearConstraint(new double[] { 0, 1, 0, 1, 0, 1 }, Relationship.EQ, 30.0));
+      constraints.add(new LinearConstraint(new double[] { 0.8, 0.2, 0.0, 0.0, 0.0, 0.0 }, Relationship.GEQ, valA));
+      constraints.add(new LinearConstraint(new double[] { 0.0, 0.0, 0.7, 0.3, 0.0, 0.0 }, Relationship.GEQ, valB));
+      constraints.add(new LinearConstraint(new double[] { 0.0, 0.0, 0.0, 0.0, 0.4, 0.6 }, Relationship.GEQ, valC));
+
+      RealPointValuePair solution2 = solver.optimize(f, constraints, GoalType.MAXIMIZE, true);
+      Assert.assertEquals(40.57143, solution2.getValue(), .0001);
+    }
+
+    @Test
+    public void testSimplexSolver() throws OptimizationException {
+        LinearObjectiveFunction f =
+            new LinearObjectiveFunction(new double[] { 15, 10 }, 7);
+        Collection<LinearConstraint> constraints = new ArrayList<LinearConstraint>();
+        constraints.add(new LinearConstraint(new double[] { 1, 0 }, Relationship.LEQ, 2));
+        constraints.add(new LinearConstraint(new double[] { 0, 1 }, Relationship.LEQ, 3));
+        constraints.add(new LinearConstraint(new double[] { 1, 1 }, Relationship.EQ, 4));
+
+        SimplexSolver solver = new SimplexSolver();
+        RealPointValuePair solution = solver.optimize(f, constraints, GoalType.MAXIMIZE, false);
+        Assert.assertEquals(2.0, solution.getPoint()[0], 0.0);
+        Assert.assertEquals(2.0, solution.getPoint()[1], 0.0);
+        Assert.assertEquals(57.0, solution.getValue(), 0.0);
+    }
+
+    @Test
+    public void testSingleVariableAndConstraint() throws OptimizationException {
+        LinearObjectiveFunction f = new LinearObjectiveFunction(new double[] { 3 }, 0);
+        Collection<LinearConstraint> constraints = new ArrayList<LinearConstraint>();
+        constraints.add(new LinearConstraint(new double[] { 1 }, Relationship.LEQ, 10));
+
+        SimplexSolver solver = new SimplexSolver();
+        RealPointValuePair solution = solver.optimize(f, constraints, GoalType.MAXIMIZE, false);
+        Assert.assertEquals(10.0, solution.getPoint()[0], 0.0);
+        Assert.assertEquals(30.0, solution.getValue(), 0.0);
+    }
+
+    /**
+     * With no artificial variables needed (no equals and no greater than
+     * constraints) we can go straight to Phase 2.
+     */
+    @Test
+    public void testModelWithNoArtificialVars() throws OptimizationException {
+        LinearObjectiveFunction f = new LinearObjectiveFunction(new double[] { 15, 10 }, 0);
+        Collection<LinearConstraint> constraints = new ArrayList<LinearConstraint>();
+        constraints.add(new LinearConstraint(new double[] { 1, 0 }, Relationship.LEQ, 2));
+        constraints.add(new LinearConstraint(new double[] { 0, 1 }, Relationship.LEQ, 3));
+        constraints.add(new LinearConstraint(new double[] { 1, 1 }, Relationship.LEQ, 4));
+
+        SimplexSolver solver = new SimplexSolver();
+        RealPointValuePair solution = solver.optimize(f, constraints, GoalType.MAXIMIZE, false);
+        Assert.assertEquals(2.0, solution.getPoint()[0], 0.0);
+        Assert.assertEquals(2.0, solution.getPoint()[1], 0.0);
+        Assert.assertEquals(50.0, solution.getValue(), 0.0);
+    }
+
+    @Test
+    public void testMinimization() throws OptimizationException {
+        LinearObjectiveFunction f = new LinearObjectiveFunction(new double[] { -2, 1 }, -5);
+        Collection<LinearConstraint> constraints = new ArrayList<LinearConstraint>();
+        constraints.add(new LinearConstraint(new double[] { 1, 2 }, Relationship.LEQ, 6));
+        constraints.add(new LinearConstraint(new double[] { 3, 2 }, Relationship.LEQ, 12));
+        constraints.add(new LinearConstraint(new double[] { 0, 1 }, Relationship.GEQ, 0));
+
+        SimplexSolver solver = new SimplexSolver();
+        RealPointValuePair solution = solver.optimize(f, constraints, GoalType.MINIMIZE, false);
+        Assert.assertEquals(4.0, solution.getPoint()[0], 0.0);
+        Assert.assertEquals(0.0, solution.getPoint()[1], 0.0);
+        Assert.assertEquals(-13.0, solution.getValue(), 0.0);
+    }
+
+    @Test
+    public void testSolutionWithNegativeDecisionVariable() throws OptimizationException {
+        LinearObjectiveFunction f = new LinearObjectiveFunction(new double[] { -2, 1 }, 0);
+        Collection<LinearConstraint> constraints = new ArrayList<LinearConstraint>();
+        constraints.add(new LinearConstraint(new double[] { 1, 1 }, Relationship.GEQ, 6));
+        constraints.add(new LinearConstraint(new double[] { 1, 2 }, Relationship.LEQ, 14));
+
+        SimplexSolver solver = new SimplexSolver();
+        RealPointValuePair solution = solver.optimize(f, constraints, GoalType.MAXIMIZE, false);
+        Assert.assertEquals(-2.0, solution.getPoint()[0], 0.0);
+        Assert.assertEquals(8.0, solution.getPoint()[1], 0.0);
+        Assert.assertEquals(12.0, solution.getValue(), 0.0);
+    }
+
+    @Test(expected = NoFeasibleSolutionException.class)
+    public void testInfeasibleSolution() throws OptimizationException {
+        LinearObjectiveFunction f = new LinearObjectiveFunction(new double[] { 15 }, 0);
+        Collection<LinearConstraint> constraints = new ArrayList<LinearConstraint>();
+        constraints.add(new LinearConstraint(new double[] { 1 }, Relationship.LEQ, 1));
+        constraints.add(new LinearConstraint(new double[] { 1 }, Relationship.GEQ, 3));
+
+        SimplexSolver solver = new SimplexSolver();
+        solver.optimize(f, constraints, GoalType.MAXIMIZE, false);
+    }
+
+    @Test(expected = UnboundedSolutionException.class)
+    public void testUnboundedSolution() throws OptimizationException {
+        LinearObjectiveFunction f = new LinearObjectiveFunction(new double[] { 15, 10 }, 0);
+        Collection<LinearConstraint> constraints = new ArrayList<LinearConstraint>();
+        constraints.add(new LinearConstraint(new double[] { 1, 0 }, Relationship.EQ, 2));
+
+        SimplexSolver solver = new SimplexSolver();
+        solver.optimize(f, constraints, GoalType.MAXIMIZE, false);
+    }
+
+    @Test
+    public void testRestrictVariablesToNonNegative() throws OptimizationException {
+        LinearObjectiveFunction f = new LinearObjectiveFunction(new double[] { 409, 523, 70, 204, 339 }, 0);
+        Collection<LinearConstraint> constraints = new ArrayList<LinearConstraint>();
+        constraints.add(new LinearConstraint(new double[] {    43,   56, 345,  56,    5 }, Relationship.LEQ,  4567456));
+        constraints.add(new LinearConstraint(new double[] {    12,   45,   7,  56,   23 }, Relationship.LEQ,    56454));
+        constraints.add(new LinearConstraint(new double[] {     8,  768,   0,  34, 7456 }, Relationship.LEQ,  1923421));
+        constraints.add(new LinearConstraint(new double[] { 12342, 2342,  34, 678, 2342 }, Relationship.GEQ,     4356));
+        constraints.add(new LinearConstraint(new double[] {    45,  678,  76,  52,   23 }, Relationship.EQ,    456356));
+
+        SimplexSolver solver = new SimplexSolver();
+        RealPointValuePair solution = solver.optimize(f, constraints, GoalType.MAXIMIZE, true);
+        Assert.assertEquals(2902.92783505155, solution.getPoint()[0], .0000001);
+        Assert.assertEquals(480.419243986254, solution.getPoint()[1], .0000001);
+        Assert.assertEquals(0.0, solution.getPoint()[2], .0000001);
+        Assert.assertEquals(0.0, solution.getPoint()[3], .0000001);
+        Assert.assertEquals(0.0, solution.getPoint()[4], .0000001);
+        Assert.assertEquals(1438556.7491409, solution.getValue(), .0000001);
+    }
+
+    @Test
+    public void testEpsilon() throws OptimizationException {
+      LinearObjectiveFunction f =
+          new LinearObjectiveFunction(new double[] { 10, 5, 1 }, 0);
+      Collection<LinearConstraint> constraints = new ArrayList<LinearConstraint>();
+      constraints.add(new LinearConstraint(new double[] {  9, 8, 0 }, Relationship.EQ,  17));
+      constraints.add(new LinearConstraint(new double[] {  0, 7, 8 }, Relationship.LEQ,  7));
+      constraints.add(new LinearConstraint(new double[] { 10, 0, 2 }, Relationship.LEQ, 10));
+
+      SimplexSolver solver = new SimplexSolver();
+      RealPointValuePair solution = solver.optimize(f, constraints, GoalType.MAXIMIZE, false);
+      Assert.assertEquals(1.0, solution.getPoint()[0], 0.0);
+      Assert.assertEquals(1.0, solution.getPoint()[1], 0.0);
+      Assert.assertEquals(0.0, solution.getPoint()[2], 0.0);
+      Assert.assertEquals(15.0, solution.getValue(), 0.0);
+  }
+
+    @Test
+    public void testTrivialModel() throws OptimizationException {
+        LinearObjectiveFunction f = new LinearObjectiveFunction(new double[] { 1, 1 }, 0);
+        Collection<LinearConstraint> constraints = new ArrayList<LinearConstraint>();
+        constraints.add(new LinearConstraint(new double[] { 1, 1 }, Relationship.EQ,  0));
+
+        SimplexSolver solver = new SimplexSolver();
+        RealPointValuePair solution = solver.optimize(f, constraints, GoalType.MAXIMIZE, true);
+        Assert.assertEquals(0, solution.getValue(), .0000001);
+    }
+
+    @Test
+    public void testLargeModel() throws OptimizationException {
+        double[] objective = new double[] {
+                                           1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+                                           1, 1, 12, 1, 1, 1, 1, 1, 1, 1,
+                                           1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+                                           1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+                                           12, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+                                           1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+                                           1, 1, 1, 1, 1, 1, 1, 1, 12, 1,
+                                           1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+                                           1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+                                           1, 1, 1, 1, 1, 1, 12, 1, 1, 1,
+                                           1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+                                           1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+                                           1, 1, 1, 1, 12, 1, 1, 1, 1, 1,
+                                           1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+                                           1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+                                           1, 1, 12, 1, 1, 1, 1, 1, 1, 1,
+                                           1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+                                           1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+                                           1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+                                           1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+                                           1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+                                           1, 1, 1, 1, 1, 1};
+
+        LinearObjectiveFunction f = new LinearObjectiveFunction(objective, 0);
+        Collection<LinearConstraint> constraints = new ArrayList<LinearConstraint>();
+        constraints.add(equationFromString(objective.length, "x0 + x1 + x2 + x3 - x12 = 0"));
+        constraints.add(equationFromString(objective.length, "x4 + x5 + x6 + x7 + x8 + x9 + x10 + x11 - x13 = 0"));
+        constraints.add(equationFromString(objective.length, "x4 + x5 + x6 + x7 + x8 + x9 + x10 + x11 >= 49"));
+        constraints.add(equationFromString(objective.length, "x0 + x1 + x2 + x3 >= 42"));
+        constraints.add(equationFromString(objective.length, "x14 + x15 + x16 + x17 - x26 = 0"));
+        constraints.add(equationFromString(objective.length, "x18 + x19 + x20 + x21 + x22 + x23 + x24 + x25 - x27 = 0"));
+        constraints.add(equationFromString(objective.length, "x14 + x15 + x16 + x17 - x12 = 0"));
+        constraints.add(equationFromString(objective.length, "x18 + x19 + x20 + x21 + x22 + x23 + x24 + x25 - x13 = 0"));
+        constraints.add(equationFromString(objective.length, "x28 + x29 + x30 + x31 - x40 = 0"));
+        constraints.add(equationFromString(objective.length, "x32 + x33 + x34 + x35 + x36 + x37 + x38 + x39 - x41 = 0"));
+        constraints.add(equationFromString(objective.length, "x32 + x33 + x34 + x35 + x36 + x37 + x38 + x39 >= 49"));
+        constraints.add(equationFromString(objective.length, "x28 + x29 + x30 + x31 >= 42"));
+        constraints.add(equationFromString(objective.length, "x42 + x43 + x44 + x45 - x54 = 0"));
+        constraints.add(equationFromString(objective.length, "x46 + x47 + x48 + x49 + x50 + x51 + x52 + x53 - x55 = 0"));
+        constraints.add(equationFromString(objective.length, "x42 + x43 + x44 + x45 - x40 = 0"));
+        constraints.add(equationFromString(objective.length, "x46 + x47 + x48 + x49 + x50 + x51 + x52 + x53 - x41 = 0"));
+        constraints.add(equationFromString(objective.length, "x56 + x57 + x58 + x59 - x68 = 0"));
+        constraints.add(equationFromString(objective.length, "x60 + x61 + x62 + x63 + x64 + x65 + x66 + x67 - x69 = 0"));
+        constraints.add(equationFromString(objective.length, "x60 + x61 + x62 + x63 + x64 + x65 + x66 + x67 >= 51"));
+        constraints.add(equationFromString(objective.length, "x56 + x57 + x58 + x59 >= 44"));
+        constraints.add(equationFromString(objective.length, "x70 + x71 + x72 + x73 - x82 = 0"));
+        constraints.add(equationFromString(objective.length, "x74 + x75 + x76 + x77 + x78 + x79 + x80 + x81 - x83 = 0"));
+        constraints.add(equationFromString(objective.length, "x70 + x71 + x72 + x73 - x68 = 0"));
+        constraints.add(equationFromString(objective.length, "x74 + x75 + x76 + x77 + x78 + x79 + x80 + x81 - x69 = 0"));
+        constraints.add(equationFromString(objective.length, "x84 + x85 + x86 + x87 - x96 = 0"));
+        constraints.add(equationFromString(objective.length, "x88 + x89 + x90 + x91 + x92 + x93 + x94 + x95 - x97 = 0"));
+        constraints.add(equationFromString(objective.length, "x88 + x89 + x90 + x91 + x92 + x93 + x94 + x95 >= 51"));
+        constraints.add(equationFromString(objective.length, "x84 + x85 + x86 + x87 >= 44"));
+        constraints.add(equationFromString(objective.length, "x98 + x99 + x100 + x101 - x110 = 0"));
+        constraints.add(equationFromString(objective.length, "x102 + x103 + x104 + x105 + x106 + x107 + x108 + x109 - x111 = 0"));
+        constraints.add(equationFromString(objective.length, "x98 + x99 + x100 + x101 - x96 = 0"));
+        constraints.add(equationFromString(objective.length, "x102 + x103 + x104 + x105 + x106 + x107 + x108 + x109 - x97 = 0"));
+        constraints.add(equationFromString(objective.length, "x112 + x113 + x114 + x115 - x124 = 0"));
+        constraints.add(equationFromString(objective.length, "x116 + x117 + x118 + x119 + x120 + x121 + x122 + x123 - x125 = 0"));
+        constraints.add(equationFromString(objective.length, "x116 + x117 + x118 + x119 + x120 + x121 + x122 + x123 >= 49"));
+        constraints.add(equationFromString(objective.length, "x112 + x113 + x114 + x115 >= 42"));
+        constraints.add(equationFromString(objective.length, "x126 + x127 + x128 + x129 - x138 = 0"));
+        constraints.add(equationFromString(objective.length, "x130 + x131 + x132 + x133 + x134 + x135 + x136 + x137 - x139 = 0"));
+        constraints.add(equationFromString(objective.length, "x126 + x127 + x128 + x129 - x124 = 0"));
+        constraints.add(equationFromString(objective.length, "x130 + x131 + x132 + x133 + x134 + x135 + x136 + x137 - x125 = 0"));
+        constraints.add(equationFromString(objective.length, "x140 + x141 + x142 + x143 - x152 = 0"));
+        constraints.add(equationFromString(objective.length, "x144 + x145 + x146 + x147 + x148 + x149 + x150 + x151 - x153 = 0"));
+        constraints.add(equationFromString(objective.length, "x144 + x145 + x146 + x147 + x148 + x149 + x150 + x151 >= 59"));
+        constraints.add(equationFromString(objective.length, "x140 + x141 + x142 + x143 >= 42"));
+        constraints.add(equationFromString(objective.length, "x154 + x155 + x156 + x157 - x166 = 0"));
+        constraints.add(equationFromString(objective.length, "x158 + x159 + x160 + x161 + x162 + x163 + x164 + x165 - x167 = 0"));
+        constraints.add(equationFromString(objective.length, "x154 + x155 + x156 + x157 - x152 = 0"));
+        constraints.add(equationFromString(objective.length, "x158 + x159 + x160 + x161 + x162 + x163 + x164 + x165 - x153 = 0"));
+        constraints.add(equationFromString(objective.length, "x83 + x82 - x168 = 0"));
+        constraints.add(equationFromString(objective.length, "x111 + x110 - x169 = 0"));
+        constraints.add(equationFromString(objective.length, "x170 - x182 = 0"));
+        constraints.add(equationFromString(objective.length, "x171 - x183 = 0"));
+        constraints.add(equationFromString(objective.length, "x172 - x184 = 0"));
+        constraints.add(equationFromString(objective.length, "x173 - x185 = 0"));
+        constraints.add(equationFromString(objective.length, "x174 - x186 = 0"));
+        constraints.add(equationFromString(objective.length, "x175 + x176 - x187 = 0"));
+        constraints.add(equationFromString(objective.length, "x177 - x188 = 0"));
+        constraints.add(equationFromString(objective.length, "x178 - x189 = 0"));
+        constraints.add(equationFromString(objective.length, "x179 - x190 = 0"));
+        constraints.add(equationFromString(objective.length, "x180 - x191 = 0"));
+        constraints.add(equationFromString(objective.length, "x181 - x192 = 0"));
+        constraints.add(equationFromString(objective.length, "x170 - x26 = 0"));
+        constraints.add(equationFromString(objective.length, "x171 - x27 = 0"));
+        constraints.add(equationFromString(objective.length, "x172 - x54 = 0"));
+        constraints.add(equationFromString(objective.length, "x173 - x55 = 0"));
+        constraints.add(equationFromString(objective.length, "x174 - x168 = 0"));
+        constraints.add(equationFromString(objective.length, "x177 - x169 = 0"));
+        constraints.add(equationFromString(objective.length, "x178 - x138 = 0"));
+        constraints.add(equationFromString(objective.length, "x179 - x139 = 0"));
+        constraints.add(equationFromString(objective.length, "x180 - x166 = 0"));
+        constraints.add(equationFromString(objective.length, "x181 - x167 = 0"));
+        constraints.add(equationFromString(objective.length, "x193 - x205 = 0"));
+        constraints.add(equationFromString(objective.length, "x194 - x206 = 0"));
+        constraints.add(equationFromString(objective.length, "x195 - x207 = 0"));
+        constraints.add(equationFromString(objective.length, "x196 - x208 = 0"));
+        constraints.add(equationFromString(objective.length, "x197 - x209 = 0"));
+        constraints.add(equationFromString(objective.length, "x198 + x199 - x210 = 0"));
+        constraints.add(equationFromString(objective.length, "x200 - x211 = 0"));
+        constraints.add(equationFromString(objective.length, "x201 - x212 = 0"));
+        constraints.add(equationFromString(objective.length, "x202 - x213 = 0"));
+        constraints.add(equationFromString(objective.length, "x203 - x214 = 0"));
+        constraints.add(equationFromString(objective.length, "x204 - x215 = 0"));
+        constraints.add(equationFromString(objective.length, "x193 - x182 = 0"));
+        constraints.add(equationFromString(objective.length, "x194 - x183 = 0"));
+        constraints.add(equationFromString(objective.length, "x195 - x184 = 0"));
+        constraints.add(equationFromString(objective.length, "x196 - x185 = 0"));
+        constraints.add(equationFromString(objective.length, "x197 - x186 = 0"));
+        constraints.add(equationFromString(objective.length, "x198 + x199 - x187 = 0"));
+        constraints.add(equationFromString(objective.length, "x200 - x188 = 0"));
+        constraints.add(equationFromString(objective.length, "x201 - x189 = 0"));
+        constraints.add(equationFromString(objective.length, "x202 - x190 = 0"));
+        constraints.add(equationFromString(objective.length, "x203 - x191 = 0"));
+        constraints.add(equationFromString(objective.length, "x204 - x192 = 0"));
+
+        SimplexSolver solver = new SimplexSolver();
+        RealPointValuePair solution = solver.optimize(f, constraints, GoalType.MINIMIZE, true);
+        Assert.assertEquals(7518.0, solution.getValue(), .0000001);
+    }
+
+    /**
+     * Converts a test string to a {@link LinearConstraint}.
+     * Ex: x0 + x1 + x2 + x3 - x12 = 0
+     */
+    private LinearConstraint equationFromString(int numCoefficients, String s) {
+        Relationship relationship;
+        if (s.contains(">=")) {
+            relationship = Relationship.GEQ;
+        } else if (s.contains("<=")) {
+            relationship = Relationship.LEQ;
+        } else if (s.contains("=")) {
+            relationship = Relationship.EQ;
+        } else {
+            throw new IllegalArgumentException();
+        }
+
+        String[] equationParts = s.split("[>|<]?=");
+        double rhs = Double.parseDouble(equationParts[1].trim());
+
+        double[] lhs = new double[numCoefficients];
+        String left = equationParts[0].replaceAll(" ?x", "");
+        String[] coefficients = left.split(" ");
+        for (String coefficient : coefficients) {
+            double value = coefficient.charAt(0) == '-' ? -1 : 1;
+            int index = Integer.parseInt(coefficient.replaceFirst("[+|-]", "").trim());
+            lhs[index] = value;
+        }
+        return new LinearConstraint(lhs, relationship, rhs);
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/optimization/linear/SimplexTableauTest.java b/src/test/java/org/apache/commons/math/optimization/linear/SimplexTableauTest.java
new file mode 100644
index 0000000..d41c238
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/optimization/linear/SimplexTableauTest.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.optimization.linear;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.optimization.GoalType;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class SimplexTableauTest {
+
+    @Test
+    public void testInitialization() {
+        LinearObjectiveFunction f = createFunction();
+        Collection<LinearConstraint> constraints = createConstraints();
+        SimplexTableau tableau =
+            new SimplexTableau(f, constraints, GoalType.MAXIMIZE, false, 1.0e-6);
+        double[][] expectedInitialTableau = {
+                                             {-1, 0,  -1,  -1,  2, 0, 0, 0, -4},
+                                             { 0, 1, -15, -10, 25, 0, 0, 0,  0},
+                                             { 0, 0,   1,   0, -1, 1, 0, 0,  2},
+                                             { 0, 0,   0,   1, -1, 0, 1, 0,  3},
+                                             { 0, 0,   1,   1, -2, 0, 0, 1,  4}
+        };
+        assertMatrixEquals(expectedInitialTableau, tableau.getData());
+    }
+
+    @Test
+    public void testDropPhase1Objective() {
+        LinearObjectiveFunction f = createFunction();
+        Collection<LinearConstraint> constraints = createConstraints();
+        SimplexTableau tableau =
+            new SimplexTableau(f, constraints, GoalType.MAXIMIZE, false, 1.0e-6);
+        double[][] expectedTableau = {
+                                      { 1, -15, -10, 0, 0, 0, 0},
+                                      { 0,   1,   0, 1, 0, 0, 2},
+                                      { 0,   0,   1, 0, 1, 0, 3},
+                                      { 0,   1,   1, 0, 0, 1, 4}
+        };
+        tableau.dropPhase1Objective();
+        assertMatrixEquals(expectedTableau, tableau.getData());
+    }
+
+    @Test
+    public void testTableauWithNoArtificialVars() {
+        LinearObjectiveFunction f = new LinearObjectiveFunction(new double[] {15, 10}, 0);
+        Collection<LinearConstraint> constraints = new ArrayList<LinearConstraint>();
+        constraints.add(new LinearConstraint(new double[] {1, 0}, Relationship.LEQ, 2));
+        constraints.add(new LinearConstraint(new double[] {0, 1}, Relationship.LEQ, 3));
+        constraints.add(new LinearConstraint(new double[] {1, 1}, Relationship.LEQ, 4));
+        SimplexTableau tableau =
+            new SimplexTableau(f, constraints, GoalType.MAXIMIZE, false, 1.0e-6);
+        double[][] initialTableau = {
+                                     {1, -15, -10, 25, 0, 0, 0, 0},
+                                     {0,   1,   0, -1, 1, 0, 0, 2},
+                                     {0,   0,   1, -1, 0, 1, 0, 3},
+                                     {0,   1,   1, -2, 0, 0, 1, 4}
+        };
+        assertMatrixEquals(initialTableau, tableau.getData());
+    }
+
+    @Test
+    public void testSerial() {
+        LinearObjectiveFunction f = createFunction();
+        Collection<LinearConstraint> constraints = createConstraints();
+        SimplexTableau tableau =
+            new SimplexTableau(f, constraints, GoalType.MAXIMIZE, false, 1.0e-6);
+        Assert.assertEquals(tableau, TestUtils.serializeAndRecover(tableau));
+    }
+
+    private LinearObjectiveFunction createFunction() {
+        return new LinearObjectiveFunction(new double[] {15, 10}, 0);
+    }
+
+    private Collection<LinearConstraint> createConstraints() {
+        Collection<LinearConstraint> constraints = new ArrayList<LinearConstraint>();
+        constraints.add(new LinearConstraint(new double[] {1, 0}, Relationship.LEQ, 2));
+        constraints.add(new LinearConstraint(new double[] {0, 1}, Relationship.LEQ, 3));
+        constraints.add(new LinearConstraint(new double[] {1, 1}, Relationship.EQ, 4));
+        return constraints;
+    }
+
+    private void assertMatrixEquals(double[][] expected, double[][] result) {
+        Assert.assertEquals("Wrong number of rows.", expected.length, result.length);
+        for (int i = 0; i < expected.length; i++) {
+            Assert.assertEquals("Wrong number of columns.", expected[i].length, result[i].length);
+            for (int j = 0; j < expected[i].length; j++) {
+                Assert.assertEquals("Wrong value at position [" + i + "," + j + "]", expected[i][j], result[i][j], 1.0e-15);
+            }
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/optimization/univariate/BracketFinderTest.java b/src/test/java/org/apache/commons/math/optimization/univariate/BracketFinderTest.java
new file mode 100644
index 0000000..2acd626
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/optimization/univariate/BracketFinderTest.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.optimization.univariate;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.optimization.GoalType;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class BracketFinderTest {
+
+    @Test
+    public void testCubicMin() throws MathException {
+        final BracketFinder bFind = new BracketFinder();
+        final UnivariateRealFunction func = new UnivariateRealFunction() {
+                public double value(double x) {
+                    if (x < -2) {
+                        return value(-2);
+                    }
+                    else  {
+                        return (x - 1) * (x + 2) * (x + 3);
+                    }
+                }
+            };
+
+        bFind.search(func, GoalType.MINIMIZE, -2 , -1);
+        final double tol = 1e-15;
+        // Comparing with results computed in Python.
+        Assert.assertEquals(-2, bFind.getLo(), tol);
+        Assert.assertEquals(-1, bFind.getMid(), tol);
+        Assert.assertEquals(0.61803399999999997, bFind.getHi(), tol);
+    }
+
+    @Test
+    public void testCubicMax() throws MathException {
+        final BracketFinder bFind = new BracketFinder();
+        final UnivariateRealFunction func = new UnivariateRealFunction() {
+                public double value(double x) {
+                    if (x < -2) {
+                        return value(-2);
+                    }
+                    else  {
+                        return -(x - 1) * (x + 2) * (x + 3);
+                    }
+                }
+            };
+
+        bFind.search(func, GoalType.MAXIMIZE, -2 , -1);
+        final double tol = 1e-15;
+        Assert.assertEquals(-2, bFind.getLo(), tol);
+        Assert.assertEquals(-1, bFind.getMid(), tol);
+        Assert.assertEquals(0.61803399999999997, bFind.getHi(), tol);
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/optimization/univariate/BrentOptimizerTest.java b/src/test/java/org/apache/commons/math/optimization/univariate/BrentOptimizerTest.java
new file mode 100644
index 0000000..2059289
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/optimization/univariate/BrentOptimizerTest.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.optimization.univariate;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.MaxIterationsExceededException;
+import org.apache.commons.math.exception.NoDataException;
+import org.apache.commons.math.analysis.QuinticFunction;
+import org.apache.commons.math.analysis.SinFunction;
+import org.apache.commons.math.analysis.UnivariateRealFunction;
+import org.apache.commons.math.optimization.GoalType;
+import org.apache.commons.math.optimization.UnivariateRealOptimizer;
+import org.apache.commons.math.stat.descriptive.DescriptiveStatistics;
+import org.apache.commons.math.util.FastMath;
+import org.junit.Test;
+
+/**
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (Sat, 05 Sep 2009) $
+ */
+public final class BrentOptimizerTest {
+
+    @Test
+    public void testSinMin() throws MathException {
+        UnivariateRealFunction f = new SinFunction();
+        UnivariateRealOptimizer minimizer = new BrentOptimizer();
+        minimizer.setMaxEvaluations(200);
+        assertEquals(200, minimizer.getMaxEvaluations());
+        try {
+            minimizer.getResult();
+            fail("an exception should have been thrown");
+        } catch (NoDataException ise) {
+            // expected
+        }
+        assertEquals(3 * FastMath.PI / 2, minimizer.optimize(f, GoalType.MINIMIZE, 4, 5), 10 * minimizer.getRelativeAccuracy());
+        assertTrue(minimizer.getIterationCount() <= 50);
+        assertEquals(3 * FastMath.PI / 2, minimizer.optimize(f, GoalType.MINIMIZE, 1, 5), 10 * minimizer.getRelativeAccuracy());
+        assertTrue(minimizer.getIterationCount() <= 50);
+        assertTrue(minimizer.getEvaluations()    <= 100);
+        assertTrue(minimizer.getEvaluations()    >=  15);
+        minimizer.setMaxEvaluations(10);
+        try {
+            minimizer.optimize(f, GoalType.MINIMIZE, 4, 5);
+            fail("an exception should have been thrown");
+        } catch (FunctionEvaluationException mue) {
+            // expected
+        } catch (Exception e) {
+            fail("wrong exception caught");
+        }
+    }
+
+    @Test
+    public void testQuinticMin() throws MathException {
+        // The function has local minima at -0.27195613 and 0.82221643.
+        UnivariateRealFunction f = new QuinticFunction();
+        UnivariateRealOptimizer minimizer = new BrentOptimizer();
+        assertEquals(-0.27195613, minimizer.optimize(f, GoalType.MINIMIZE, -0.3, -0.2), 1.0e-8);
+        assertEquals( 0.82221643, minimizer.optimize(f, GoalType.MINIMIZE,  0.3,  0.9), 1.0e-8);
+        assertTrue(minimizer.getIterationCount() <= 50);
+
+        // search in a large interval
+        assertEquals(-0.27195613, minimizer.optimize(f, GoalType.MINIMIZE, -1.0, 0.2), 1.0e-8);
+        assertTrue(minimizer.getIterationCount() <= 50);
+    }
+
+    @Test
+    public void testQuinticMinStatistics() throws MathException {
+        // The function has local minima at -0.27195613 and 0.82221643.
+        UnivariateRealFunction f = new QuinticFunction();
+        UnivariateRealOptimizer minimizer = new BrentOptimizer();
+        minimizer.setRelativeAccuracy(1e-10);
+        minimizer.setAbsoluteAccuracy(1e-11);
+
+        final DescriptiveStatistics[] stat = new DescriptiveStatistics[3];
+        for (int i = 0; i < stat.length; i++) {
+            stat[i] = new DescriptiveStatistics();
+        }
+
+        final double min = -0.75;
+        final double max = 0.25;
+        final int nSamples = 200;
+        final double delta = (max - min) / nSamples;
+        for (int i = 0; i < nSamples; i++) {
+            final double start = min + i * delta;
+            stat[0].addValue(minimizer.optimize(f, GoalType.MINIMIZE, min, max, start));
+            stat[1].addValue(minimizer.getIterationCount());
+            stat[2].addValue(minimizer.getEvaluations());
+        }
+
+        final double meanOptValue = stat[0].getMean();
+        final double medianIter = stat[1].getPercentile(50);
+        final double medianEval = stat[2].getPercentile(50);
+        assertTrue(meanOptValue > -0.27195612812 && meanOptValue < -0.27195612811);
+        assertEquals(medianIter, 17, FastMath.ulp(1d));
+        assertEquals(medianEval, 18, FastMath.ulp(1d));
+    }
+
+    @Test
+    public void testQuinticMax() throws MathException {
+        // The quintic function has zeros at 0, +-0.5 and +-1.
+        // The function has a local maximum at 0.27195613.
+        UnivariateRealFunction f = new QuinticFunction();
+        UnivariateRealOptimizer minimizer = new BrentOptimizer();
+        assertEquals(0.27195613, minimizer.optimize(f, GoalType.MAXIMIZE, 0.2, 0.3), 1.0e-8);
+        minimizer.setMaximalIterationCount(5);
+        try {
+            minimizer.optimize(f, GoalType.MAXIMIZE, 0.2, 0.3);
+            fail("an exception should have been thrown");
+        } catch (MaxIterationsExceededException miee) {
+            // expected
+        }
+    }
+
+    @Test
+    public void testMinEndpoints() throws Exception {
+        UnivariateRealFunction f = new SinFunction();
+        UnivariateRealOptimizer solver = new BrentOptimizer();
+
+        solver.setRelativeAccuracy(1e-8);
+
+        // endpoint is minimum
+        double result = solver.optimize(f, GoalType.MINIMIZE, 3 * FastMath.PI / 2, 5);
+        assertEquals(3 * FastMath.PI / 2, result, 10 * solver.getRelativeAccuracy());
+
+        result = solver.optimize(f, GoalType.MINIMIZE, 4, 3 * FastMath.PI / 2);
+        assertEquals(3 * FastMath.PI / 2, result, 10 * solver.getRelativeAccuracy());
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/random/AbstractRandomGeneratorTest.java b/src/test/java/org/apache/commons/math/random/AbstractRandomGeneratorTest.java
new file mode 100644
index 0000000..fae0941
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/random/AbstractRandomGeneratorTest.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.random;
+
+import org.apache.commons.math.stat.Frequency;
+
+
+/**
+ * Test cases for the AbstractRandomGenerator class
+ *
+ * @version $Revision: 902201 $ $Date: 2010-01-22 19:18:16 +0100 (ven. 22 janv. 2010) $
+ */
+
+public class AbstractRandomGeneratorTest extends RandomDataTest {
+
+    protected TestRandomGenerator testGenerator = new TestRandomGenerator();
+
+    public AbstractRandomGeneratorTest(String name) {
+        super(name);
+        randomData = new RandomDataImpl(testGenerator);
+    }
+
+    @Override
+    public void testNextInt() {
+        try {
+            testGenerator.nextInt(-1);
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+        Frequency freq = new Frequency();
+        int value = 0;
+        for (int i=0; i<smallSampleSize; i++) {
+            value = testGenerator.nextInt(4);
+            assertTrue("nextInt range",(value >= 0) && (value <= 3));
+            freq.addValue(value);
+        }
+        long[] observed = new long[4];
+        for (int i=0; i<4; i++) {
+            observed[i] = freq.getCount(i);
+        }
+
+        /* Use ChiSquare dist with df = 4-1 = 3, alpha = .001
+         * Change to 11.34 for alpha = .01
+         */
+        assertTrue("chi-square test -- will fail about 1 in 1000 times",
+                testStatistic.chiSquare(expected,observed) < 16.27);
+    }
+
+    @Override
+    public void testNextLong() {
+        long q1 = Long.MAX_VALUE/4;
+        long q2 = 2 *  q1;
+        long q3 = 3 * q1;
+
+        Frequency freq = new Frequency();
+        long val = 0;
+        int value = 0;
+        for (int i=0; i<smallSampleSize; i++) {
+            val = testGenerator.nextLong();
+            if (val < q1) {
+                value = 0;
+            } else if (val < q2) {
+                value = 1;
+            } else if (val < q3) {
+                value = 2;
+            } else {
+                value = 3;
+            }
+            freq.addValue(value);
+        }
+        long[] observed = new long[4];
+        for (int i=0; i<4; i++) {
+            observed[i] = freq.getCount(i);
+        }
+
+        /* Use ChiSquare dist with df = 4-1 = 3, alpha = .001
+         * Change to 11.34 for alpha = .01
+         */
+        assertTrue("chi-square test -- will fail about 1 in 1000 times",
+                testStatistic.chiSquare(expected,observed) < 16.27);
+    }
+
+    public void testNextBoolean() {
+        long halfSampleSize = smallSampleSize / 2;
+        double[] expected = {halfSampleSize, halfSampleSize};
+        long[] observed = new long[2];
+        for (int i=0; i<smallSampleSize; i++) {
+            if (testGenerator.nextBoolean()) {
+                observed[0]++;
+            } else {
+                observed[1]++;
+            }
+        }
+        /* Use ChiSquare dist with df = 2-1 = 1, alpha = .001
+         * Change to 6.635 for alpha = .01
+         */
+        assertTrue("chi-square test -- will fail about 1 in 1000 times",
+                testStatistic.chiSquare(expected,observed) < 10.828);
+    }
+
+    public void testNextFloat() {
+        Frequency freq = new Frequency();
+        float val = 0;
+        int value = 0;
+        for (int i=0; i<smallSampleSize; i++) {
+            val = testGenerator.nextFloat();
+            if (val < 0.25) {
+                value = 0;
+            } else if (val < 0.5) {
+                value = 1;
+            } else if (val < 0.75) {
+                value = 2;
+            } else {
+                value = 3;
+            }
+            freq.addValue(value);
+        }
+        long[] observed = new long[4];
+        for (int i=0; i<4; i++) {
+            observed[i] = freq.getCount(i);
+        }
+
+        /* Use ChiSquare dist with df = 4-1 = 3, alpha = .001
+         * Change to 11.34 for alpha = .01
+         */
+        assertTrue("chi-square test -- will fail about 1 in 1000 times",
+                testStatistic.chiSquare(expected,observed) < 16.27);
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/random/CorrelatedRandomVectorGeneratorTest.java b/src/test/java/org/apache/commons/math/random/CorrelatedRandomVectorGeneratorTest.java
new file mode 100644
index 0000000..842c2bf
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/random/CorrelatedRandomVectorGeneratorTest.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;
+
+import junit.framework.TestCase;
+
+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.stat.descriptive.moment.VectorialCovariance;
+import org.apache.commons.math.stat.descriptive.moment.VectorialMean;
+import org.apache.commons.math.util.FastMath;
+
+public class CorrelatedRandomVectorGeneratorTest
+extends TestCase {
+
+    public CorrelatedRandomVectorGeneratorTest(String name) {
+        super(name);
+        mean       = null;
+        covariance = null;
+        generator  = null;
+    }
+
+    public void testRank() {
+        assertEquals(3, generator.getRank());
+    }
+
+    public void testMath226()
+        throws DimensionMismatchException, NotPositiveDefiniteMatrixException {
+        double[] mean = { 1, 1, 10, 1 };
+        double[][] cov = {
+                { 1, 3, 2, 6 },
+                { 3, 13, 16, 2 },
+                { 2, 16, 38, -1 },
+                { 6, 2, -1, 197 }
+        };
+        RealMatrix covRM = MatrixUtils.createRealMatrix(cov);
+        JDKRandomGenerator jg = new JDKRandomGenerator();
+        jg.setSeed(5322145245211l);
+        NormalizedRandomGenerator rg = new GaussianRandomGenerator(jg);
+        CorrelatedRandomVectorGenerator sg =
+            new CorrelatedRandomVectorGenerator(mean, covRM, 0.00001, rg);
+
+        for (int i = 0; i < 10; i++) {
+            double[] generated = sg.nextVector();
+            assertTrue(FastMath.abs(generated[0] - 1) > 0.1);
+        }
+
+    }
+
+    public void testRootMatrix() {
+        RealMatrix b = generator.getRootMatrix();
+        RealMatrix bbt = b.multiply(b.transpose());
+        for (int i = 0; i < covariance.getRowDimension(); ++i) {
+            for (int j = 0; j < covariance.getColumnDimension(); ++j) {
+                assertEquals(covariance.getEntry(i, j), bbt.getEntry(i, j), 1.0e-12);
+            }
+        }
+    }
+
+    public void testMeanAndCovariance() throws DimensionMismatchException {
+
+        VectorialMean meanStat = new VectorialMean(mean.length);
+        VectorialCovariance covStat = new VectorialCovariance(mean.length, true);
+        for (int i = 0; i < 5000; ++i) {
+            double[] v = generator.nextVector();
+            meanStat.increment(v);
+            covStat.increment(v);
+        }
+
+        double[] estimatedMean = meanStat.getResult();
+        RealMatrix estimatedCovariance = covStat.getResult();
+        for (int i = 0; i < estimatedMean.length; ++i) {
+            assertEquals(mean[i], estimatedMean[i], 0.07);
+            for (int j = 0; j <= i; ++j) {
+                assertEquals(covariance.getEntry(i, j),
+                        estimatedCovariance.getEntry(i, j),
+                        0.1 * (1.0 + FastMath.abs(mean[i])) * (1.0 + FastMath.abs(mean[j])));
+            }
+        }
+
+    }
+
+    @Override
+    public void setUp() {
+        try {
+            mean = new double[] { 0.0, 1.0, -3.0, 2.3};
+
+            RealMatrix b = MatrixUtils.createRealMatrix(4, 3);
+            int counter = 0;
+            for (int i = 0; i < b.getRowDimension(); ++i) {
+                for (int j = 0; j < b.getColumnDimension(); ++j) {
+                    b.setEntry(i, j, 1.0 + 0.1 * ++counter);
+                }
+            }
+            RealMatrix bbt = b.multiply(b.transpose());
+            covariance = MatrixUtils.createRealMatrix(mean.length, mean.length);
+            for (int i = 0; i < covariance.getRowDimension(); ++i) {
+                covariance.setEntry(i, i, bbt.getEntry(i, i));
+                for (int j = 0; j < covariance.getColumnDimension(); ++j) {
+                    double s = bbt.getEntry(i, j);
+                    covariance.setEntry(i, j, s);
+                    covariance.setEntry(j, i, s);
+                }
+            }
+
+            RandomGenerator rg = new JDKRandomGenerator();
+            rg.setSeed(17399225432l);
+            GaussianRandomGenerator rawGenerator = new GaussianRandomGenerator(rg);
+            generator = new CorrelatedRandomVectorGenerator(mean,
+                                                            covariance,
+                                                            1.0e-12 * covariance.getNorm(),
+                                                            rawGenerator);
+        } catch (DimensionMismatchException e) {
+            fail(e.getMessage());
+        } catch (NotPositiveDefiniteMatrixException e) {
+            fail("not positive definite matrix");
+        }
+    }
+
+    @Override
+    public void tearDown() {
+        mean       = null;
+        covariance = null;
+        generator  = null;
+    }
+
+    private double[] mean;
+    private RealMatrix covariance;
+    private CorrelatedRandomVectorGenerator generator;
+
+}
diff --git a/src/test/java/org/apache/commons/math/random/EmpiricalDistributionTest.java b/src/test/java/org/apache/commons/math/random/EmpiricalDistributionTest.java
new file mode 100644
index 0000000..dfacf30
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/random/EmpiricalDistributionTest.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.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.ArrayList;
+
+import org.apache.commons.math.RetryTestCase;
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.stat.descriptive.SummaryStatistics;
+
+/**
+ * Test cases for the EmpiricalDistribution class
+ *
+ * @version $Revision: 1003907 $ $Date: 2010-10-03 00:23:34 +0200 (dim. 03 oct. 2010) $
+ */
+
+public final class EmpiricalDistributionTest extends RetryTestCase {
+
+    protected EmpiricalDistribution empiricalDistribution = null;
+    protected EmpiricalDistribution empiricalDistribution2 = null;
+    protected File file = null;
+    protected URL url = null;
+    protected double[] dataArray = null;
+
+    public EmpiricalDistributionTest(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setUp() throws IOException {
+        empiricalDistribution = new EmpiricalDistributionImpl(100);
+        url = getClass().getResource("testData.txt");
+
+        empiricalDistribution2 = new EmpiricalDistributionImpl(100);
+        BufferedReader in =
+                new BufferedReader(new InputStreamReader(
+                        url.openStream()));
+        String str = null;
+        ArrayList<Double> list = new ArrayList<Double>();
+        while ((str = in.readLine()) != null) {
+            list.add(Double.valueOf(str));
+        }
+        in.close();
+        in = null;
+
+        dataArray = new double[list.size()];
+        int i = 0;
+        for (Double data : list) {
+            dataArray[i] = data.doubleValue();
+            i++;
+        }
+    }
+
+    /**
+     * Test EmpiricalDistrbution.load() using sample data file.<br>
+     * Check that the sampleCount, mu and sigma match data in
+     * the sample data file.
+     */
+    public void testLoad() throws Exception {
+        empiricalDistribution.load(url);
+        // testData File has 10000 values, with mean ~ 5.0, std dev ~ 1
+        // Make sure that loaded distribution matches this
+        assertEquals(empiricalDistribution.getSampleStats().getN(),1000,10E-7);
+        //TODO: replace with statistical tests
+        assertEquals
+            (empiricalDistribution.getSampleStats().getMean(),
+                5.069831575018909,10E-7);
+        assertEquals
+          (empiricalDistribution.getSampleStats().getStandardDeviation(),
+                1.0173699343977738,10E-7);
+    }
+
+    /**
+     * Test EmpiricalDistrbution.load(double[]) using data taken from
+     * sample data file.<br>
+     * Check that the sampleCount, mu and sigma match data in
+     * the sample data file.
+     */
+    public void testDoubleLoad() throws Exception {
+        empiricalDistribution2.load(dataArray);
+        // testData File has 10000 values, with mean ~ 5.0, std dev ~ 1
+        // Make sure that loaded distribution matches this
+        assertEquals(empiricalDistribution2.getSampleStats().getN(),1000,10E-7);
+        //TODO: replace with statistical tests
+        assertEquals
+            (empiricalDistribution2.getSampleStats().getMean(),
+                5.069831575018909,10E-7);
+        assertEquals
+          (empiricalDistribution2.getSampleStats().getStandardDeviation(),
+                1.0173699343977738,10E-7);
+
+        double[] bounds = ((EmpiricalDistributionImpl) empiricalDistribution2).getGeneratorUpperBounds();
+        assertEquals(bounds.length, 100);
+        assertEquals(bounds[99], 1.0, 10e-12);
+
+    }
+
+    /**
+      * Generate 1000 random values and make sure they look OK.<br>
+      * Note that there is a non-zero (but very small) probability that
+      * these tests will fail even if the code is working as designed.
+      */
+    public void testNext() throws Exception {
+        tstGen(0.1);
+        tstDoubleGen(0.1);
+    }
+
+    /**
+      * Make sure exception thrown if digest getNext is attempted
+      * before loading empiricalDistribution.
+     */
+    public void testNexFail() {
+        try {
+            empiricalDistribution.getNextValue();
+            empiricalDistribution2.getNextValue();
+            fail("Expecting IllegalStateException");
+        } catch (IllegalStateException ex) {
+            // expected
+        }
+    }
+
+    /**
+     * Make sure we can handle a grid size that is too fine
+     */
+    public void testGridTooFine() throws Exception {
+        empiricalDistribution = new EmpiricalDistributionImpl(1001);
+        tstGen(0.1);
+        empiricalDistribution2 = new EmpiricalDistributionImpl(1001);
+        tstDoubleGen(0.1);
+    }
+
+    /**
+     * How about too fat?
+     */
+    public void testGridTooFat() throws Exception {
+        empiricalDistribution = new EmpiricalDistributionImpl(1);
+        tstGen(5); // ridiculous tolerance; but ridiculous grid size
+                   // really just checking to make sure we do not bomb
+        empiricalDistribution2 = new EmpiricalDistributionImpl(1);
+        tstDoubleGen(5);
+    }
+
+    /**
+     * Test bin index overflow problem (BZ 36450)
+     */
+    public void testBinIndexOverflow() throws Exception {
+        double[] x = new double[] {9474.94326071674, 2080107.8865462579};
+        new EmpiricalDistributionImpl().load(x);
+    }
+
+    public void testSerialization() {
+        // Empty
+        EmpiricalDistribution dist = new EmpiricalDistributionImpl();
+        EmpiricalDistribution dist2 = (EmpiricalDistribution) TestUtils.serializeAndRecover(dist);
+        verifySame(dist, dist2);
+
+        // Loaded
+        empiricalDistribution2.load(dataArray);
+        dist2 = (EmpiricalDistribution) TestUtils.serializeAndRecover(empiricalDistribution2);
+        verifySame(empiricalDistribution2, dist2);
+    }
+
+    public void testLoadNullDoubleArray() {
+        EmpiricalDistribution dist = new EmpiricalDistributionImpl();
+        try {
+            dist.load((double[]) null);
+            fail("load((double[]) null) expected NullPointerException");
+        } catch (NullPointerException e) {
+            // expected
+        }
+    }
+
+    public void testLoadNullURL() throws Exception {
+        EmpiricalDistribution dist = new EmpiricalDistributionImpl();
+        try {
+            dist.load((URL) null);
+            fail("load((URL) null) expected NullPointerException");
+        } catch (NullPointerException e) {
+            // expected
+        }
+    }
+
+    public void testLoadNullFile() throws Exception {
+        EmpiricalDistribution dist = new EmpiricalDistributionImpl();
+        try {
+            dist.load((File) null);
+            fail("load((File) null) expected NullPointerException");
+        } catch (NullPointerException e) {
+            // expected
+        }
+    }
+
+    /**
+     * MATH-298
+     */
+    public void testGetBinUpperBounds() {
+        double[] testData = {0, 1, 1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10};
+        EmpiricalDistributionImpl dist = new EmpiricalDistributionImpl(5);
+        dist.load(testData);
+        double[] expectedBinUpperBounds = {2, 4, 6, 8, 10};
+        double[] expectedGeneratorUpperBounds = {4d/13d, 7d/13d, 9d/13d, 11d/13d, 1};
+        double tol = 10E-12;
+        TestUtils.assertEquals(expectedBinUpperBounds, dist.getUpperBounds(), tol);
+        TestUtils.assertEquals(expectedGeneratorUpperBounds, dist.getGeneratorUpperBounds(), tol);
+    }
+
+    private void verifySame(EmpiricalDistribution d1, EmpiricalDistribution d2) {
+        assertEquals(d1.isLoaded(), d2.isLoaded());
+        assertEquals(d1.getBinCount(), d2.getBinCount());
+        assertEquals(d1.getSampleStats(), d2.getSampleStats());
+        if (d1.isLoaded()) {
+            for (int i = 0;  i < d1.getUpperBounds().length; i++) {
+                assertEquals(d1.getUpperBounds()[i], d2.getUpperBounds()[i], 0);
+            }
+            assertEquals(d1.getBinStats(), d2.getBinStats());
+        }
+    }
+
+    private void tstGen(double tolerance)throws Exception {
+        empiricalDistribution.load(url);
+        SummaryStatistics stats = new SummaryStatistics();
+        for (int i = 1; i < 1000; i++) {
+            stats.addValue(empiricalDistribution.getNextValue());
+        }
+        assertEquals("mean", stats.getMean(),5.069831575018909,tolerance);
+        assertEquals
+         ("std dev", stats.getStandardDeviation(),1.0173699343977738,tolerance);
+    }
+
+    private void tstDoubleGen(double tolerance)throws Exception {
+        empiricalDistribution2.load(dataArray);
+        SummaryStatistics stats = new SummaryStatistics();
+        for (int i = 1; i < 1000; i++) {
+            stats.addValue(empiricalDistribution2.getNextValue());
+        }
+        assertEquals("mean", stats.getMean(),5.069831575018909,tolerance);
+        assertEquals
+         ("std dev", stats.getStandardDeviation(),1.0173699343977738,tolerance);
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/random/GaussianRandomGeneratorTest.java b/src/test/java/org/apache/commons/math/random/GaussianRandomGeneratorTest.java
new file mode 100644
index 0000000..185d8b6
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/random/GaussianRandomGeneratorTest.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.random;
+
+import org.apache.commons.math.stat.StatUtils;
+
+import junit.framework.*;
+
+public class GaussianRandomGeneratorTest
+extends TestCase {
+
+    public GaussianRandomGeneratorTest(String name) {
+        super(name);
+    }
+
+    public void testMeanAndStandardDeviation() {
+        RandomGenerator rg = new JDKRandomGenerator();
+        rg.setSeed(17399225432l);
+        GaussianRandomGenerator generator = new GaussianRandomGenerator(rg);
+        double[] sample = new double[10000];
+        for (int i = 0; i < sample.length; ++i) {
+            sample[i] = generator.nextNormalizedDouble();
+        }
+        assertEquals(0.0, StatUtils.mean(sample), 0.012);
+        assertEquals(1.0, StatUtils.variance(sample), 0.01);
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/random/MersenneTwisterTest.java b/src/test/java/org/apache/commons/math/random/MersenneTwisterTest.java
new file mode 100644
index 0000000..7547236
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/random/MersenneTwisterTest.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.random;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.commons.math.stat.descriptive.SummaryStatistics;
+import org.apache.commons.math.util.FastMath;
+import org.junit.Test;
+
+public class MersenneTwisterTest {
+
+    @Test
+    public void testGaussian() {
+        MersenneTwister mt = new MersenneTwister(42853252100l);
+        SummaryStatistics sample = new SummaryStatistics();
+        for (int i = 0; i < 1000; ++i) {
+            sample.addValue(mt.nextGaussian());
+        }
+        assertEquals(0.0, sample.getMean(), 0.005);
+        assertEquals(1.0, sample.getStandardDeviation(), 0.025);
+    }
+
+    @Test
+    public void testDouble() {
+        MersenneTwister mt = new MersenneTwister(195357343514l);
+        SummaryStatistics sample = new SummaryStatistics();
+        for (int i = 0; i < 1000; ++i) {
+            sample.addValue(mt.nextDouble());
+        }
+        assertEquals(0.5, sample.getMean(), 0.02);
+        assertEquals(1.0 / (2.0 * FastMath.sqrt(3.0)),
+                     sample.getStandardDeviation(),
+                     0.002);
+    }
+
+    @Test
+    public void testFloat() {
+        MersenneTwister mt = new MersenneTwister(4442733263l);
+        SummaryStatistics sample = new SummaryStatistics();
+        for (int i = 0; i < 1000; ++i) {
+            sample.addValue(mt.nextFloat());
+        }
+        assertEquals(0.5, sample.getMean(), 0.01);
+        assertEquals(1.0 / (2.0 * FastMath.sqrt(3.0)),
+                     sample.getStandardDeviation(),
+                     0.006);
+    }
+
+    @Test(expected=java.lang.IllegalArgumentException.class)
+    public void testNextIntNeg() {
+        new MersenneTwister(1).nextInt(-1);
+    }
+
+    @Test
+    public void testNextIntN() {
+        MersenneTwister mt = new MersenneTwister(0x12b8a7412bb25el);
+        for (int n = 1; n < 20; ++n) {
+            int[] count = new int[n];
+            for (int k = 0; k < 10000; ++k) {
+                int l = mt.nextInt(n);
+                ++count[l];
+                assertTrue(l >= 0);
+                assertTrue(l <  n);
+            }
+            for (int i = 0; i < n; ++i) {
+                assertTrue(n * count[i] >  8600);
+                assertTrue(n * count[i] < 11200);
+            }
+        }
+    }
+
+    @Test
+    public void testNextInt() {
+        MersenneTwister mt = new MersenneTwister(new int[] { 1, 2, 3, 4, 5 });
+        int walk = 0;
+        for (int k = 0; k < 10000; ++k) {
+           if (mt.nextInt() >= 0) {
+               ++walk;
+           } else {
+               --walk;
+           }
+        }
+        assertTrue(FastMath.abs(walk) < 120);
+    }
+
+    @Test
+    public void testNextLong() {
+        MersenneTwister mt = new MersenneTwister(12345);
+        int walk = 0;
+        for (int k = 0; k < 10000; ++k) {
+           if (mt.nextLong() >= 0) {
+               ++walk;
+           } else {
+               --walk;
+           }
+        }
+        assertTrue(FastMath.abs(walk) < 50);
+    }
+
+    @Test
+    public void testNexBoolean() {
+        MersenneTwister mt = new MersenneTwister(76342);
+        int walk = 0;
+        for (int k = 0; k < 10000; ++k) {
+           if (mt.nextBoolean()) {
+               ++walk;
+           } else {
+               --walk;
+           }
+        }
+        assertTrue(FastMath.abs(walk) < 250);
+    }
+
+    @Test
+    public void testNexBytes() {
+        MersenneTwister mt = new MersenneTwister(0);
+        int[] count = new int[256];
+        byte[] bytes = new byte[10];
+        for (int k = 0; k < 100000; ++k) {
+           mt.nextBytes(bytes);
+           for (byte b : bytes) {
+               ++count[b + 128];
+           }
+        }
+        int min = Integer.MAX_VALUE;
+        int max = Integer.MIN_VALUE;
+        for (int c : count) {
+            min = FastMath.min(min, c);
+            max = FastMath.max(max, c);
+        }
+        int expected = (100000 * bytes.length) / count.length;
+        assertTrue((expected - 200) < min);
+        assertTrue(max < (expected + 200));
+    }
+
+    @Test
+    public void testMakotoNishimura() {
+        MersenneTwister mt = new MersenneTwister(new int[] {0x123, 0x234, 0x345, 0x456});
+        long[] refInt = {
+            1067595299l,  955945823l,  477289528l, 4107218783l, 4228976476l, 3344332714l, 3355579695l,  227628506l,
+            810200273l, 2591290167l, 2560260675l, 3242736208l,  646746669l, 1479517882l, 4245472273l, 1143372638l,
+            3863670494l, 3221021970l, 1773610557l, 1138697238l, 1421897700l, 1269916527l, 2859934041l, 1764463362l,
+            3874892047l, 3965319921l,   72549643l, 2383988930l, 2600218693l, 3237492380l, 2792901476l,  725331109l,
+            605841842l,  271258942l,  715137098l, 3297999536l, 1322965544l, 4229579109l, 1395091102l, 3735697720l,
+            2101727825l, 3730287744l, 2950434330l, 1661921839l, 2895579582l, 2370511479l, 1004092106l, 2247096681l,
+            2111242379l, 3237345263l, 4082424759l,  219785033l, 2454039889l, 3709582971l,  835606218l, 2411949883l,
+            2735205030l,  756421180l, 2175209704l, 1873865952l, 2762534237l, 4161807854l, 3351099340l,  181129879l,
+            3269891896l,  776029799l, 2218161979l, 3001745796l, 1866825872l, 2133627728l,   34862734l, 1191934573l,
+            3102311354l, 2916517763l, 1012402762l, 2184831317l, 4257399449l, 2899497138l, 3818095062l, 3030756734l,
+            1282161629l,  420003642l, 2326421477l, 2741455717l, 1278020671l, 3744179621l,  271777016l, 2626330018l,
+            2560563991l, 3055977700l, 4233527566l, 1228397661l, 3595579322l, 1077915006l, 2395931898l, 1851927286l,
+            3013683506l, 1999971931l, 3006888962l, 1049781534l, 1488758959l, 3491776230l,  104418065l, 2448267297l,
+            3075614115l, 3872332600l,  891912190l, 3936547759l, 2269180963l, 2633455084l, 1047636807l, 2604612377l,
+            2709305729l, 1952216715l,  207593580l, 2849898034l,  670771757l, 2210471108l,  467711165l,  263046873l,
+            3569667915l, 1042291111l, 3863517079l, 1464270005l, 2758321352l, 3790799816l, 2301278724l, 3106281430l,
+            7974801l, 2792461636l,  555991332l,  621766759l, 1322453093l,  853629228l,  686962251l, 1455120532l,
+            957753161l, 1802033300l, 1021534190l, 3486047311l, 1902128914l, 3701138056l, 4176424663l, 1795608698l,
+            560858864l, 3737752754l, 3141170998l, 1553553385l, 3367807274l,  711546358l, 2475125503l,  262969859l,
+            251416325l, 2980076994l, 1806565895l,  969527843l, 3529327173l, 2736343040l, 2987196734l, 1649016367l,
+            2206175811l, 3048174801l, 3662503553l, 3138851612l, 2660143804l, 1663017612l, 1816683231l,  411916003l,
+            3887461314l, 2347044079l, 1015311755l, 1203592432l, 2170947766l, 2569420716l,  813872093l, 1105387678l,
+            1431142475l,  220570551l, 4243632715l, 4179591855l, 2607469131l, 3090613241l,  282341803l, 1734241730l,
+            1391822177l, 1001254810l,  827927915l, 1886687171l, 3935097347l, 2631788714l, 3905163266l,  110554195l,
+            2447955646l, 3717202975l, 3304793075l, 3739614479l, 3059127468l,  953919171l, 2590123714l, 1132511021l,
+            3795593679l, 2788030429l,  982155079l, 3472349556l,  859942552l, 2681007391l, 2299624053l,  647443547l,
+            233600422l,  608168955l, 3689327453l, 1849778220l, 1608438222l, 3968158357l, 2692977776l, 2851872572l,
+            246750393l, 3582818628l, 3329652309l, 4036366910l, 1012970930l,  950780808l, 3959768744l, 2538550045l,
+            191422718l, 2658142375l, 3276369011l, 2927737484l, 1234200027l, 1920815603l, 3536074689l, 1535612501l,
+            2184142071l, 3276955054l,  428488088l, 2378411984l, 4059769550l, 3913744741l, 2732139246l,   64369859l,
+            3755670074l,  842839565l, 2819894466l, 2414718973l, 1010060670l, 1839715346l, 2410311136l,  152774329l,
+            3485009480l, 4102101512l, 2852724304l,  879944024l, 1785007662l, 2748284463l, 1354768064l, 3267784736l,
+            2269127717l, 3001240761l, 3179796763l,  895723219l,  865924942l, 4291570937l,   89355264l, 1471026971l,
+            4114180745l, 3201939751l, 2867476999l, 2460866060l, 3603874571l, 2238880432l, 3308416168l, 2072246611l,
+            2755653839l, 3773737248l, 1709066580l, 4282731467l, 2746170170l, 2832568330l,  433439009l, 3175778732l,
+            26248366l, 2551382801l,  183214346l, 3893339516l, 1928168445l, 1337157619l, 3429096554l, 3275170900l,
+            1782047316l, 4264403756l, 1876594403l, 4289659572l, 3223834894l, 1728705513l, 4068244734l, 2867840287l,
+            1147798696l,  302879820l, 1730407747l, 1923824407l, 1180597908l, 1569786639l,  198796327l,  560793173l,
+            2107345620l, 2705990316l, 3448772106l, 3678374155l,  758635715l,  884524671l,  486356516l, 1774865603l,
+            3881226226l, 2635213607l, 1181121587l, 1508809820l, 3178988241l, 1594193633l, 1235154121l,  326117244l,
+            2304031425l,  937054774l, 2687415945l, 3192389340l, 2003740439l, 1823766188l, 2759543402l,   10067710l,
+            1533252662l, 4132494984l,   82378136l,  420615890l, 3467563163l,  541562091l, 3535949864l, 2277319197l,
+            3330822853l, 3215654174l, 4113831979l, 4204996991l, 2162248333l, 3255093522l, 2219088909l, 2978279037l,
+            255818579l, 2859348628l, 3097280311l, 2569721123l, 1861951120l, 2907080079l, 2719467166l,  998319094l,
+            2521935127l, 2404125338l,  259456032l, 2086860995l, 1839848496l, 1893547357l, 2527997525l, 1489393124l,
+            2860855349l,   76448234l, 2264934035l,  744914583l, 2586791259l, 1385380501l,   66529922l, 1819103258l,
+            1899300332l, 2098173828l, 1793831094l,  276463159l,  360132945l, 4178212058l,  595015228l,  177071838l,
+            2800080290l, 1573557746l, 1548998935l,  378454223l, 1460534296l, 1116274283l, 3112385063l, 3709761796l,
+            827999348l, 3580042847l, 1913901014l,  614021289l, 4278528023l, 1905177404l,   45407939l, 3298183234l,
+            1184848810l, 3644926330l, 3923635459l, 1627046213l, 3677876759l,  969772772l, 1160524753l, 1522441192l,
+            452369933l, 1527502551l,  832490847l, 1003299676l, 1071381111l, 2891255476l,  973747308l, 4086897108l,
+            1847554542l, 3895651598l, 2227820339l, 1621250941l, 2881344691l, 3583565821l, 3510404498l,  849362119l,
+            862871471l,  797858058l, 2867774932l, 2821282612l, 3272403146l, 3997979905l,  209178708l, 1805135652l,
+            6783381l, 2823361423l,  792580494l, 4263749770l,  776439581l, 3798193823l, 2853444094l, 2729507474l,
+            1071873341l, 1329010206l, 1289336450l, 3327680758l, 2011491779l,   80157208l,  922428856l, 1158943220l,
+            1667230961l, 2461022820l, 2608845159l,  387516115l, 3345351910l, 1495629111l, 4098154157l, 3156649613l,
+            3525698599l, 4134908037l,  446713264l, 2137537399l, 3617403512l,  813966752l, 1157943946l, 3734692965l,
+            1680301658l, 3180398473l, 3509854711l, 2228114612l, 1008102291l,  486805123l,  863791847l, 3189125290l,
+            1050308116l, 3777341526l, 4291726501l,  844061465l, 1347461791l, 2826481581l,  745465012l, 2055805750l,
+            4260209475l, 2386693097l, 2980646741l,  447229436l, 2077782664l, 1232942813l, 4023002732l, 1399011509l,
+            3140569849l, 2579909222l, 3794857471l,  900758066l, 2887199683l, 1720257997l, 3367494931l, 2668921229l,
+            955539029l, 3818726432l, 1105704962l, 3889207255l, 2277369307l, 2746484505l, 1761846513l, 2413916784l,
+            2685127085l, 4240257943l, 1166726899l, 4215215715l, 3082092067l, 3960461946l, 1663304043l, 2087473241l,
+            4162589986l, 2507310778l, 1579665506l,  767234210l,  970676017l,  492207530l, 1441679602l, 1314785090l,
+            3262202570l, 3417091742l, 1561989210l, 3011406780l, 1146609202l, 3262321040l, 1374872171l, 1634688712l,
+            1280458888l, 2230023982l,  419323804l, 3262899800l,   39783310l, 1641619040l, 1700368658l, 2207946628l,
+            2571300939l, 2424079766l,  780290914l, 2715195096l, 3390957695l,  163151474l, 2309534542l, 1860018424l,
+            555755123l,  280320104l, 1604831083l, 2713022383l, 1728987441l, 3639955502l,  623065489l, 3828630947l,
+            4275479050l, 3516347383l, 2343951195l, 2430677756l,  635534992l, 3868699749l,  808442435l, 3070644069l,
+            4282166003l, 2093181383l, 2023555632l, 1568662086l, 3422372620l, 4134522350l, 3016979543l, 3259320234l,
+            2888030729l, 3185253876l, 4258779643l, 1267304371l, 1022517473l,  815943045l,  929020012l, 2995251018l,
+            3371283296l, 3608029049l, 2018485115l,  122123397l, 2810669150l, 1411365618l, 1238391329l, 1186786476l,
+            3155969091l, 2242941310l, 1765554882l,  279121160l, 4279838515l, 1641578514l, 3796324015l,   13351065l,
+            103516986l, 1609694427l,  551411743l, 2493771609l, 1316337047l, 3932650856l, 4189700203l,  463397996l,
+            2937735066l, 1855616529l, 2626847990l,   55091862l, 3823351211l,  753448970l, 4045045500l, 1274127772l,
+            1124182256l,   92039808l, 2126345552l,  425973257l,  386287896l, 2589870191l, 1987762798l, 4084826973l,
+            2172456685l, 3366583455l, 3602966653l, 2378803535l, 2901764433l, 3716929006l, 3710159000l, 2653449155l,
+            3469742630l, 3096444476l, 3932564653l, 2595257433l,  318974657l, 3146202484l,  853571438l,  144400272l,
+            3768408841l,  782634401l, 2161109003l,  570039522l, 1886241521l,   14249488l, 2230804228l, 1604941699l,
+            3928713335l, 3921942509l, 2155806892l,  134366254l,  430507376l, 1924011722l,  276713377l,  196481886l,
+            3614810992l, 1610021185l, 1785757066l,  851346168l, 3761148643l, 2918835642l, 3364422385l, 3012284466l,
+            3735958851l, 2643153892l, 3778608231l, 1164289832l,  205853021l, 2876112231l, 3503398282l, 3078397001l,
+            3472037921l, 1748894853l, 2740861475l,  316056182l, 1660426908l,  168885906l,  956005527l, 3984354789l,
+            566521563l, 1001109523l, 1216710575l, 2952284757l, 3834433081l, 3842608301l, 2467352408l, 3974441264l,
+            3256601745l, 1409353924l, 1329904859l, 2307560293l, 3125217879l, 3622920184l, 3832785684l, 3882365951l,
+            2308537115l, 2659155028l, 1450441945l, 3532257603l, 3186324194l, 1225603425l, 1124246549l,  175808705l,
+            3009142319l, 2796710159l, 3651990107l,  160762750l, 1902254979l, 1698648476l, 1134980669l,  497144426l,
+            3302689335l, 4057485630l, 3603530763l, 4087252587l,  427812652l,  286876201l,  823134128l, 1627554964l,
+            3745564327l, 2589226092l, 4202024494l,   62878473l, 3275585894l, 3987124064l, 2791777159l, 1916869511l,
+            2585861905l, 1375038919l, 1403421920l,   60249114l, 3811870450l, 3021498009l, 2612993202l,  528933105l,
+            2757361321l, 3341402964l, 2621861700l,  273128190l, 4015252178l, 3094781002l, 1621621288l, 2337611177l,
+            1796718448l, 1258965619l, 4241913140l, 2138560392l, 3022190223l, 4174180924l,  450094611l, 3274724580l,
+            617150026l, 2704660665l, 1469700689l, 1341616587l,  356715071l, 1188789960l, 2278869135l, 1766569160l,
+            2795896635l,   57824704l, 2893496380l, 1235723989l, 1630694347l, 3927960522l,  428891364l, 1814070806l,
+            2287999787l, 4125941184l, 3968103889l, 3548724050l, 1025597707l, 1404281500l, 2002212197l,   92429143l,
+            2313943944l, 2403086080l, 3006180634l, 3561981764l, 1671860914l, 1768520622l, 1803542985l,  844848113l,
+            3006139921l, 1410888995l, 1157749833l, 2125704913l, 1789979528l, 1799263423l,  741157179l, 2405862309l,
+            767040434l, 2655241390l, 3663420179l, 2172009096l, 2511931187l, 1680542666l,  231857466l, 1154981000l,
+            157168255l, 1454112128l, 3505872099l, 1929775046l, 2309422350l, 2143329496l, 2960716902l,  407610648l,
+            2938108129l, 2581749599l,  538837155l, 2342628867l,  430543915l,  740188568l, 1937713272l, 3315215132l,
+            2085587024l, 4030765687l,  766054429l, 3517641839l,  689721775l, 1294158986l, 1753287754l, 4202601348l,
+            1974852792l,   33459103l, 3568087535l, 3144677435l, 1686130825l, 4134943013l, 3005738435l, 3599293386l,
+            426570142l,  754104406l, 3660892564l, 1964545167l,  829466833l,  821587464l, 1746693036l, 1006492428l,
+            1595312919l, 1256599985l, 1024482560l, 1897312280l, 2902903201l,  691790057l, 1037515867l, 3176831208l,
+            1968401055l, 2173506824l, 1089055278l, 1748401123l, 2941380082l,  968412354l, 1818753861l, 2973200866l,
+            3875951774l, 1119354008l, 3988604139l, 1647155589l, 2232450826l, 3486058011l, 3655784043l, 3759258462l,
+            847163678l, 1082052057l,  989516446l, 2871541755l, 3196311070l, 3929963078l,  658187585l, 3664944641l,
+            2175149170l, 2203709147l, 2756014689l, 2456473919l, 3890267390l, 1293787864l, 2830347984l, 3059280931l,
+            4158802520l, 1561677400l, 2586570938l,  783570352l, 1355506163l,   31495586l, 3789437343l, 3340549429l,
+            2092501630l,  896419368l,  671715824l, 3530450081l, 3603554138l, 1055991716l, 3442308219l, 1499434728l,
+            3130288473l, 3639507000l,   17769680l, 2259741420l,  487032199l, 4227143402l, 3693771256l, 1880482820l,
+            3924810796l,  381462353l, 4017855991l, 2452034943l, 2736680833l, 2209866385l, 2128986379l,  437874044l,
+            595759426l,  641721026l, 1636065708l, 3899136933l,  629879088l, 3591174506l,  351984326l, 2638783544l,
+            2348444281l, 2341604660l, 2123933692l,  143443325l, 1525942256l,  364660499l,  599149312l,  939093251l,
+            1523003209l,  106601097l,  376589484l, 1346282236l, 1297387043l,  764598052l, 3741218111l,  933457002l,
+            1886424424l, 3219631016l,  525405256l, 3014235619l,  323149677l, 2038881721l, 4100129043l, 2851715101l,
+            2984028078l, 1888574695l, 2014194741l, 3515193880l, 4180573530l, 3461824363l, 2641995497l, 3179230245l,
+            2902294983l, 2217320456l, 4040852155l, 1784656905l, 3311906931l,   87498458l, 2752971818l, 2635474297l,
+            2831215366l, 3682231106l, 2920043893l, 3772929704l, 2816374944l,  309949752l, 2383758854l,  154870719l,
+            385111597l, 1191604312l, 1840700563l,  872191186l, 2925548701l, 1310412747l, 2102066999l, 1504727249l,
+            3574298750l, 1191230036l, 3330575266l, 3180292097l, 3539347721l,  681369118l, 3305125752l, 3648233597l,
+            950049240l, 4173257693l, 1760124957l,  512151405l,  681175196l,  580563018l, 1169662867l, 4015033554l,
+            2687781101l,  699691603l, 2673494188l, 1137221356l,  123599888l,  472658308l, 1053598179l, 1012713758l,
+            3481064843l, 3759461013l, 3981457956l, 3830587662l, 1877191791l, 3650996736l,  988064871l, 3515461600l,
+            4089077232l, 2225147448l, 1249609188l, 2643151863l, 3896204135l, 2416995901l, 1397735321l, 3460025646l
+        };
+        double[] refDouble = {
+            0.76275443, 0.99000644, 0.98670464, 0.10143112, 0.27933125, 0.69867227, 0.94218740, 0.03427201,
+            0.78842173, 0.28180608, 0.92179002, 0.20785655, 0.54534773, 0.69644020, 0.38107718, 0.23978165,
+            0.65286910, 0.07514568, 0.22765211, 0.94872929, 0.74557914, 0.62664415, 0.54708246, 0.90959343,
+            0.42043116, 0.86334511, 0.19189126, 0.14718544, 0.70259889, 0.63426346, 0.77408121, 0.04531601,
+            0.04605807, 0.88595519, 0.69398270, 0.05377184, 0.61711170, 0.05565708, 0.10133577, 0.41500776,
+            0.91810699, 0.22320679, 0.23353705, 0.92871862, 0.98897234, 0.19786706, 0.80558809, 0.06961067,
+            0.55840445, 0.90479405, 0.63288060, 0.95009721, 0.54948447, 0.20645042, 0.45000959, 0.87050869,
+            0.70806991, 0.19406895, 0.79286390, 0.49332866, 0.78483914, 0.75145146, 0.12341941, 0.42030252,
+            0.16728160, 0.59906494, 0.37575460, 0.97815160, 0.39815952, 0.43595080, 0.04952478, 0.33917805,
+            0.76509902, 0.61034321, 0.90654701, 0.92915732, 0.85365931, 0.18812377, 0.65913428, 0.28814566,
+            0.59476081, 0.27835931, 0.60722542, 0.68310435, 0.69387186, 0.03699800, 0.65897714, 0.17527003,
+            0.02889304, 0.86777366, 0.12352068, 0.91439461, 0.32022990, 0.44445731, 0.34903686, 0.74639273,
+            0.65918367, 0.92492794, 0.31872642, 0.77749724, 0.85413832, 0.76385624, 0.32744211, 0.91326300,
+            0.27458185, 0.22190155, 0.19865383, 0.31227402, 0.85321225, 0.84243342, 0.78544200, 0.71854080,
+            0.92503892, 0.82703064, 0.88306297, 0.47284073, 0.70059042, 0.48003761, 0.38671694, 0.60465770,
+            0.41747204, 0.47163243, 0.72750808, 0.65830223, 0.10955369, 0.64215401, 0.23456345, 0.95944940,
+            0.72822249, 0.40888451, 0.69980355, 0.26677428, 0.57333635, 0.39791582, 0.85377858, 0.76962816,
+            0.72004885, 0.90903087, 0.51376506, 0.37732665, 0.12691640, 0.71249738, 0.81217908, 0.37037313,
+            0.32772374, 0.14238259, 0.05614811, 0.74363008, 0.39773267, 0.94859135, 0.31452454, 0.11730313,
+            0.62962618, 0.33334237, 0.45547255, 0.10089665, 0.56550662, 0.60539371, 0.16027624, 0.13245301,
+            0.60959939, 0.04671662, 0.99356286, 0.57660859, 0.40269560, 0.45274629, 0.06699735, 0.85064246,
+            0.87742744, 0.54508392, 0.87242982, 0.29321385, 0.67660627, 0.68230715, 0.79052073, 0.48592054,
+            0.25186266, 0.93769755, 0.28565487, 0.47219067, 0.99054882, 0.13155240, 0.47110470, 0.98556600,
+            0.84397623, 0.12875246, 0.90953202, 0.49129015, 0.23792727, 0.79481194, 0.44337770, 0.96564297,
+            0.67749118, 0.55684872, 0.27286897, 0.79538393, 0.61965356, 0.22487929, 0.02226018, 0.49248200,
+            0.42247006, 0.91797788, 0.99250134, 0.23449967, 0.52531508, 0.10246337, 0.78685622, 0.34310922,
+            0.89892996, 0.40454552, 0.68608407, 0.30752487, 0.83601319, 0.54956031, 0.63777550, 0.82199797,
+            0.24890696, 0.48801123, 0.48661910, 0.51223987, 0.32969635, 0.31075073, 0.21393155, 0.73453207,
+            0.15565705, 0.58584522, 0.28976728, 0.97621478, 0.61498701, 0.23891470, 0.28518540, 0.46809591,
+            0.18371914, 0.37597910, 0.13492176, 0.66849449, 0.82811466, 0.56240330, 0.37548956, 0.27562998,
+            0.27521910, 0.74096121, 0.77176757, 0.13748143, 0.99747138, 0.92504502, 0.09175241, 0.21389176,
+            0.21766512, 0.31183245, 0.23271221, 0.21207367, 0.57903312, 0.77523344, 0.13242613, 0.31037988,
+            0.01204835, 0.71652949, 0.84487594, 0.14982178, 0.57423142, 0.45677888, 0.48420169, 0.53465428,
+            0.52667473, 0.46880526, 0.49849733, 0.05670710, 0.79022476, 0.03872047, 0.21697212, 0.20443086,
+            0.28949326, 0.81678186, 0.87629474, 0.92297064, 0.27373097, 0.84625273, 0.51505586, 0.00582792,
+            0.33295971, 0.91848412, 0.92537226, 0.91760033, 0.07541125, 0.71745848, 0.61158698, 0.00941650,
+            0.03135554, 0.71527471, 0.24821915, 0.63636652, 0.86159918, 0.26450229, 0.60160194, 0.35557725,
+            0.24477500, 0.07186456, 0.51757096, 0.62120362, 0.97981062, 0.69954667, 0.21065616, 0.13382753,
+            0.27693186, 0.59644095, 0.71500764, 0.04110751, 0.95730081, 0.91600724, 0.47704678, 0.26183479,
+            0.34706971, 0.07545431, 0.29398385, 0.93236070, 0.60486023, 0.48015011, 0.08870451, 0.45548581,
+            0.91872718, 0.38142712, 0.10668643, 0.01397541, 0.04520355, 0.93822273, 0.18011940, 0.57577277,
+            0.91427606, 0.30911399, 0.95853475, 0.23611214, 0.69619891, 0.69601980, 0.76765372, 0.58515930,
+            0.49479057, 0.11288752, 0.97187699, 0.32095365, 0.57563608, 0.40760618, 0.78703383, 0.43261152,
+            0.90877651, 0.84686346, 0.10599030, 0.72872803, 0.19315490, 0.66152912, 0.10210518, 0.06257876,
+            0.47950688, 0.47062066, 0.72701157, 0.48915116, 0.66110261, 0.60170685, 0.24516994, 0.12726050,
+            0.03451185, 0.90864994, 0.83494878, 0.94800035, 0.91035206, 0.14480751, 0.88458997, 0.53498312,
+            0.15963215, 0.55378627, 0.35171349, 0.28719791, 0.09097957, 0.00667896, 0.32309622, 0.87561479,
+            0.42534520, 0.91748977, 0.73908457, 0.41793223, 0.99279792, 0.87908370, 0.28458072, 0.59132853,
+            0.98672190, 0.28547393, 0.09452165, 0.89910674, 0.53681109, 0.37931425, 0.62683489, 0.56609740,
+            0.24801549, 0.52948179, 0.98328855, 0.66403523, 0.55523786, 0.75886666, 0.84784685, 0.86829981,
+            0.71448906, 0.84670080, 0.43922919, 0.20771016, 0.64157936, 0.25664246, 0.73055695, 0.86395782,
+            0.65852932, 0.99061803, 0.40280575, 0.39146298, 0.07291005, 0.97200603, 0.20555729, 0.59616495,
+            0.08138254, 0.45796388, 0.33681125, 0.33989127, 0.18717090, 0.53545811, 0.60550838, 0.86520709,
+            0.34290701, 0.72743276, 0.73023855, 0.34195926, 0.65019733, 0.02765254, 0.72575740, 0.32709576,
+            0.03420866, 0.26061893, 0.56997511, 0.28439072, 0.84422744, 0.77637570, 0.55982168, 0.06720327,
+            0.58449067, 0.71657369, 0.15819609, 0.58042821, 0.07947911, 0.40193792, 0.11376012, 0.88762938,
+            0.67532159, 0.71223735, 0.27829114, 0.04806073, 0.21144026, 0.58830274, 0.04140071, 0.43215628,
+            0.12952729, 0.94668759, 0.87391019, 0.98382450, 0.27750768, 0.90849647, 0.90962737, 0.59269720,
+            0.96102026, 0.49544979, 0.32007095, 0.62585546, 0.03119821, 0.85953001, 0.22017528, 0.05834068,
+            0.80731217, 0.53799961, 0.74166948, 0.77426600, 0.43938444, 0.54862081, 0.58575513, 0.15886492,
+            0.73214332, 0.11649057, 0.77463977, 0.85788827, 0.17061997, 0.66838056, 0.96076133, 0.07949296,
+            0.68521946, 0.89986254, 0.05667410, 0.12741385, 0.83470977, 0.63969104, 0.46612929, 0.10200126,
+            0.01194925, 0.10476340, 0.90285217, 0.31221221, 0.32980614, 0.46041971, 0.52024973, 0.05425470,
+            0.28330912, 0.60426543, 0.00598243, 0.97244013, 0.21135841, 0.78561597, 0.78428734, 0.63422849,
+            0.32909934, 0.44771136, 0.27380750, 0.14966697, 0.18156268, 0.65686758, 0.28726350, 0.97074787,
+            0.63676171, 0.96649494, 0.24526295, 0.08297372, 0.54257548, 0.03166785, 0.33735355, 0.15946671,
+            0.02102971, 0.46228045, 0.11892296, 0.33408336, 0.29875681, 0.29847692, 0.73767569, 0.02080745,
+            0.62980060, 0.08082293, 0.22993106, 0.25031439, 0.87787525, 0.45150053, 0.13673441, 0.63407612,
+            0.97907688, 0.52241942, 0.50580158, 0.06273902, 0.05270283, 0.77031811, 0.05113352, 0.24393329,
+            0.75036441, 0.37436336, 0.22877652, 0.59975358, 0.85707591, 0.88691457, 0.85547165, 0.36641027,
+            0.58720133, 0.45462835, 0.09243817, 0.32981586, 0.07820411, 0.25421519, 0.36004706, 0.60092307,
+            0.46192412, 0.36758683, 0.98424170, 0.08019934, 0.68594024, 0.45826386, 0.29962317, 0.79365413,
+            0.89231296, 0.49478547, 0.87645944, 0.23590734, 0.28106737, 0.75026285, 0.08136314, 0.79582424,
+            0.76010628, 0.82792971, 0.27947652, 0.72482861, 0.82191216, 0.46171689, 0.79189752, 0.96043686,
+            0.51609668, 0.88995725, 0.28998963, 0.55191845, 0.03934737, 0.83033700, 0.49553013, 0.98009549,
+            0.19017594, 0.98347750, 0.33452066, 0.87144372, 0.72106301, 0.71272114, 0.71465963, 0.88361677,
+            0.85571283, 0.73782329, 0.20920458, 0.34855153, 0.46766817, 0.02780062, 0.74898344, 0.03680650,
+            0.44866557, 0.77426312, 0.91025891, 0.25195236, 0.87319953, 0.63265037, 0.25552148, 0.27422476,
+            0.95217406, 0.39281839, 0.66441573, 0.09158900, 0.94515992, 0.07800798, 0.02507888, 0.39901462,
+            0.17382573, 0.12141278, 0.85502334, 0.19902911, 0.02160210, 0.44460522, 0.14688742, 0.68020336,
+            0.71323733, 0.60922473, 0.95400380, 0.99611159, 0.90897777, 0.41073520, 0.66206647, 0.32064685,
+            0.62805003, 0.50677209, 0.52690101, 0.87473387, 0.73918362, 0.39826974, 0.43683919, 0.80459118,
+            0.32422684, 0.01958019, 0.95319576, 0.98326137, 0.83931735, 0.69060863, 0.33671416, 0.68062550,
+            0.65152380, 0.33392969, 0.03451730, 0.95227244, 0.68200635, 0.85074171, 0.64721009, 0.51234433,
+            0.73402047, 0.00969637, 0.93835057, 0.80803854, 0.31485260, 0.20089527, 0.01323282, 0.59933780,
+            0.31584602, 0.20209563, 0.33754800, 0.68604181, 0.24443049, 0.19952227, 0.78162632, 0.10336988,
+            0.11360736, 0.23536740, 0.23262256, 0.67803776, 0.48749791, 0.74658435, 0.92156640, 0.56706407,
+            0.36683221, 0.99157136, 0.23421374, 0.45183767, 0.91609720, 0.85573315, 0.37706276, 0.77042618,
+            0.30891908, 0.40709595, 0.06944866, 0.61342849, 0.88817388, 0.58734506, 0.98711323, 0.14744128,
+            0.63242656, 0.87704136, 0.68347125, 0.84446569, 0.43265239, 0.25146321, 0.04130111, 0.34259839,
+            0.92697368, 0.40878778, 0.56990338, 0.76204273, 0.19820348, 0.66314909, 0.02482844, 0.06669207,
+            0.50205581, 0.26084093, 0.65139159, 0.41650223, 0.09733904, 0.56344203, 0.62651696, 0.67332139,
+            0.58037374, 0.47258086, 0.21010758, 0.05713135, 0.89390629, 0.10781246, 0.32037450, 0.07628388,
+            0.34227964, 0.42190597, 0.58201860, 0.77363549, 0.49595133, 0.86031236, 0.83906769, 0.81098161,
+            0.26694195, 0.14215941, 0.88210306, 0.53634237, 0.12090720, 0.82480459, 0.75930318, 0.31847147,
+            0.92768077, 0.01037616, 0.56201727, 0.88107122, 0.35925856, 0.85860762, 0.61109408, 0.70408301,
+            0.58434977, 0.92192494, 0.62667915, 0.75988365, 0.06858761, 0.36156496, 0.58057195, 0.13636150,
+            0.57719713, 0.59340255, 0.63530602, 0.22976282, 0.71915530, 0.41162531, 0.63979565, 0.09931342,
+            0.79344045, 0.10893790, 0.84450224, 0.23122236, 0.99485593, 0.73637397, 0.17276368, 0.13357764,
+            0.74965804, 0.64991737, 0.61990341, 0.41523170, 0.05878239, 0.05687301, 0.05497131, 0.42868366,
+            0.42571090, 0.25810502, 0.89642955, 0.30439758, 0.39310223, 0.11357431, 0.04288255, 0.23397550,
+            0.11200634, 0.85621396, 0.89733974, 0.37508865, 0.42077265, 0.68597384, 0.72781399, 0.19296476,
+            0.61699087, 0.31667128, 0.67756410, 0.00177323, 0.05725176, 0.79474693, 0.18885238, 0.06724856,
+            0.68193156, 0.42202167, 0.22082041, 0.28554673, 0.64995708, 0.87851940, 0.29124547, 0.61009521,
+            0.87374537, 0.05743712, 0.69902994, 0.81925115, 0.45653873, 0.37236821, 0.31118709, 0.52734307,
+            0.39672836, 0.38185294, 0.30163915, 0.17374510, 0.04913278, 0.90404879, 0.25742801, 0.58266467,
+            0.97663209, 0.79823377, 0.36437958, 0.15206043, 0.26529938, 0.22690047, 0.05839021, 0.84721160,
+            0.18622435, 0.37809403, 0.55706977, 0.49828704, 0.47659049, 0.24289680, 0.88477595, 0.07807463,
+            0.56245739, 0.73490635, 0.21099431, 0.13164942, 0.75840044, 0.66877037, 0.28988183, 0.44046090,
+            0.24967434, 0.80048356, 0.26029740, 0.30416821, 0.64151867, 0.52067892, 0.12880774, 0.85465381,
+            0.02690525, 0.19149288, 0.49630295, 0.79682619, 0.43566145, 0.00288078, 0.81484193, 0.03763639,
+            0.68529083, 0.01339574, 0.38405386, 0.30537067, 0.22994703, 0.44000045, 0.27217985, 0.53831243,
+            0.02870435, 0.86282045, 0.61831306, 0.09164956, 0.25609707, 0.07445781, 0.72185784, 0.90058883,
+            0.30070608, 0.94476583, 0.56822213, 0.21933909, 0.96772793, 0.80063440, 0.26307906, 0.31183306,
+            0.16501252, 0.55436179, 0.68562285, 0.23829083, 0.86511559, 0.57868991, 0.81888344, 0.20126869,
+            0.93172350, 0.66028129, 0.21786948, 0.78515828, 0.10262106, 0.35390326, 0.79303876, 0.63427924,
+            0.90479631, 0.31024934, 0.60635447, 0.56198079, 0.63573813, 0.91854197, 0.99701497, 0.83085849,
+            0.31692291, 0.01925964, 0.97446405, 0.98751283, 0.60944293, 0.13751018, 0.69519957, 0.68956636,
+            0.56969015, 0.46440193, 0.88341765, 0.36754434, 0.89223647, 0.39786427, 0.85055280, 0.12749961,
+            0.79452122, 0.89449784, 0.14567830, 0.45716830, 0.74822309, 0.28200437, 0.42546044, 0.17464886,
+            0.68308746, 0.65496587, 0.52935411, 0.12736159, 0.61523955, 0.81590528, 0.63107864, 0.39786553,
+            0.20102294, 0.53292914, 0.75485590, 0.59847044, 0.32861691, 0.12125866, 0.58917183, 0.07638293,
+            0.86845380, 0.29192617, 0.03989733, 0.52180460, 0.32503407, 0.64071852, 0.69516575, 0.74254998,
+            0.54587026, 0.48713246, 0.32920155, 0.08719954, 0.63497059, 0.54328459, 0.64178757, 0.45583809,
+            0.70694291, 0.85212760, 0.86074305, 0.33163422, 0.85739792, 0.59908488, 0.74566046, 0.72157152
+        };
+
+        for (int i = 0; i < refInt.length; ++i) {
+            int r = mt.nextInt();
+            assertEquals(refInt[i], (r & 0x7fffffffl) | ((r < 0) ? 0x80000000l : 0x0l));
+        }
+
+        for (int i = 0; i < refDouble.length; ++i) {
+            int r = mt.nextInt();
+            assertEquals(refDouble[i],
+                         ((r & 0x7fffffffl) | ((r < 0) ? 0x80000000l : 0x0l)) / 4294967296.0,
+                         1.0e-8);
+        }
+
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/random/RandomAdaptorTest.java b/src/test/java/org/apache/commons/math/random/RandomAdaptorTest.java
new file mode 100644
index 0000000..d4e9305
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/random/RandomAdaptorTest.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.random;
+
+import java.util.Random;
+
+/**
+ * Test cases for the RandomAdaptor class
+ *
+ * @version $Revision: 902201 $ $Date: 2010-01-22 19:18:16 +0100 (ven. 22 janv. 2010) $
+ */
+
+public class RandomAdaptorTest extends RandomDataTest {
+
+    public RandomAdaptorTest(String name) {
+        super(name);
+    }
+
+    public void testAdaptor() {
+        ConstantGenerator generator = new ConstantGenerator();
+        Random random = RandomAdaptor.createAdaptor(generator);
+        checkConstant(random);
+        RandomAdaptor randomAdaptor = new RandomAdaptor(generator);
+        checkConstant(randomAdaptor);
+    }
+
+    private void checkConstant(Random random) {
+        byte[] bytes = new byte[] {0};
+        random.nextBytes(bytes);
+        assertEquals(0, bytes[0]);
+        assertEquals(false, random.nextBoolean());
+        assertEquals(0, random.nextDouble(), 0);
+        assertEquals(0, random.nextFloat(), 0);
+        assertEquals(0, random.nextGaussian(), 0);
+        assertEquals(0, random.nextInt());
+        assertEquals(0, random.nextInt(1));
+        assertEquals(0, random.nextLong());
+        random.setSeed(100);
+        assertEquals(0, random.nextDouble(), 0);
+    }
+
+    /*
+     * "Constant" generator to test Adaptor delegation.
+     * "Powered by Eclipse ;-)"
+     *
+     */
+    private static class ConstantGenerator implements RandomGenerator {
+
+        public boolean nextBoolean() {
+            return false;
+        }
+
+        public void nextBytes(byte[] bytes) {
+        }
+
+        public double nextDouble() {
+            return 0;
+        }
+
+        public float nextFloat() {
+            return 0;
+        }
+
+        public double nextGaussian() {
+            return 0;
+        }
+
+        public int nextInt() {
+            return 0;
+        }
+
+        public int nextInt(int n) {
+            return 0;
+        }
+
+        public long nextLong() {
+            return 0;
+        }
+
+        public void setSeed(int seed) {
+        }
+
+        public void setSeed(int[] seed) {
+        }
+
+        public void setSeed(long seed) {
+        }
+
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/random/RandomDataTest.java b/src/test/java/org/apache/commons/math/random/RandomDataTest.java
new file mode 100644
index 0000000..d117021
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/random/RandomDataTest.java
@@ -0,0 +1,990 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+
+import junit.framework.AssertionFailedError;
+
+import org.apache.commons.math.RetryTestCase;
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.distribution.BetaDistributionImpl;
+import org.apache.commons.math.distribution.BinomialDistributionImpl;
+import org.apache.commons.math.distribution.BinomialDistributionTest;
+import org.apache.commons.math.distribution.CauchyDistributionImpl;
+import org.apache.commons.math.distribution.ChiSquaredDistributionImpl;
+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.HypergeometricDistributionTest;
+import org.apache.commons.math.distribution.PascalDistributionImpl;
+import org.apache.commons.math.distribution.PascalDistributionTest;
+import org.apache.commons.math.distribution.PoissonDistribution;
+import org.apache.commons.math.distribution.PoissonDistributionImpl;
+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.distribution.ZipfDistributionTest;
+import org.apache.commons.math.stat.Frequency;
+import org.apache.commons.math.stat.descriptive.SummaryStatistics;
+import org.apache.commons.math.stat.inference.ChiSquareTest;
+import org.apache.commons.math.stat.inference.ChiSquareTestImpl;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Test cases for the RandomData class.
+ *
+ * @version $Revision: 1042376 $ $Date: 2009-04-05 11:55:59 -0500 (Sun, 05 Apr
+ *          2009) $
+ */
+
+public class RandomDataTest extends RetryTestCase {
+
+    public RandomDataTest(String name) {
+        super(name);
+        randomData = new RandomDataImpl();
+    }
+
+    protected final long smallSampleSize = 1000;
+    protected final double[] expected = { 250, 250, 250, 250 };
+    protected final int largeSampleSize = 10000;
+    private final String[] hex = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
+            "a", "b", "c", "d", "e", "f" };
+    protected RandomDataImpl randomData = null;
+    protected final ChiSquareTestImpl testStatistic = new ChiSquareTestImpl();
+
+    public void testNextIntExtremeValues() {
+        int x = randomData.nextInt(Integer.MIN_VALUE, Integer.MAX_VALUE);
+        int y = randomData.nextInt(Integer.MIN_VALUE, Integer.MAX_VALUE);
+        assertFalse(x == y);
+    }
+
+    public void testNextLongExtremeValues() {
+        long x = randomData.nextLong(Long.MIN_VALUE, Long.MAX_VALUE);
+        long y = randomData.nextLong(Long.MIN_VALUE, Long.MAX_VALUE);
+        assertFalse(x == y);
+    }
+
+    /** test dispersion and failure modes for nextInt() */
+    public void testNextInt() {
+        try {
+            randomData.nextInt(4, 3);
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+        Frequency freq = new Frequency();
+        int value = 0;
+        for (int i = 0; i < smallSampleSize; i++) {
+            value = randomData.nextInt(0, 3);
+            assertTrue("nextInt range", (value >= 0) && (value <= 3));
+            freq.addValue(value);
+        }
+        long[] observed = new long[4];
+        for (int i = 0; i < 4; i++) {
+            observed[i] = freq.getCount(i);
+        }
+
+        /*
+         * Use ChiSquare dist with df = 4-1 = 3, alpha = .001 Change to 11.34
+         * for alpha = .01
+         */
+        assertTrue("chi-square test -- will fail about 1 in 1000 times",
+                testStatistic.chiSquare(expected, observed) < 16.27);
+    }
+
+    /** test dispersion and failure modes for nextLong() */
+    public void testNextLong() {
+        try {
+            randomData.nextLong(4, 3);
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+        Frequency freq = new Frequency();
+        long value = 0;
+        for (int i = 0; i < smallSampleSize; i++) {
+            value = randomData.nextLong(0, 3);
+            assertTrue("nextInt range", (value >= 0) && (value <= 3));
+            freq.addValue(value);
+        }
+        long[] observed = new long[4];
+        for (int i = 0; i < 4; i++) {
+            observed[i] = freq.getCount(i);
+        }
+
+        /*
+         * Use ChiSquare dist with df = 4-1 = 3, alpha = .001 Change to 11.34
+         * for alpha = .01
+         */
+        assertTrue("chi-square test -- will fail about 1 in 1000 times",
+                testStatistic.chiSquare(expected, observed) < 16.27);
+    }
+
+    /** test dispersion and failure modes for nextSecureLong() */
+    public void testNextSecureLong() {
+        try {
+            randomData.nextSecureLong(4, 3);
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+        Frequency freq = new Frequency();
+        long value = 0;
+        for (int i = 0; i < smallSampleSize; i++) {
+            value = randomData.nextSecureLong(0, 3);
+            assertTrue("nextInt range", (value >= 0) && (value <= 3));
+            freq.addValue(value);
+        }
+        long[] observed = new long[4];
+        for (int i = 0; i < 4; i++) {
+            observed[i] = freq.getCount(i);
+        }
+
+        /*
+         * Use ChiSquare dist with df = 4-1 = 3, alpha = .001 Change to 11.34
+         * for alpha = .01
+         */
+        assertTrue("chi-square test -- will fail about 1 in 1000 times",
+                testStatistic.chiSquare(expected, observed) < 16.27);
+    }
+
+    /** test dispersion and failure modes for nextSecureInt() */
+    public void testNextSecureInt() {
+        try {
+            randomData.nextSecureInt(4, 3);
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+        Frequency freq = new Frequency();
+        int value = 0;
+        for (int i = 0; i < smallSampleSize; i++) {
+            value = randomData.nextSecureInt(0, 3);
+            assertTrue("nextInt range", (value >= 0) && (value <= 3));
+            freq.addValue(value);
+        }
+        long[] observed = new long[4];
+        for (int i = 0; i < 4; i++) {
+            observed[i] = freq.getCount(i);
+        }
+
+        /*
+         * Use ChiSquare dist with df = 4-1 = 3, alpha = .001 Change to 11.34
+         * for alpha = .01
+         */
+        assertTrue("chi-square test -- will fail about 1 in 1000 times",
+                testStatistic.chiSquare(expected, observed) < 16.27);
+    }
+
+    /**
+     * Make sure that empirical distribution of random Poisson(4)'s has P(X <=
+     * 5) close to actual cumulative Poisson probability and that nextPoisson
+     * fails when mean is non-positive TODO: replace with statistical test,
+     * adding test stat to TestStatistic
+     */
+    public void testNextPoisson() {
+        try {
+            randomData.nextPoisson(0);
+            fail("zero mean -- expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+        Frequency f = new Frequency();
+        for (int i = 0; i < largeSampleSize; i++) {
+            f.addValue(randomData.nextPoisson(4.0d));
+        }
+        long cumFreq = f.getCount(0) + f.getCount(1) + f.getCount(2)
+                + f.getCount(3) + f.getCount(4) + f.getCount(5);
+        long sumFreq = f.getSumFreq();
+        double cumPct = Double.valueOf(cumFreq).doubleValue()
+                / Double.valueOf(sumFreq).doubleValue();
+        assertEquals("cum Poisson(4)", cumPct, 0.7851, 0.2);
+        try {
+            randomData.nextPoisson(-1);
+            fail("negative mean supplied -- IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+        try {
+            randomData.nextPoisson(0);
+            fail("0 mean supplied -- IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+
+    }
+
+    public void testNextPoissonConsistency() throws Exception {
+        
+        // Reseed randomGenerator to get fixed sequence
+        randomData.reSeed(1000);  
+        
+        // Small integral means
+        for (int i = 1; i < 100; i++) {
+            checkNextPoissonConsistency(i);
+        }
+        // non-integer means
+        for (int i = 1; i < 10; i++) {
+            checkNextPoissonConsistency(randomData.nextUniform(1, 1000));
+        }
+        // large means
+        // TODO: When MATH-282 is resolved, s/3000/10000 below
+        for (int i = 1; i < 10; i++) {
+            checkNextPoissonConsistency(randomData.nextUniform(1000, 3000));
+        }
+    }
+
+    /**
+     * Verifies that nextPoisson(mean) generates an empirical distribution of values
+     * consistent with PoissonDistributionImpl by generating 1000 values, computing a
+     * grouped frequency distribution of the observed values and comparing this distribution
+     * to the corresponding expected distribution computed using PoissonDistributionImpl.
+     * Uses ChiSquare test of goodness of fit to evaluate the null hypothesis that the
+     * distributions are the same. If the null hypothesis can be rejected with confidence
+     * 1 - alpha, the check fails.
+     */
+    public void checkNextPoissonConsistency(double mean) throws Exception {
+        // Generate sample values
+        final int sampleSize = 1000;        // Number of deviates to generate
+        final int minExpectedCount = 7;     // Minimum size of expected bin count
+        long maxObservedValue = 0;
+        final double alpha = 0.001;         // Probability of false failure
+        Frequency frequency = new Frequency();
+        for (int i = 0; i < sampleSize; i++) {
+            long value = randomData.nextPoisson(mean);
+            if (value > maxObservedValue) {
+                maxObservedValue = value;
+            }
+            frequency.addValue(value);
+        }
+
+        /*
+         *  Set up bins for chi-square test.
+         *  Ensure expected counts are all at least minExpectedCount.
+         *  Start with upper and lower tail bins.
+         *  Lower bin = [0, lower); Upper bin = [upper, +inf).
+         */
+        PoissonDistribution poissonDistribution = new PoissonDistributionImpl(mean);
+        int lower = 1;
+        while (poissonDistribution.cumulativeProbability(lower - 1) * sampleSize < minExpectedCount) {
+            lower++;
+        }
+        int upper = (int) (5 * mean);  // Even for mean = 1, not much mass beyond 5
+        while ((1 - poissonDistribution.cumulativeProbability(upper - 1)) * sampleSize < minExpectedCount) {
+            upper--;
+        }
+
+        // Set bin width for interior bins.  For poisson, only need to look at end bins.
+        int binWidth = 1;
+        boolean widthSufficient = false;
+        double lowerBinMass = 0;
+        double upperBinMass = 0;
+        while (!widthSufficient) {
+            lowerBinMass = poissonDistribution.cumulativeProbability(lower, lower + binWidth - 1);
+            upperBinMass = poissonDistribution.cumulativeProbability(upper - binWidth + 1, upper);
+            widthSufficient = FastMath.min(lowerBinMass, upperBinMass) * sampleSize >= minExpectedCount;
+            binWidth++;
+        }
+
+        /*
+         *  Determine interior bin bounds.  Bins are
+         *  [1, lower = binBounds[0]), [lower, binBounds[1]), [binBounds[1], binBounds[2]), ... ,
+         *    [binBounds[binCount - 2], upper = binBounds[binCount - 1]), [upper, +inf)
+         *
+         */
+        List<Integer> binBounds = new ArrayList<Integer>();
+        binBounds.add(lower);
+        int bound = lower + binWidth;
+        while (bound < upper - binWidth) {
+            binBounds.add(bound);
+            bound += binWidth;
+        }
+        binBounds.add(bound);
+        binBounds.add(upper);
+
+        // Compute observed and expected bin counts
+        final int binCount = binBounds.size() + 1;
+        long[] observed = new long[binCount];
+        double[] expected = new double[binCount];
+
+        // Bottom bin
+        observed[0] = 0;
+        for (int i = 0; i < lower; i++) {
+            observed[0] += frequency.getCount(i);
+        }
+        expected[0] = poissonDistribution.cumulativeProbability(lower - 1) * sampleSize;
+
+        // Top bin
+        observed[binCount - 1] = 0;
+        for (int i = upper; i <= maxObservedValue; i++) {
+            observed[binCount - 1] += frequency.getCount(i);
+        }
+        expected[binCount - 1] = (1 - poissonDistribution.cumulativeProbability(upper - 1)) * sampleSize;
+
+        // Interior bins
+        for (int i = 1; i < binCount - 1; i++) {
+            observed[i] = 0;
+            for (int j = binBounds.get(i - 1); j < binBounds.get(i); j++) {
+                observed[i] += frequency.getCount(j);
+            } // Expected count is (mass in [binBounds[i], binBounds[i+1])) * sampleSize
+            expected[i] = (poissonDistribution.cumulativeProbability(binBounds.get(i) - 1) -
+                poissonDistribution.cumulativeProbability(binBounds.get(i - 1) -1)) * sampleSize;
+        }
+
+        // Use chisquare test to verify that generated values are poisson(mean)-distributed
+        ChiSquareTest chiSquareTest = new ChiSquareTestImpl();
+        try {
+            // Fail if we can reject null hypothesis that distributions are the same
+            assertFalse(chiSquareTest.chiSquareTest(expected, observed, alpha));
+        } catch (AssertionFailedError ex) {
+            StringBuilder msgBuffer = new StringBuilder();
+            DecimalFormat df = new DecimalFormat("#.##");
+            msgBuffer.append("Chisquare test failed for mean = ");
+            msgBuffer.append(mean);
+            msgBuffer.append(" p-value = ");
+            msgBuffer.append(chiSquareTest.chiSquareTest(expected, observed));
+            msgBuffer.append(" chisquare statistic = ");
+            msgBuffer.append(chiSquareTest.chiSquare(expected, observed));
+            msgBuffer.append(". \n");
+            msgBuffer.append("bin\t\texpected\tobserved\n");
+            for (int i = 0; i < expected.length; i++) {
+                msgBuffer.append("[");
+                msgBuffer.append(i == 0 ? 1: binBounds.get(i - 1));
+                msgBuffer.append(",");
+                msgBuffer.append(i == binBounds.size() ? "inf": binBounds.get(i));
+                msgBuffer.append(")");
+                msgBuffer.append("\t\t");
+                msgBuffer.append(df.format(expected[i]));
+                msgBuffer.append("\t\t");
+                msgBuffer.append(observed[i]);
+                msgBuffer.append("\n");
+            }
+            msgBuffer.append("This test can fail randomly due to sampling error with probability ");
+            msgBuffer.append(alpha);
+            msgBuffer.append(".");
+            fail(msgBuffer.toString());
+        }
+    }
+
+    /** test dispersion and failure modes for nextHex() */
+    public void testNextHex() {
+        try {
+            randomData.nextHexString(-1);
+            fail("negative length supplied -- IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+        try {
+            randomData.nextHexString(0);
+            fail("zero length supplied -- IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+        String hexString = randomData.nextHexString(3);
+        if (hexString.length() != 3) {
+            fail("incorrect length for generated string");
+        }
+        hexString = randomData.nextHexString(1);
+        if (hexString.length() != 1) {
+            fail("incorrect length for generated string");
+        }
+        try {
+            hexString = randomData.nextHexString(0);
+            fail("zero length requested -- expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+        if (hexString.length() != 1) {
+            fail("incorrect length for generated string");
+        }
+        Frequency f = new Frequency();
+        for (int i = 0; i < smallSampleSize; i++) {
+            hexString = randomData.nextHexString(100);
+            if (hexString.length() != 100) {
+                fail("incorrect length for generated string");
+            }
+            for (int j = 0; j < hexString.length(); j++) {
+                f.addValue(hexString.substring(j, j + 1));
+            }
+        }
+        double[] expected = new double[16];
+        long[] observed = new long[16];
+        for (int i = 0; i < 16; i++) {
+            expected[i] = (double) smallSampleSize * 100 / 16;
+            observed[i] = f.getCount(hex[i]);
+        }
+        /*
+         * Use ChiSquare dist with df = 16-1 = 15, alpha = .001 Change to 30.58
+         * for alpha = .01
+         */
+        assertTrue("chi-square test -- will fail about 1 in 1000 times",
+                testStatistic.chiSquare(expected, observed) < 37.70);
+    }
+
+    /** test dispersion and failure modes for nextHex() */
+    public void testNextSecureHex() {
+        try {
+            randomData.nextSecureHexString(-1);
+            fail("negative length -- IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+        try {
+            randomData.nextSecureHexString(0);
+            fail("zero length -- IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+        String hexString = randomData.nextSecureHexString(3);
+        if (hexString.length() != 3) {
+            fail("incorrect length for generated string");
+        }
+        hexString = randomData.nextSecureHexString(1);
+        if (hexString.length() != 1) {
+            fail("incorrect length for generated string");
+        }
+        try {
+            hexString = randomData.nextSecureHexString(0);
+            fail("zero length requested -- expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+        if (hexString.length() != 1) {
+            fail("incorrect length for generated string");
+        }
+        Frequency f = new Frequency();
+        for (int i = 0; i < smallSampleSize; i++) {
+            hexString = randomData.nextSecureHexString(100);
+            if (hexString.length() != 100) {
+                fail("incorrect length for generated string");
+            }
+            for (int j = 0; j < hexString.length(); j++) {
+                f.addValue(hexString.substring(j, j + 1));
+            }
+        }
+        double[] expected = new double[16];
+        long[] observed = new long[16];
+        for (int i = 0; i < 16; i++) {
+            expected[i] = (double) smallSampleSize * 100 / 16;
+            observed[i] = f.getCount(hex[i]);
+        }
+        /*
+         * Use ChiSquare dist with df = 16-1 = 15, alpha = .001 Change to 30.58
+         * for alpha = .01
+         */
+        assertTrue("chi-square test -- will fail about 1 in 1000 times",
+                testStatistic.chiSquare(expected, observed) < 37.70);
+    }
+
+    /** test failure modes and dispersion of nextUniform() */
+    public void testNextUniform() {
+        try {
+            randomData.nextUniform(4, 3);
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+        try {
+            randomData.nextUniform(3, 3);
+            fail("IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+        double[] expected = { 500, 500 };
+        long[] observed = { 0, 0 };
+        double lower = -1d;
+        double upper = 20d;
+        double midpoint = (lower + upper) / 2d;
+        double result = 0;
+        for (int i = 0; i < 1000; i++) {
+            result = randomData.nextUniform(lower, upper);
+            if ((result == lower) || (result == upper)) {
+                fail("generated value equal to an endpoint: " + result);
+            }
+            if (result < midpoint) {
+                observed[0]++;
+            } else {
+                observed[1]++;
+            }
+        }
+        /*
+         * Use ChiSquare dist with df = 2-1 = 1, alpha = .001 Change to 6.64 for
+         * alpha = .01
+         */
+        assertTrue("chi-square test -- will fail about 1 in 1000 times",
+                testStatistic.chiSquare(expected, observed) < 10.83);
+    }
+
+    /** test exclusive endpoints of nextUniform **/
+    public void testNextUniformExclusiveEndpoints() {
+        for (int i = 0; i < 1000; i++) {
+            double u = randomData.nextUniform(0.99, 1);
+            assertTrue(u > 0.99 && u < 1);
+        }
+    }
+
+    /** test failure modes and distribution of nextGaussian() */
+    public void testNextGaussian() {
+        try {
+            randomData.nextGaussian(0, 0);
+            fail("zero sigma -- IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+        SummaryStatistics u = new SummaryStatistics();
+        for (int i = 0; i < largeSampleSize; i++) {
+            u.addValue(randomData.nextGaussian(0, 1));
+        }
+        double xbar = u.getMean();
+        double s = u.getStandardDeviation();
+        double n = u.getN();
+        /*
+         * t-test at .001-level TODO: replace with externalized t-test, with
+         * test statistic defined in TestStatistic
+         */
+        assertTrue(FastMath.abs(xbar) / (s / FastMath.sqrt(n)) < 3.29);
+    }
+
+    /** test failure modes and distribution of nextExponential() */
+    public void testNextExponential() {
+        try {
+            randomData.nextExponential(-1);
+            fail("negative mean -- expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+        try {
+            randomData.nextExponential(0);
+            fail("zero mean -- expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+        long cumFreq = 0;
+        double v = 0;
+        for (int i = 0; i < largeSampleSize; i++) {
+            v = randomData.nextExponential(1);
+            assertTrue("exponential deviate postive", v > 0);
+            if (v < 2)
+                cumFreq++;
+        }
+        /*
+         * TODO: Replace with a statistical test, with statistic added to
+         * TestStatistic. Check below compares observed cumulative distribution
+         * evaluated at 2 with exponential CDF
+         */
+        assertEquals("exponential cumulative distribution", (double) cumFreq
+                / (double) largeSampleSize, 0.8646647167633873, .2);
+    }
+
+    /** test reseeding, algorithm/provider games */
+    public void testConfig() {
+        randomData.reSeed(1000);
+        double v = randomData.nextUniform(0, 1);
+        randomData.reSeed();
+        assertTrue("different seeds", Math
+                .abs(v - randomData.nextUniform(0, 1)) > 10E-12);
+        randomData.reSeed(1000);
+        assertEquals("same seeds", v, randomData.nextUniform(0, 1), 10E-12);
+        randomData.reSeedSecure(1000);
+        String hex = randomData.nextSecureHexString(40);
+        randomData.reSeedSecure();
+        assertTrue("different seeds", !hex.equals(randomData
+                .nextSecureHexString(40)));
+        randomData.reSeedSecure(1000);
+        assertTrue("same seeds", !hex
+                .equals(randomData.nextSecureHexString(40)));
+
+        /*
+         * remove this test back soon, since it takes about 4 seconds
+         *
+         * try { randomData.setSecureAlgorithm("SHA1PRNG","SUN"); } catch
+         * (NoSuchProviderException ex) { ; } assertTrue("different seeds",
+         * !hex.equals(randomData.nextSecureHexString(40))); try {
+         * randomData.setSecureAlgorithm("NOSUCHTHING","SUN");
+         * fail("expecting NoSuchAlgorithmException"); } catch
+         * (NoSuchProviderException ex) { ; } catch (NoSuchAlgorithmException
+         * ex) { ; }
+         *
+         * try { randomData.setSecureAlgorithm("SHA1PRNG","NOSUCHPROVIDER");
+         * fail("expecting NoSuchProviderException"); } catch
+         * (NoSuchProviderException ex) { ; }
+         */
+
+        // test reseeding without first using the generators
+        RandomDataImpl rd = new RandomDataImpl();
+        rd.reSeed(100);
+        rd.nextLong(1, 2);
+        RandomDataImpl rd2 = new RandomDataImpl();
+        rd2.reSeedSecure(2000);
+        rd2.nextSecureLong(1, 2);
+        rd = new RandomDataImpl();
+        rd.reSeed();
+        rd.nextLong(1, 2);
+        rd2 = new RandomDataImpl();
+        rd2.reSeedSecure();
+        rd2.nextSecureLong(1, 2);
+    }
+
+    /** tests for nextSample() sampling from Collection */
+    public void testNextSample() {
+        Object[][] c = { { "0", "1" }, { "0", "2" }, { "0", "3" },
+                { "0", "4" }, { "1", "2" }, { "1", "3" }, { "1", "4" },
+                { "2", "3" }, { "2", "4" }, { "3", "4" } };
+        long[] observed = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+        double[] expected = { 100, 100, 100, 100, 100, 100, 100, 100, 100, 100 };
+
+        HashSet<Object> cPop = new HashSet<Object>(); // {0,1,2,3,4}
+        for (int i = 0; i < 5; i++) {
+            cPop.add(Integer.toString(i));
+        }
+
+        Object[] sets = new Object[10]; // 2-sets from 5
+        for (int i = 0; i < 10; i++) {
+            HashSet<Object> hs = new HashSet<Object>();
+            hs.add(c[i][0]);
+            hs.add(c[i][1]);
+            sets[i] = hs;
+        }
+
+        for (int i = 0; i < 1000; i++) {
+            Object[] cSamp = randomData.nextSample(cPop, 2);
+            observed[findSample(sets, cSamp)]++;
+        }
+
+        /*
+         * Use ChiSquare dist with df = 10-1 = 9, alpha = .001 Change to 21.67
+         * for alpha = .01
+         */
+        assertTrue("chi-square test -- will fail about 1 in 1000 times",
+                testStatistic.chiSquare(expected, observed) < 27.88);
+
+        // Make sure sample of size = size of collection returns same collection
+        HashSet<Object> hs = new HashSet<Object>();
+        hs.add("one");
+        Object[] one = randomData.nextSample(hs, 1);
+        String oneString = (String) one[0];
+        if ((one.length != 1) || !oneString.equals("one")) {
+            fail("bad sample for set size = 1, sample size = 1");
+        }
+
+        // Make sure we fail for sample size > collection size
+        try {
+            one = randomData.nextSample(hs, 2);
+            fail("sample size > set size, expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+
+        // Make sure we fail for empty collection
+        try {
+            hs = new HashSet<Object>();
+            one = randomData.nextSample(hs, 0);
+            fail("n = k = 0, expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private int findSample(Object[] u, Object[] samp) {
+        for (int i = 0; i < u.length; i++) {
+            HashSet<Object> set = (HashSet<Object>) u[i];
+            HashSet<Object> sampSet = new HashSet<Object>();
+            for (int j = 0; j < samp.length; j++) {
+                sampSet.add(samp[j]);
+            }
+            if (set.equals(sampSet)) {
+                return i;
+            }
+        }
+        fail("sample not found:{" + samp[0] + "," + samp[1] + "}");
+        return -1;
+    }
+
+    /** tests for nextPermutation */
+    public void testNextPermutation() {
+        int[][] p = { { 0, 1, 2 }, { 0, 2, 1 }, { 1, 0, 2 }, { 1, 2, 0 },
+                { 2, 0, 1 }, { 2, 1, 0 } };
+        long[] observed = { 0, 0, 0, 0, 0, 0 };
+        double[] expected = { 100, 100, 100, 100, 100, 100 };
+
+        for (int i = 0; i < 600; i++) {
+            int[] perm = randomData.nextPermutation(3, 3);
+            observed[findPerm(p, perm)]++;
+        }
+
+        /*
+         * Use ChiSquare dist with df = 6-1 = 5, alpha = .001 Change to 15.09
+         * for alpha = .01
+         */
+        assertTrue("chi-square test -- will fail about 1 in 1000 times",
+                testStatistic.chiSquare(expected, observed) < 20.52);
+
+        // Check size = 1 boundary case
+        int[] perm = randomData.nextPermutation(1, 1);
+        if ((perm.length != 1) || (perm[0] != 0)) {
+            fail("bad permutation for n = 1, sample k = 1");
+
+            // Make sure we fail for k size > n
+            try {
+                perm = randomData.nextPermutation(2, 3);
+                fail("permutation k > n, expecting IllegalArgumentException");
+            } catch (IllegalArgumentException ex) {
+                // ignored
+            }
+
+            // Make sure we fail for n = 0
+            try {
+                perm = randomData.nextPermutation(0, 0);
+                fail("permutation k = n = 0, expecting IllegalArgumentException");
+            } catch (IllegalArgumentException ex) {
+                // ignored
+            }
+
+            // Make sure we fail for k < n < 0
+            try {
+                perm = randomData.nextPermutation(-1, -3);
+                fail("permutation k < n < 0, expecting IllegalArgumentException");
+            } catch (IllegalArgumentException ex) {
+                // ignored
+            }
+
+        }
+    }
+
+    // Disable until we have equals
+    //public void testSerial() {
+    //    assertEquals(randomData, TestUtils.serializeAndRecover(randomData));
+    //}
+
+    private int findPerm(int[][] p, int[] samp) {
+        for (int i = 0; i < p.length; i++) {
+            boolean good = true;
+            for (int j = 0; j < samp.length; j++) {
+                if (samp[j] != p[i][j]) {
+                    good = false;
+                }
+            }
+            if (good) {
+                return i;
+            }
+        }
+        fail("permutation not found");
+        return -1;
+    }
+    
+    public void testNextInversionDeviate() throws Exception {
+        // Set the seed for the default random generator
+        randomData.reSeed(100);
+        double[] quantiles = new double[10];
+        for (int i = 0; i < 10; i++) {
+            quantiles[i] = randomData.nextUniform(0, 1);
+        }
+        // Reseed again so the inversion generator gets the same sequence
+        randomData.reSeed(100);
+        BetaDistributionImpl betaDistribution = new BetaDistributionImpl(2, 4);
+        /*
+         *  Generate a sequence of deviates using inversion - the distribution function
+         *  evaluated at the random value from the distribution should match the uniform
+         *  random value used to generate it, which is stored in the quantiles[] array.
+         */
+        for (int i = 0; i < 10; i++) {
+            double value = randomData.nextInversionDeviate(betaDistribution);
+            assertEquals(betaDistribution.cumulativeProbability(value), quantiles[i], 10E-9);
+        } 
+    }
+    
+    public void testNextBeta() throws Exception {
+        double[] quartiles = TestUtils.getDistributionQuartiles(new BetaDistributionImpl(2,5));
+        long[] counts = new long[4];
+        randomData.reSeed(1000);
+        for (int i = 0; i < 1000; i++) {
+            double value = randomData.nextBeta(2, 5);
+            TestUtils.updateCounts(value, counts, quartiles);
+        }
+        TestUtils.assertChiSquareAccept(expected, counts, 0.001);
+    }
+    
+    public void testNextCauchy() throws Exception {
+        double[] quartiles = TestUtils.getDistributionQuartiles(new CauchyDistributionImpl(1.2, 2.1));
+        long[] counts = new long[4];
+        randomData.reSeed(1000);
+        for (int i = 0; i < 1000; i++) {
+            double value = randomData.nextCauchy(1.2, 2.1);
+            TestUtils.updateCounts(value, counts, quartiles);
+        }
+        TestUtils.assertChiSquareAccept(expected, counts, 0.001);
+    }
+    
+    public void testNextChiSquare() throws Exception {
+        double[] quartiles = TestUtils.getDistributionQuartiles(new ChiSquaredDistributionImpl(12));
+        long[] counts = new long[4];
+        randomData.reSeed(1000);
+        for (int i = 0; i < 1000; i++) {
+            double value = randomData.nextChiSquare(12);
+            TestUtils.updateCounts(value, counts, quartiles);
+        }
+        TestUtils.assertChiSquareAccept(expected, counts, 0.001);
+    }
+    
+    public void testNextF() throws Exception {
+        double[] quartiles = TestUtils.getDistributionQuartiles(new FDistributionImpl(12, 5));
+        long[] counts = new long[4];
+        randomData.reSeed(1000);
+        for (int i = 0; i < 1000; i++) {
+            double value = randomData.nextF(12, 5);
+            TestUtils.updateCounts(value, counts, quartiles);
+        }
+        TestUtils.assertChiSquareAccept(expected, counts, 0.001);
+    }
+    
+    public void testNextGamma() throws Exception {
+        double[] quartiles = TestUtils.getDistributionQuartiles(new GammaDistributionImpl(4, 2));
+        long[] counts = new long[4];
+        randomData.reSeed(1000);
+        for (int i = 0; i < 1000; i++) {
+            double value = randomData.nextGamma(4, 2);
+            TestUtils.updateCounts(value, counts, quartiles);
+        }
+        TestUtils.assertChiSquareAccept(expected, counts, 0.001);
+    }
+    
+    public void testNextT() throws Exception {
+        double[] quartiles = TestUtils.getDistributionQuartiles(new TDistributionImpl(10));
+        long[] counts = new long[4];
+        randomData.reSeed(1000);
+        for (int i = 0; i < 1000; i++) {
+            double value = randomData.nextT(10);
+            TestUtils.updateCounts(value, counts, quartiles);
+        }
+        TestUtils.assertChiSquareAccept(expected, counts, 0.001);
+    }
+    
+    public void testNextWeibull() throws Exception {
+        double[] quartiles = TestUtils.getDistributionQuartiles(new WeibullDistributionImpl(1.2, 2.1));
+        long[] counts = new long[4];
+        randomData.reSeed(1000);
+        for (int i = 0; i < 1000; i++) {
+            double value = randomData.nextWeibull(1.2, 2.1);
+            TestUtils.updateCounts(value, counts, quartiles);
+        }
+        TestUtils.assertChiSquareAccept(expected, counts, 0.001);
+    }
+    
+    public void testNextBinomial() throws Exception {
+        BinomialDistributionTest testInstance = new BinomialDistributionTest("");
+        int[] densityPoints = testInstance.makeDensityTestPoints();
+        double[] densityValues = testInstance.makeDensityTestValues();
+        int sampleSize = 1000;
+        int length = TestUtils.eliminateZeroMassPoints(densityPoints, densityValues);
+        BinomialDistributionImpl distribution = (BinomialDistributionImpl) testInstance.makeDistribution();
+        double[] expectedCounts = new double[length];
+        long[] observedCounts = new long[length];
+        for (int i = 0; i < length; i++) {
+            expectedCounts[i] = sampleSize * densityValues[i];
+        }
+        randomData.reSeed(1000);
+        for (int i = 0; i < sampleSize; i++) {
+          int value = randomData.nextBinomial(distribution.getNumberOfTrials(),
+                  distribution.getProbabilityOfSuccess());
+          for (int j = 0; j < length; j++) {
+              if (value == densityPoints[j]) {
+                  observedCounts[j]++;
+              }
+          }
+        }
+        TestUtils.assertChiSquareAccept(densityPoints, expectedCounts, observedCounts, .001);
+    }
+    
+    public void testNextHypergeometric() throws Exception {
+        HypergeometricDistributionTest testInstance = new HypergeometricDistributionTest("");
+        int[] densityPoints = testInstance.makeDensityTestPoints();
+        double[] densityValues = testInstance.makeDensityTestValues();
+        int sampleSize = 1000;
+        int length = TestUtils.eliminateZeroMassPoints(densityPoints, densityValues);
+        HypergeometricDistributionImpl distribution = (HypergeometricDistributionImpl) testInstance.makeDistribution();
+        double[] expectedCounts = new double[length];
+        long[] observedCounts = new long[length];
+        for (int i = 0; i < length; i++) {
+            expectedCounts[i] = sampleSize * densityValues[i];
+        }
+        randomData.reSeed(1000);
+        for (int i = 0; i < sampleSize; i++) {
+          int value = randomData.nextHypergeometric(distribution.getPopulationSize(),
+                  distribution.getNumberOfSuccesses(), distribution.getSampleSize());
+          for (int j = 0; j < length; j++) {
+              if (value == densityPoints[j]) {
+                  observedCounts[j]++;
+              }
+          }
+        }
+        TestUtils.assertChiSquareAccept(densityPoints, expectedCounts, observedCounts, .001);
+    }
+    
+    public void testNextPascal() throws Exception {
+        PascalDistributionTest testInstance = new PascalDistributionTest("");
+        int[] densityPoints = testInstance.makeDensityTestPoints();
+        double[] densityValues = testInstance.makeDensityTestValues();
+        int sampleSize = 1000;
+        int length = TestUtils.eliminateZeroMassPoints(densityPoints, densityValues);
+        PascalDistributionImpl distribution = (PascalDistributionImpl) testInstance.makeDistribution();
+        double[] expectedCounts = new double[length];
+        long[] observedCounts = new long[length];
+        for (int i = 0; i < length; i++) {
+            expectedCounts[i] = sampleSize * densityValues[i];
+        }
+        randomData.reSeed(1000);
+        for (int i = 0; i < sampleSize; i++) {
+          int value = randomData.nextPascal(distribution.getNumberOfSuccesses(), distribution.getProbabilityOfSuccess());
+          for (int j = 0; j < length; j++) {
+              if (value == densityPoints[j]) {
+                  observedCounts[j]++;
+              }
+          }
+        }
+        TestUtils.assertChiSquareAccept(densityPoints, expectedCounts, observedCounts, .001);
+    }
+    
+    public void testNextZipf() throws Exception {
+        ZipfDistributionTest testInstance = new ZipfDistributionTest("");
+        int[] densityPoints = testInstance.makeDensityTestPoints();
+        double[] densityValues = testInstance.makeDensityTestValues();
+        int sampleSize = 1000;
+        int length = TestUtils.eliminateZeroMassPoints(densityPoints, densityValues);
+        ZipfDistributionImpl distribution = (ZipfDistributionImpl) testInstance.makeDistribution();
+        double[] expectedCounts = new double[length];
+        long[] observedCounts = new long[length];
+        for (int i = 0; i < length; i++) {
+            expectedCounts[i] = sampleSize * densityValues[i];
+        }
+        randomData.reSeed(1000);
+        for (int i = 0; i < sampleSize; i++) {
+          int value = randomData.nextZipf(distribution.getNumberOfElements(), distribution.getExponent());
+          for (int j = 0; j < length; j++) {
+              if (value == densityPoints[j]) {
+                  observedCounts[j]++;
+              }
+          }
+        }
+        TestUtils.assertChiSquareAccept(densityPoints, expectedCounts, observedCounts, .001);
+    }
+    
+}
diff --git a/src/test/java/org/apache/commons/math/random/TestRandomGenerator.java b/src/test/java/org/apache/commons/math/random/TestRandomGenerator.java
new file mode 100644
index 0000000..46ed416
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/random/TestRandomGenerator.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.random;
+import java.util.Random;
+
+/**
+ * Dummy AbstractRandomGenerator concrete subclass that just wraps a
+ * java.util.Random instance.  Used by AbstractRandomGeneratorTest to test
+ * default implementations in AbstractRandomGenerator.
+ *
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+public class TestRandomGenerator extends AbstractRandomGenerator {
+
+    private Random random = new Random();
+
+    @Override
+    public void setSeed(long seed) {
+       clear();
+       random.setSeed(seed);
+    }
+
+    @Override
+    public double nextDouble() {
+        return random.nextDouble();
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/random/UncorrelatedRandomVectorGeneratorTest.java b/src/test/java/org/apache/commons/math/random/UncorrelatedRandomVectorGeneratorTest.java
new file mode 100644
index 0000000..60f3b1f
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/random/UncorrelatedRandomVectorGeneratorTest.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.DimensionMismatchException;
+import org.apache.commons.math.linear.RealMatrix;
+import org.apache.commons.math.stat.descriptive.moment.VectorialCovariance;
+import org.apache.commons.math.stat.descriptive.moment.VectorialMean;
+
+import junit.framework.*;
+
+public class UncorrelatedRandomVectorGeneratorTest
+extends TestCase {
+
+    public UncorrelatedRandomVectorGeneratorTest(String name) {
+        super(name);
+        mean = null;
+        standardDeviation = null;
+        generator = null;
+    }
+
+    public void testMeanAndCorrelation() throws DimensionMismatchException {
+
+        VectorialMean meanStat = new VectorialMean(mean.length);
+        VectorialCovariance covStat = new VectorialCovariance(mean.length, true);
+        for (int i = 0; i < 10000; ++i) {
+            double[] v = generator.nextVector();
+            meanStat.increment(v);
+            covStat.increment(v);
+        }
+
+        double[] estimatedMean = meanStat.getResult();
+        double scale;
+        RealMatrix estimatedCorrelation = covStat.getResult();
+        for (int i = 0; i < estimatedMean.length; ++i) {
+            assertEquals(mean[i], estimatedMean[i], 0.07);
+            for (int j = 0; j < i; ++j) {
+                scale = standardDeviation[i] * standardDeviation[j];
+                assertEquals(0, estimatedCorrelation.getEntry(i, j) / scale, 0.03);
+            }
+            scale = standardDeviation[i] * standardDeviation[i];
+            assertEquals(1, estimatedCorrelation.getEntry(i, i) / scale, 0.025);
+        }
+
+    }
+
+    @Override
+    public void setUp() {
+        mean              = new double[] {0.0, 1.0, -3.0, 2.3};
+        standardDeviation = new double[] {1.0, 2.0, 10.0, 0.1};
+        RandomGenerator rg = new JDKRandomGenerator();
+        rg.setSeed(17399225432l);
+        generator =
+            new UncorrelatedRandomVectorGenerator(mean, standardDeviation,
+                    new GaussianRandomGenerator(rg));
+    }
+
+    @Override
+    public void tearDown() {
+        mean = null;
+        standardDeviation = null;
+        generator = null;
+    }
+
+    private double[] mean;
+    private double[] standardDeviation;
+    private UncorrelatedRandomVectorGenerator generator;
+
+}
diff --git a/src/test/java/org/apache/commons/math/random/UniformRandomGeneratorTest.java b/src/test/java/org/apache/commons/math/random/UniformRandomGeneratorTest.java
new file mode 100644
index 0000000..893bdbf
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/random/UniformRandomGeneratorTest.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.random;
+
+import org.apache.commons.math.stat.StatUtils;
+
+import junit.framework.*;
+
+public class UniformRandomGeneratorTest
+extends TestCase {
+
+    public UniformRandomGeneratorTest(String name) {
+        super(name);
+    }
+
+    public void testMeanAndStandardDeviation() {
+        RandomGenerator rg = new JDKRandomGenerator();
+        rg.setSeed(17399225432l);
+        UniformRandomGenerator generator = new UniformRandomGenerator(rg);
+        double[] sample = new double[10000];
+        for (int i = 0; i < sample.length; ++i) {
+            sample[i] = generator.nextNormalizedDouble();
+        }
+        assertEquals(0.0, StatUtils.mean(sample), 0.07);
+        assertEquals(1.0, StatUtils.variance(sample), 0.02);
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/random/ValueServerTest.java b/src/test/java/org/apache/commons/math/random/ValueServerTest.java
new file mode 100644
index 0000000..f0f33a9
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/random/ValueServerTest.java
@@ -0,0 +1,199 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.random;
+
+import java.io.EOFException;
+import java.net.URL;
+
+import org.apache.commons.math.RetryTestCase;
+import org.apache.commons.math.stat.descriptive.SummaryStatistics;
+
+/**
+ * Test cases for the ValueServer class.
+ *
+ * @version $Revision: 1003907 $ $Date: 2010-10-03 00:23:34 +0200 (dim. 03 oct. 2010) $
+ */
+
+public final class ValueServerTest extends RetryTestCase {
+
+    private ValueServer vs = new ValueServer();
+
+    public ValueServerTest(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setUp() {
+        vs.setMode(ValueServer.DIGEST_MODE);
+        URL url = getClass().getResource("testData.txt");
+        vs.setValuesFileURL(url);
+    }
+
+    /**
+      * Generate 1000 random values and make sure they look OK.<br>
+      * Note that there is a non-zero (but very small) probability that
+      * these tests will fail even if the code is working as designed.
+      */
+    public void testNextDigest() throws Exception{
+        double next = 0.0;
+        double tolerance = 0.1;
+        vs.computeDistribution();
+        assertTrue("empirical distribution property",
+            vs.getEmpiricalDistribution() != null);
+        SummaryStatistics stats = new SummaryStatistics();
+        for (int i = 1; i < 1000; i++) {
+            next = vs.getNext();
+            stats.addValue(next);
+        }
+        assertEquals("mean", 5.069831575018909, stats.getMean(), tolerance);
+        assertEquals
+         ("std dev", 1.0173699343977738, stats.getStandardDeviation(),
+            tolerance);
+
+        vs.computeDistribution(500);
+        stats = new SummaryStatistics();
+        for (int i = 1; i < 1000; i++) {
+            next = vs.getNext();
+            stats.addValue(next);
+        }
+        assertEquals("mean", 5.069831575018909, stats.getMean(), tolerance);
+        assertEquals
+         ("std dev", 1.0173699343977738, stats.getStandardDeviation(),
+            tolerance);
+
+    }
+
+    /**
+      * Make sure exception thrown if digest getNext is attempted
+      * before loading empiricalDistribution.
+      */
+    public void testNextDigestFail() throws Exception {
+        try {
+            vs.getNext();
+            fail("Expecting IllegalStateException");
+        } catch (IllegalStateException ex) {}
+    }
+
+    public void testEmptyReplayFile() throws Exception {
+        try {
+            URL url = getClass().getResource("emptyFile.txt");
+            vs.setMode(ValueServer.REPLAY_MODE);
+            vs.setValuesFileURL(url);
+            vs.getNext();
+            fail("an exception should have been thrown");
+        } catch (EOFException eof) {
+            // expected behavior
+        }
+    }
+
+    public void testEmptyDigestFile() throws Exception {
+        try {
+            URL url = getClass().getResource("emptyFile.txt");
+            vs.setMode(ValueServer.DIGEST_MODE);
+            vs.setValuesFileURL(url);
+            vs.computeDistribution();
+            fail("an exception should have been thrown");
+        } catch (EOFException eof) {
+            // expected behavior
+        }
+    }
+
+    /**
+     * Test ValueServer REPLAY_MODE using values in testData file.<br>
+     * Check that the values 1,2,1001,1002 match data file values 1 and 2.
+     * the sample data file.
+     */
+    public void testReplay() throws Exception {
+        double firstDataValue = 4.038625496201205;
+        double secondDataValue = 3.6485326248346936;
+        double tolerance = 10E-15;
+        double compareValue = 0.0d;
+        vs.setMode(ValueServer.REPLAY_MODE);
+        vs.resetReplayFile();
+        compareValue = vs.getNext();
+        assertEquals(compareValue,firstDataValue,tolerance);
+        compareValue = vs.getNext();
+        assertEquals(compareValue,secondDataValue,tolerance);
+        for (int i = 3; i < 1001; i++) {
+           compareValue = vs.getNext();
+        }
+        compareValue = vs.getNext();
+        assertEquals(compareValue,firstDataValue,tolerance);
+        compareValue = vs.getNext();
+        assertEquals(compareValue,secondDataValue,tolerance);
+        vs.closeReplayFile();
+        // make sure no NPE
+        vs.closeReplayFile();
+    }
+
+    /**
+     * Test other ValueServer modes
+     */
+    public void testModes() throws Exception {
+        vs.setMode(ValueServer.CONSTANT_MODE);
+        vs.setMu(0);
+        assertEquals("constant mode test",vs.getMu(),vs.getNext(),Double.MIN_VALUE);
+        vs.setMode(ValueServer.UNIFORM_MODE);
+        vs.setMu(2);
+        double val = vs.getNext();
+        assertTrue(val > 0 && val < 4);
+        vs.setSigma(1);
+        vs.setMode(ValueServer.GAUSSIAN_MODE);
+        val = vs.getNext();
+        assertTrue("gaussian value close enough to mean",
+            val < vs.getMu() + 100*vs.getSigma());
+        vs.setMode(ValueServer.EXPONENTIAL_MODE);
+        val = vs.getNext();
+        assertTrue(val > 0);
+        try {
+            vs.setMode(1000);
+            vs.getNext();
+            fail("bad mode, expecting IllegalStateException");
+        } catch (IllegalStateException ex) {
+            // ignored
+        }
+    }
+
+    /**
+     * Test fill
+     */
+    public void testFill() throws Exception {
+        vs.setMode(ValueServer.CONSTANT_MODE);
+        vs.setMu(2);
+        double[] val = new double[5];
+        vs.fill(val);
+        for (int i = 0; i < 5; i++) {
+            assertEquals("fill test in place",2,val[i],Double.MIN_VALUE);
+        }
+        double v2[] = vs.fill(3);
+        for (int i = 0; i < 3; i++) {
+            assertEquals("fill test in place",2,v2[i],Double.MIN_VALUE);
+        }
+    }
+
+    /**
+     * Test getters to make Clover happy
+     */
+    public void testProperties() throws Exception {
+        vs.setMode(ValueServer.CONSTANT_MODE);
+        assertEquals("mode test",ValueServer.CONSTANT_MODE,vs.getMode());
+        vs.setValuesFileURL("http://www.apache.org");
+        URL url = vs.getValuesFileURL();
+        assertEquals("valuesFileURL test","http://www.apache.org",url.toString());
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/random/Well1024aTest.java b/src/test/java/org/apache/commons/math/random/Well1024aTest.java
new file mode 100644
index 0000000..2130657
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/random/Well1024aTest.java
@@ -0,0 +1,200 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.random;
+
+import org.junit.Assert;
+
+import org.apache.commons.math.stat.descriptive.SummaryStatistics;
+import org.apache.commons.math.util.FastMath;
+import org.junit.Test;
+
+public class Well1024aTest {
+
+    @Test
+    public void testGaussian() {
+        Well1024a mt = new Well1024a(42853252100l);
+        SummaryStatistics sample = new SummaryStatistics();
+        for (int i = 0; i < 10000; ++i) {
+            sample.addValue(mt.nextGaussian());
+        }
+        Assert.assertEquals(0.0, sample.getMean(), 0.004);
+        Assert.assertEquals(1.0, sample.getStandardDeviation(), 0.003);
+    }
+
+    @Test
+    public void testDouble() {
+        Well1024a mt = new Well1024a(195357343514l);
+        SummaryStatistics sample = new SummaryStatistics();
+        for (int i = 0; i < 10000; ++i) {
+            sample.addValue(mt.nextDouble());
+        }
+        Assert.assertEquals(0.5, sample.getMean(), 0.0006);
+        Assert.assertEquals(1.0 / (2.0 * FastMath.sqrt(3.0)),
+                     sample.getStandardDeviation(),
+                     0.002);
+    }
+
+    @Test
+    public void testFloat() {
+        Well1024a mt = new Well1024a(4442733263l);
+        SummaryStatistics sample = new SummaryStatistics();
+        for (int i = 0; i < 10000; ++i) {
+            sample.addValue(mt.nextFloat());
+        }
+        Assert.assertEquals(0.5, sample.getMean(), 0.0001);
+        Assert.assertEquals(1.0 / (2.0 * FastMath.sqrt(3.0)),
+                     sample.getStandardDeviation(),
+                     0.003);
+    }
+
+    @Test(expected=java.lang.IllegalArgumentException.class)
+    public void testNextIntNeg() {
+        new Well1024a(1).nextInt(-1);
+    }
+
+    @Test
+    public void testNextIntN() {
+        Well1024a mt = new Well1024a(0x12b8a7412bb25el);
+        for (int n = 1; n < 20; ++n) {
+            int[] count = new int[n];
+            for (int k = 0; k < 10000; ++k) {
+                int l = mt.nextInt(n);
+                ++count[l];
+                Assert.assertTrue(l >= 0);
+                Assert.assertTrue(l <  n);
+            }
+            for (int i = 0; i < n; ++i) {
+                Assert.assertTrue(n * count[i] >  8600);
+                Assert.assertTrue(n * count[i] < 11200);
+            }
+        }
+    }
+
+    @Test
+    public void testNextInt() {
+        Well1024a mt = new Well1024a(new int[] { 1, 2, 3, 4, 5 });
+        int walk = 0;
+        for (int k = 0; k < 10000; ++k) {
+           if (mt.nextInt() >= 0) {
+               ++walk;
+           } else {
+               --walk;
+           }
+        }
+        Assert.assertTrue(FastMath.abs(walk) < 70);
+    }
+
+    @Test
+    public void testNextLong() {
+        Well1024a mt = new Well1024a(12345);
+        int walk = 0;
+        for (int k = 0; k < 10000; ++k) {
+           if (mt.nextLong() >= 0) {
+               ++walk;
+           } else {
+               --walk;
+           }
+        }
+        Assert.assertTrue(FastMath.abs(walk) < 70);
+    }
+
+    @Test
+    public void testNexBoolean() {
+        Well1024a mt = new Well1024a(76342);
+        int walk = 0;
+        for (int k = 0; k < 10000; ++k) {
+           if (mt.nextBoolean()) {
+               ++walk;
+           } else {
+               --walk;
+           }
+        }
+        Assert.assertTrue(FastMath.abs(walk) < 180);
+    }
+
+    @Test
+    public void testNexBytes() {
+        Well1024a mt = new Well1024a(0);
+        int[] count = new int[256];
+        byte[] bytes = new byte[10];
+        for (int k = 0; k < 1000000; ++k) {
+           mt.nextBytes(bytes);
+           for (byte b : bytes) {
+               ++count[b + 128];
+           }
+        }
+        int min = Integer.MAX_VALUE;
+        int max = Integer.MIN_VALUE;
+        for (int c : count) {
+            min = FastMath.min(min, c);
+            max = FastMath.max(max, c);
+        }
+        int expected = (1000000 * bytes.length) / count.length;
+        Assert.assertTrue((expected - 600) < min);
+        Assert.assertTrue(max < (expected + 600));
+    }
+
+    @Test
+    public void testReferenceCode() {
+        Well1024a mt = new Well1024a(new int[] {
+            740849862,  1202665156,  -199039369,  -259008301,  -291878969, -1164428990, -1565918811,   491009864,
+          -1883086670,  1383450241,  1244617256,   689006653, -1576746370, -1307940314,  1421489086,  1742094000,
+           -595495729,  1047766204,  1875773301, -1637793284,  1379017098,   262792705,   191880010,  -251000180,
+          -1753047622,  -972355720,    90626881,  1644693418,  1503365577,   439653419,  1806361562,  1268823869
+       });
+        int[] refInt = {
+           -1478749726,  -1645579484,  -2075363835,  -2063444174,  -1834148336,  -1769045872,    -40711346,   1717441026,
+            2130656771,    783441285,    570433609,   1560023451,    653233971,   1368672434,    -72036215,   1071111800,
+             933776492,     26114960,     49888778,   1808107515,   1092989296,    754848337,   1336994364,  -1987450448,
+            -691190146,  -1803870839,   1110716866,   1173269113,   -391000050,   2014216908,    180756301,   -382891013,
+           -1908154585,   1580737629,   1080267957,   -125532248,   2094530239,   2132964485,   -438596348,   -760299445,
+            1058181869,   2050816800,  -1534429037,    -62552782,    824524142,   -818590371,  -1857695907,   -684762866,
+            -156556543,   -902759995,   -880795194,  -1387351132,  -1263017515,    448006597,    201038266,   1929826313,
+            -455367306,    672963027,   2000073013,  -1546842042,    446341090,   1001696686,   -779919012,   -347722602,
+           -1342821677,   1639571150,   -835315755,   1505585376,    367004975,  -2035864404,  -1786623553,   1249724913,
+             182435312,   1444514513,   1815333708,   1333772382,    299664001,   -284691169,   2034403374,   1423310887,
+           -1319051884,   1557286441,   -445198266,   -251809030,   1602786123,    944036382,  -1020529634,    258344235,
+             685254367,   1838964943,   -156674528,   -979736602,   -538312836,    234643178,    211152102,   -635498640,
+           -1036733933,  -1347589147,   -565609042,  -1358714165,    508618483,   -786364693,   2071450261,   1206956772,
+            -678931458,    167690617,    144698821,   1719720781,   1575869280,  -1343221123,  -1766469944,    284991647,
+            -717305514,    892653651,  -1368347075,   -615701972,   -730369849,   1360396003,  -1869287623,   1778269052,
+            -586061545,   -699517114,     61530249,  -1860611767,   -519660852,   1841085925,   1555610093,   -399979337,
+            -790345742,    422355947,   2007965433,   2044952550,  -1712164595,   -102915702,   -693865324,  -1894042487,
+           -1285020072,   -215883074,     95833252,   1625818040,  -1055951680,    513067085,   1825246558,   -553461652,
+           -1923361799,  -1869480206,    567232636,  -1751727150,  -1832301399,   -108136455,  -1312244126,     14006795,
+             850221366,   -382389732,  -1741556188,  -1317464467,   1948314870,    753994471,   1028235947,    342494132,
+           -1862256693,    723808794,   -234257642,   1609928369,   -802733456,   1315831915,   1436072885,   1224767136,
+            2144557791,  -1839965886,    224821018,  -1461697757,  -1080386760,   1638573498,  -1188173812,   -325181523,
+           -1750676219,  -1780415850,    698793362,   -908352052,    299746482,   -161660934,   1938166833,    800297005,
+              56640033,  -1214932666,  -1248124842,   1822796868,   1777615881,   -718517774,   1908159957,   1733053281,
+            1851844331,   1283519375,  -1771494956,   2060179999,   1666129209,   1919453531,   -498145770,    697567008,
+            1855487148,  -1587163491,    565216434,  -1477877933,   -925662919,   -806492585,  -1201439047,  -1424534232,
+            1788616523,     69414717,    655893636,  -1175978556,     24787512,   -861550001,    439525754,   -190433174,
+            -383811606,   -508589783,   1441608687,    608181366,   1539467064,    925903122,    697209654,   1878283393,
+           -1967567432,  -1659677763,   -249658183,    847096354,    397741956,   -125334541,  -1286840731,   1016461908,
+            -997968592,   1795331475,   1856856501,  -1716726445,   -582181331,   -887091847,    426964855,   -609219941,
+           -1456232632,   -483467616,   1069260754,    972242064,  -1406786247,   1954194029,     52627891,   1212755081,
+            2117436668,    281073392,    741537353,   -483063506,   1850906286,   -244876135,   -270818140,   1817568823
+        };
+
+        for (int i = 0; i < refInt.length; ++i) {
+            Assert.assertEquals(refInt[i], mt.nextInt());
+        }
+
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/random/Well19937aTest.java b/src/test/java/org/apache/commons/math/random/Well19937aTest.java
new file mode 100644
index 0000000..78e0bf5
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/random/Well19937aTest.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.random;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class Well19937aTest {
+
+    @Test
+    public void testReferenceCode() {
+        int[] base = {
+              740849862,  1202665156,  -199039369,  -259008301,  -291878969, -1164428990, -1565918811,   491009864,
+            -1883086670,  1383450241,  1244617256,   689006653, -1576746370, -1307940314,  1421489086,  1742094000,
+             -595495729,  1047766204,  1875773301, -1637793284,  1379017098,   262792705,   191880010,  -251000180,
+            -1753047622,  -972355720,    90626881,  1644693418,  1503365577,   439653419,  1806361562,  1268823869
+         };
+        int[] init = new int[624];
+        for (int i = 0; i < init.length; ++i) {
+            init[i] = base[i % base.length] + i;
+        }
+        Well19937a mt = new Well19937a(init);
+        int[] refInt = {
+            -612874471,   -354976292,  -1838197125,  -1781560577,    278390997,   1214938280,  -1752615390,   -760835246,  -1712883765,   -241205782,   -145390202,    495649160,   -514388259,  -1271015916,  -1640000013,    849273623,
+            -549729394,  -1206917255,   -545909692,    811925434,  -1665729633,  -1525292882,   1416246482,   -153220826,   1148868872,   -326143196,   1724979062,   1790931148,  -1648679618,   -439051683,    112482777,  -1484051520,
+           -1881272572,  -1270447031,  -1216102401,   1579107248,  -1224395621,   2144411988,   -416216641,  -1529222361,   1628987080,    164445245,   1506928916,    928145916,   1436000427,    862025970,    560077705,  -1887251027,
+            -514360858,   1735094506,    475624879,   1755802355,    295448361,   -155399225,      3972415,   1368201076,   -465126094,  -1622687259,   -246099304,   1798631152,  -1937269102,  -1700560396,   -293352622,   -896632303,
+           -2088933220,   -194382452,   -480218162,  -1618517785,  -1925031481,   -150217434,   1678937261,   2130832364,   -485546678,  -1499224981,   1430390884,  -1895417302,    210514746,   1781140999,  -1940853105,  -1238099647,
+             485922557,   -103223212,    633481679,   -632946979,    695235541,  -1661735272,    277603567,   -958341538,    256982285,   1850270018,   -327388076,   -219053874,   1380560653,  -1221689980,   1335863752,   -545032383,
+            -575291735,  -1295623907,   -140058298,   1063302709,  -1290702617,   -790401546,   -170630961,  -1203114473,   1458063108,  -1212753301,   1546428514,   2112636413,  -1463028928,  -1812598032,   -883529486,   1131084094,
+              62042165,   2135819802,  -1192342739,     98361522,  -1341042205,   -475283063,  -1632033747,   1745196892,    168608689,   -914987039,    274428907,   -881357258,    167940012,  -1975737532,   -903960486,  -1370984244,
+            -589352935,   1783633514,   -570111010,     71495377,    194463285,  -1243905021,  -1398490898,    221691209,    -55728834,   -638916786,   -770622372,  -1911651810,   -295233027,    301467998,   2058638784,    681490183,
+           -1547865078,  -1668135684,   1299261826,   1649468635,    287995017,  -2076844852,   1193468826,   -853948258,    120082777,   1051829542,  -1288514343,   -159456430,    275748820,   -480127107,   -604943233,  -2138088332,
+            1202614819,   1427201263,  -1906344469,  -1230779533,   1690367192,    733159097,    794410312,  -1114452505,  -1601554413,    976747949,   1517787154,   2091780205,   1052078906,   1919282771,   -191013374,   1805397142,
+             736939268,  -1056272823,   -727464316,   -659459005,    797803875,  -1104633884,   1042342081,    -24514837,   1919469940,   1903722546,   -814157872,   1605407665,   -262351256,   -288949635,    729204844,  -1132605534,
+             745453338,    387915035,   1094173337,   2100279147,    156863702,   -257377544,   -719587984,  -1496015613,   1908993744,   2016957554,    918749666,   -135963651,  -1356808639,  -1711185741,   1472589240,   -398100149,
+             628791415,  -1381837652,  -1820702771,   -593586943,  -1456631279,  -1837975351,  -1394249972,   -556916726,    833231177,     43449750,   1029237092,  -2086437337,   -459463076,   -533031784,  -1739648287,  -1374722961,
+            2024908394,   1389678488,      2018558,  -1391707864,   -795935743,    904816957,    836583280,   1766194531,  -1374431014,   -904437876,   2030248636,   -265724199,   2056758426,   -810499837,    887193593,    -77811488,
+            1496312336,  -1874348275,   -456193866,  -2137130942,    868120387,     29025455,  -1999867716,   2001322335,   -579152815,   -390892056,   1592011837,   -306394879,     93636886,   -190879994,   1923358153,    269052141,
+            -396050253,   -987531729,    480350991,   1276744541,  -1445571957,   -957571005,  -2046270221,  -1715395752,   1113585628,  -1782113514,   -697560146,    835320000,   1014320959,  -2119834109,    460056841,  -1464772991,
+           -1282790418,  -2120806165,     86176097,   -731086307,    832497517,  -1876684928,    541008240,    551124479,   -450919132,    647860281,  -2115397586,    979247589,   1095559204,   1927958688,    169497703,   1999579054,
+            2019745038,   1656022059,  -1109662138,    375237154,   1450814436,    919988416,    849761266,   1457057327,   1771166577,  -1639880487,   -852488298,   1767063646,    657295386,   -585561879,    740792583,   1664558308,
+            -654749506,   1109275990,    182597559,   1106789745,  -1806628480,     25948116,   1748374299,    196057325,   -164213209,   1687024594,    782029276,   1879737947,  -1528219611,    412585737,   1190325629,   1985821911,
+           -1272945202,  -1238637137,    465818730,  -1537670961,   1131953615,    905623579,    609183424,   1138422991,   1522974699,    589719061,  -1310894604,    890952933,   -885204790,   -393535694,   1238408670,   1780660354,
+             677803525,  -1121509064,   1553148616,   1109165936,  -1450120385,   1525252521,  -1354897489,   -595402189,  -1274551767,   -869281409,   1788815975,   2020262116,   1124100185,   -400839020,    310574108,   1354413045,
+           -1310514485,   1895732085,    626639054,   1667355357,   2065637178,  -1889009143,   -440157749,   1762849463,  -1693853642,    -56602956,   -930874188,   -398470740,    778356402,  -2113156881,     42854964,   1844399604,
+           -2098310302,  -1812029757,   1441188713,    899579267,   1266994172,   1841370863,   -660740252,    -43254718,   1124500192,   1884907320,    879997211,   1775139845,  -1360112721,   1630490057,    362567879,   1113475029,
+             290319279,  -1209506867,    398146039,   -957742350,   1185761854,   1519676447,   -912689915,  -1117128973,   -305563462,  -1928033363,  -1766324543,   1702753492,   1696951912,  -1895072395,    932663591,   -566548128,
+             991675996,     56529814,    980735023,    718166662,   -650028466,   -886842051,   1857048587,   -569023569,  -1820572202,   -851452711,   -958700452,   -621825633,    -65649888,   -510143183,    761267599,  -1692108035,
+            1729071710,   1623630864,    -53498654,    267235687,    659201413,   1152882627,   -824194574,    356636960,   -502391121,   -538453360,     66115376,  -1633290370,  -1522088932,    268949070,    684499443,   -859474501,
+            1586764345,  -1515639709,    319695602,   -307025150,     69076508,   1050726785,  -1340930110,    552191600,   -207852941,   -273572993,   -539580440,    710343120,   1957076127,  -1107172811,   -561518280,  -1775022699,
+            1978792904,   1935531451,  -2084046304,   -419742902,   -737652926,    614022023,   1676952428,    769892939,  -1092786807,  -1117113223,   -266029995,   -350150999,    207738542,   1964896575,     48805284,   1736500159,
+             551289617,  -1847923501,   1856609505,   2007480480,   -681860219,  -1198106493,   1483591043,   -523895316,  -1814473078,  -1521087404,  -1348859926,   1298056897,   1813789478,    946683654,     79196400,   1766250931,
+             472737685,   1764634332,  -1844726079,   -130619045,   -508713868,  -1762537125,   1010108863,    170107098,   1705386380,  -1139681802,    183739097,   1662699401,   1842694501,   1714633805,     46208876,    616720693,
+            -252553427,   1986302230,   -103130254,   1943225981,    110746655,    553260552,   1588938073,  -1934623163,  -2144781332,  -2086217416,   1941265852,   -781953226,   1216234254,    605543697,   -710872598,   2048636577,
+           -1986927728,  -1007017623,   1243051501,   -614249563,  -2128221291,    581579813,   1173464240,  -1906830937,    261329601,  -1805974103,    769823490,   1858731164,   -561762071,    516417430,  -1221329437,   -825500715,
+            1091364656,   -993658663,  -1475434188,  -1070804384,  -1876492082,    899494424,    683486936,    878807455,     56642807,  -1268202879,   1379172046,  -1386869373,  -1158233876,   1759190552,   1597629789,   1411151497,
+           -1254268471,   1075936979,   -918778269,  -2132675184,    953140888,   1906982077,   1154200766,   -365384600,  -1142488826,    708535121,  -2134869964,  -1531201665,  -2100526761,   1268256467,   2071480803,    193135243,
+            1374158182,    989505347,   -933612202,  -2134839213,  -1302795271,  -2092029041,   1812014826,   2090855917,   2005348528,    606434393,    -60141386,     11156360,    539516285,   -122485034,   -893237911,   -978127424,
+            1509901816,   -451029719,    428544700,  -1622965963,  -1993611605,  -1989324583,   1104111587,   -795138585,   -899552401,  -2110167769,   -234502445,   1586963605,   -503778455,    529261062,    325327284,   -106186403,
+              65369563,  -1475700698,   -228624261,    715975009,   1099352363,  -1796883396,   1376542700,   -308942420,   -344940451,   -395389249,  -1562737166,   1869802677,   1273494710,   2075587668,   -789570273,   1563347596,
+            1142901755,   1676422422,  -1729157809,  -1399423717,  -1814262429,  -1809707284,   1393992342,   -570246212,   1065528749,   -781643849,   1218667301,  -1097949471,   1305629790,    901301039,   -704762030,    360582612,
+            1411910672,   1848068741,   -614500891,   -146889637,   -913903597,    723527277,   -147033328,   -199273155,    734997691,  -2072735286,   2129258691,  -1385074104,    931616624,   1065477319,  -1543474555,   -531410292,
+           -2123119121,  -1538464113,  -1153585193,   1559931968,   -654877011,    879865200,   1489681397,   1998864644,  -1964160144,    163671782,   -858364148,   -323324233,    801208648,    705864113,    436184243,    643773864,
+            2087594507,    134637265,   -749956494,  -1657343972,  -1828172168,    -27357303,  -1145161336,  -1192513644,    216148260,    611393153,    -13752671,   -358631090,  -1211920749,    593572064,    657629904,  -1445961088,
+            -250704995,   1797542707,  -2122311891,   -316774825,   -296303057,   -868002056,    -86697533,   2020588145,   1203427903,  -1371839056,    669531557,  -2031033836,   1323994690,     13703036,    785437772,  -1465821554,
+            -694756014,  -2131068154,  -1745448876,  -1095891733,    936594025,  -1119068454,    855423970,   1705079340,   -905640608,    162297141,   1336619311,   -344353769,    -92608588,  -1080573824,   2002293105,  -2088030765,
+           -1684198727,   -129054718,   -949437132,   -127983221,   -216664110,   1700146143,   -711174649,   1500113839,   1212236226,  -2017364219,  -1263597675,    511929344,   -323998524,  -2021313185,   1803000924,    927670608,
+             336267187,   1244256964,  -1665390108,    991395134,   -251232188,   1267445783,   1547951569,    740269916,   1776431169,   1687220659,    228229817,    271386089,   -682906779,   -438090344,   1190436796,   -744272540,
+            1879221151,   1145200306,  -1730983338,  -1119209309,     90826726,   1567861540,   1638852830,  -1645384932,   1566909531,   1088584561,   1030555565,  -1872052014,    720320695,   -885053674,   -321216789,    739907579,
+             368580703,   -443635520,   1232705619,  -1355949988,  -1047211249,  -1571429448,    599299852,   1036970439,   1513838571,    -51797291,    -26647565,  -1262878942,   -916262582,   1579082269,   -292007383,   1289013866,
+           -1612184284,   1451738668,    448608569,    476432992,  -1229609565,    786372409,    929928149,   -150100614,    448155944,  -1322320576,   -856549627,   1057443268,  -1536809554,    577508258,    584906122,    275295163,
+            -604262071,   -236043234,  -1866434954,  -2072447013,    646132876,    847562546,   -310005953,  -1104162658,    393261203,   -730102354,    440824482,   1654535035,  -1296359745,   1487359328,   -977776604,   -775827779,
+           -1298695106,    519080622,   1697162240,    227873031,   -371123123,   1273702312,  -1710063656,  -2138342344,   1139555478,   1531578907,  -1498880699,   1183507362,   1875307493,  -1649740413,   2135386504,   -962458407,
+             424161768,    504272962,    202204247,   1783466420,   2015579232,   -676642965,   2067456450,    914480415,   -620398841,   1880405399,   1406637142,   1951104977,    633496157,    224861869,    -58659291,    994942775,
+            -479000645,   1421449115,    100168104,    249754169,  -1219011494,   1736303638,    364013694,  -1750035055,   -479217141,   1652913106,  -2109452331,   1633842910,  -1547663337,    936627493,  -1152799743,    896955899,
+           -1407742850,   -523769014,    357161414,    872293304,    744895980,    720829676,   -240843156,   -111779524,   1292836315,  -1792141538,   1946959925,   1181751089,  -1120674052,   1185192575,  -1387002557,   1973209255,
+            -120887476,   -766577735,   -443913073,    786620227,    428564781,   -101232106,   -425959852,    198082021,   1173272226,  -1744840378,  -1621135606,  -1539498583,  -1101274572,     43399711,  -1256764602,   1201920787,
+            2049426139,    846545551,  -2121520873,  -1202939675,   -470425740,    321987390,   1862019060,   -951540342,   -894238318,   -430407175,  -1662746491,    656574776,   1580373777,   -431290218,   1645824323,  -1953526979,
+            -374682356,    474291752,   1071558425,    511038981,   -760598678,   -567797285,  -1176476266,   -268409005,  -2130644484,    -67970563,   1756046948,   1429860462,  -1130984739,   -124916495,  -1544436836,  -1863524031,
+            1024916487,  -1388636482,  -1573205065,    892628956,   1831270021,   1176430590,   1158914682,  -2006787098,  -1228130033,   1516111488,  -1499151347,    470546266,   1642603981,   1425140838,  -1823071475,  -1775267236,
+           -1009380612,    164746986,   1129677098,   1842642579,   -482342932,   -507480364,   1656012309,   1981601761,   -881042120,   -511987083,    342447017,    381192578,    983008095,    741012865,  -1877136350,   -199211983,
+            -452784912,   1929572576,  -1678291139,   -864375281,  -1610561247,  -1936356726,   -749553767,   -865893512,   -567081879,  -1303973729,   -939636958,   -622974563,    428284937,   1049237414,    852280765,     86648946,
+           -1353851401,  -1045422335,    898035731,  -1636093996,  -1083174191,    245046915,   -359768226,  -1028491655,   1051575118,   1774289451,   1839389415,  -1594053468,    736707953,   1873556950,    401186168,   -583669552,
+             -88375334,   2002752071,    264506453,  -1304812107,   -759203942,   -114958524,  -1878903503,    841613720,   1910863820,  -1738114003,    701455920,   1791058048,  -1850960547,   1672292671,   1172188809,    604848896,
+           -1607489375,    305370478,   -948153885,  -1971080100,  -1848966954,   -584538365,     39416319,  -1689119162,    944942598,   1777111075,   1534005553,   2022718432,    -25820385,      3077695,   -315950520,   1859184648,
+           -1397829266,  -1666371809,    858913807,   -610818620,   1554973298,    580023809,  -1662988256,   -408630026,   1316681876,    738204271,    942829881,   -758486983,    780345857,    667165037,  -2086803585,    789741324
+        };
+
+        for (int i = 0; i < refInt.length; ++i) {
+            Assert.assertEquals(refInt[i], mt.nextInt());
+        }
+
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/random/Well19937cTest.java b/src/test/java/org/apache/commons/math/random/Well19937cTest.java
new file mode 100644
index 0000000..1f40fa4
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/random/Well19937cTest.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.random;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class Well19937cTest {
+
+    @Test
+    public void testReferenceCode() {
+        int[] base = {
+              740849862,  1202665156,  -199039369,  -259008301,  -291878969, -1164428990, -1565918811,   491009864,
+            -1883086670,  1383450241,  1244617256,   689006653, -1576746370, -1307940314,  1421489086,  1742094000,
+             -595495729,  1047766204,  1875773301, -1637793284,  1379017098,   262792705,   191880010,  -251000180,
+            -1753047622,  -972355720,    90626881,  1644693418,  1503365577,   439653419,  1806361562,  1268823869
+         };
+        int[] init = new int[624];
+        for (int i = 0; i < init.length; ++i) {
+            init[i] = base[i % base.length] + i;
+        }
+        Well19937c mt = new Well19937c(init);
+        int[] refInt = {
+            2128528153,    327121884,    935445371,    -83026433,  -1041143083,   2084595880,  -1073535198,  -1678863790,   -523636021,  -1514837782,   -736786810,   1527711112,  -1051227939,    978703380,    410322163,   1727815703,
+            -648426354,    636056441,   1954420292,     17754810,   -958628705,  -1091307602,   1793078738,  -1680336346,   1792171272,    941973796,  -2066152330,  -1248758068,  -1061211586,    262020189,   1276960217,   -233886784,
+            1767509252,  -1811939255,   -406116097,   -742435920,  -1349799525,    240329556,   -332161345,   1488943143,   -332244280,   2093328957,    674753300,  -1930135556,    257111467,     63793650,  -1964335223,   1315849133,
+            -797349146,   1372022250,  -1451892049,  -1325138957,   -870401239,  -1294317369,     91490879,    386205044,   -704074702,  -1230679067,   1513674392,   -262996240,   1196007314,   1398903796,    803719762,  -1750926831,
+           -1268814180,   1233515404,   1498313934,   -970591257,    611113671,   -261632474,   1834097325,   1709440492,   -150396854,   2120561003,    -62645660,    479080234,   1535125050,   1823378695,  -1129289329,  -1095198399,
+            2092564733,     78836308,   -692015409,   1647147229,  -1847922219,   1838279320,   -848333841,  -1375151778,    920238861,   1512628290,   -749439404,    288851918,   -427218675,    679640964,    425700808,  -2077624511,
+           -1929434455,   -647176419,    650437190,  -1926749131,  -1564744729,    734494454,    108193743,    246246679,    810042628,   1952337771,   1089253730,  -1874275331,   1428419392,   -492969232,   1945270770,   -201265602,
+            -755490251,   -624426214,   -699605715,   -113446478,    809091299,  -1521531511,   1136505389,   -523660964,    132928433,   1926559713,  -1485314325,   -508322506,     46307756,  -1627479740,   -589386406,  -1855555892,
+             584299545,   1272841066,   -597242658,    925134545,   1102566453,   -753335037,     -9523218,  -1778632375,    568963646,    764338254,   1259944540,  -2000124642,   1307414525,   -151384482,    807294400,   1993749511,
+             -15503094,   -709471492,   2104830082,   1387684315,  -1929056119,    224254668,   -733550950,   -889466978,  -1987783335,   -437144026,    995905753,  -1021386158,  -1096313388,  -1014152835,  -1303258241,   1201884788,
+           -1845042397,   1421462511,    980805867,   2143771251,    481226968,   1790544569,    328448328,   1995857639,    -66668269,  -1411421267,   -222586606,    866950765,   -308713926,  -1048350893,    993222402,  -1139265642,
+            -871837948,   1145571913,    381928580,     35386691,   1640961123,  -1192981020,    775971009,    594246635,   1603197812,   -575106766,   2023682000,  -1636301903,   -718093720,  -1666421635,  -2146115988,    320593570,
+             287355418,    454400027,   1112753817,   1751196267,    782077910,  -1478447368,  -1007557264,   -862315517,  -2035355952,   2123515250,   -557641502,  -1789932035,    879640129,     44167603,    791148984,   1382939723,
+           -2135684233,   1825489580,    937345485,  -1839983359,  -1536880111,  -1472578359,   1548052748,  -1471535862,    -14508727,   1509621398,  -2134967452,   -787485401,    815341660,   -327905128,   1028096737,    866906991,
+           -1585990806,    859229080,    234806270,    998518056,  -1897890815,   -900923587,   1179856752,   1529572451,    620486106,   1119836556,   1661285564,   2097404633,  -1437490790,    265306115,   -984880135,   1326751968,
+            1280043536,    680210701,    155786166,   1550973250,   -325781949,   -597789777,     -1939780,   1345275487,   1930450001,    941449704,    669301309,    693651713,   -990721514,    582968326,    976132553,  -1892942099,
+           -1065070157,   -711990993,   -688974833,  -1026091683,   1115346827,  -1305730749,  -1733626381,   -364566696,    -21761572,    -37152746,   -262011730,   1302722752,  -1806313409,   -767072509,    764112137,   1671157377,
+            1837645038,  -1021606421,  -1781898911,   -232127459,   -310742675,  -1818095744,  -1128320656,   -705565953,   -354445532,   -523172807,   -433877202,    131904485,    -64292316,    381829280,    229820263,   1797992622,
+            1359665678,    978481451,   -885267130,  -1415988446,   -356533788,   -961419072,   1938703090,    708344111,    679299953,    744615129,   1328811158,   1257588574,    569216282,   -753296151,  -1519838713,   2016884452,
+            1062684606,   1561736790,   2028643511,  -1353001615,    886376832,   1466953172,   1664783899,   1290079981,    -57483993,  -1176112430,   1634916316,   1976304475,   1374136869,   -648738039,   1058175869,   -909000745,
+           -1526439218,    726626991,   2066596202,     64980943,    -26166577,   -885900005,  -1821546816,  -1103727665,    730606315,  -1324948459,   -696956940,  -1300869403,   1171578314,    797249074,  -1600611618,   1928247682,
+             307164165,  -1482476232,  -1886179640,   1306433392,   1945271359,  -1272113751,  -1285984081,  -2057145549,    795047465,   1262569087,  -1239828121,   1426641636,   -786371495,   2120199316,   1273690652,     74457589,
+           -1033394229,    338952565,     46122958,   1225741533,   2115308090,    678200841,  -1618264885,   -101162569,  -1628976330,  -1232839500,    468709044,   1876019116,     92723122,    233398255,   -554960844,     38494196,
+            -406437278,   2083528643,  -1106878615,   -340722557,  -2123964932,    223183343,    108918116,  -1014629054,   -901344544,   -838896840,  -1908460517,  -1763508731,   -926890833,   1703791049,   -667755577,   1694418389,
+             791641263,   1095689677,   1119202039,  -1419111438,  -2012259010,    188017439,  -1775110395,  -1971099661,  -1688113734,    131472813,   -776304959,   1511388884,   2080864872,  -1733824651,   1992147495,   1119828320,
+            1065336924,  -1357606762,    462963503,   1180719494,   -202678962,   -892646595,    605869323,   1144255663,    878462678,  -1051371303,    872374876,    631322271,   -172600544,  -1552071375,  -1939570033,    151973117,
+            1640861022,    310682640,     34192866,   2057773671,  -2004476027,  -1879238973,    582736114,    900581664,   -427390545,  -1232348528,   -535115984,   1321853054,     69386780,  -1729375922,   1418473715,   1022091451,
+             496799289,    -80757405,  -1903543310,  -1128846846,      1703964,   1984450945,    856753858,   -812919184,    775486323,  -1376056193,    638628840,    314243536,   1030626207,    644050997,     73923896,    362270613,
+             236584904,   1463240891,   -223614432,    435371594,   -751940030,   -124274553,  -1991092884,   1579624267,   1249632649,    157589625,   -345229739,   -366245207,  -1399995986,   1651729983,   1965074340,  -1108970305,
+            1163690769,   1732013523,  -1461252895,    669755552,   -476503675,   -264578685,    -32813949,    288222188,    -25734262,    106040916,   1654395626,   -365148479,   2014455846,  -2040447994,   1351639280,   -919975757,
+           -1970412139,    -47306532,    222377665,   -363434917,  -1091717516,   2090685531,  -1221091649,  -1729649190,  -1239406708,   1064945398,   -105437479,   -419675255,     74701669,    -12862899,   -498269844,   1566898997,
+           -1872838355,   1596887574,    485902962,    469225597,   -881763553,   1307841032,  -1642872487,   1388543045,    379792876,   1095683384,    840780732,   1934378038,   1851278350,  -1359389423,    130868458,   -313448799,
+            -663624816,   1031714153,   -608443411,   -205137499,  -1849464427,   1973593637,   1068741808,  -1420655961,   1188762305,    954044841,   -995454462,  -1818101092,  -1937201943,   -324541290,  -1520603933,    572873173,
+            -554764496,   1051557081,  -1245136076,   -985349536,    329320398,   1787901464,    -37803304,  -1759310177,  -1463492617,  -1861729663,   1251768782,    256937091,   -779036948,  -2049893864,   1256022877,   1228075657,
+           -1550195255,   -611319853,   1190797155,   2047604112,   -576077160,  -1532843331,  -1324899394,   -159729560,   -622525946,  -1080302767,   -236033484,   1895243903,   -410123689,  -1944154157,   -681781021,   1208453003,
+             579595878,   1303914051,   -145607082,   -131567277,  -1917288455,    894217359,   -175688726,  -1585480723,    663691440,  -1140068263,   -641711178,   1596080008,    629197693,    976422358,  -1570451095,    525923776,
+             895046136,   -504151767,   1602553020,  -1233054923,  -1798474837,  -1488857895,   1055782627,    261863143,   1879276655,    488240679,   1910982611,  -1919441259,    370435945,   1265230086,  -1293284428,  -1503576227,
+            2076963035,  -1379628250,   1157098875,   1984461153,  -1947837397,   1705880124,   1453607404,  -1233649748,   1479943773,   -863878721,   -862415630,   -736723275,    940306358,  -1596000684,  -1174889953,   -615723892,
+            -885006597,  -1796723178,   1844159055,   -188942309,   2107251811,  -1675486996,  -1009475178,   -859263556,   -431866963,     -9593673,  -1878920923,   -104853791,  -1535224994,    -69315537,    586690130,  -1292234796,
+            1378749456,   -301873019,   -319297563,   1677205851,    292450579,  -1289441171,   1788113680,   1907606333,   1464711611,  -1372023606,  -1978832445,  -1772259768,   1949124464,   1818322887,  -1138036603,   1249727628,
+           -1474866449,  -1868013169,  -1384567593,    717007936,    954189997,  -1900561040,    738470389,   -158973180,   1732860784,   1936031206,  -1133354740,  -1173166665,   1432976712,    852636081,   1732064691,  -1831788120,
+            1273933579,    455403217,   1988395890,    106493468,    506092152,   -610530423,   1698053512,   1311747476,   1969503012,  -1887461759,   1613543073,    903200334,   -737865837,    325656800,  -1234001200,   1492148864,
+            2009861533,   -368262605,   1091338541,   2076108119,   -961392337,   1835877112,    316250307,   -853333391,  -2125443777,    815363504,   -798707803,   -158146540,    690786114,   -530775684,   1203556940,   1611485582,
+           -1661412270,    -53184506,   2126287444,   -232222229,   1559486057,    283532250,   1202760418,    932144172,   1082594656,   -570104011,    413509167,   -995027177,   -996477516,      -540544,   -745537167,   -712135469,
+            -996294983,   -592787198,   1889840948,   1314628747,   -394266926,   -682316577,    456447239,   1728806063,   -396279614,    -43387643,   1915717013,   -861574144,  -1078710588,   -561401249,   1111464540,     63643984,
+           -1693870413,   -968369980,  -1053148188,    708799038,   1883537988,    373371671,   -156410415,  -1596483236,  -1846890431,    888692915,  -1025632583,  -1666477591,   -343066267,  -2059058792,    641501628,  -1744347292,
+            1648632991,   1743540146,   2020952406,    164014499,    990508262,   1706408228,  -1236471842,   -347116260,   1843634523,    827255665,    300519853,  -1265974830,   -547247177,   -583064554,  -1995437077,    689210107,
+             -93151393,    835365056,   1706367315,  -1605902756,    200954895,    431093688,   -277573364,   -928486713,   -552221973,    145432789,   1128919795,   1675095586,   1930359882,   1215849501,  -1447770583,    657776490,
+            1885869860,  -1629237204,   -868897479,  -1258169760,   1828140195,   -883850439,    463933909,   -347361158,   1478116648,    801176896,  -1501915899,   1017335748,  -1654508882,    123994786,   1588785290,    791166651,
+           -1523108535,    340411166,   -496474762,  -1189711141,     -7392628,   2045171250,  -1245366209,    834787230,  -1346883181,   2034209454,    737043362,    898803323,   1983089087,  -1845404320,      9585188,  -1180608323,
+            1665100606,   1949474222,   -211115008,   1151308295,  -2132174259,    913126312,  -2085061672,   1419864120,  -1134542954,    -53833957,   -246913211,    468382370,  -1759479323,   1136686211,   1307012488,  -2036299559,
+           -1346099736,    314743106,  -1683101865,   -947151948,   -234529696,  -2103334293,   -279256894,     -1484257,  -1053953017,   1801205399,    941594454,   -874119215,   -672865187,    762284205,  -1494975451,    486607927,
+            -898264389,  -1711861093,   -212572760,   2106484281,  -1610786470,   1352525590,   -837779586,   1568282001,   -593019125,  -1146260782,  -1595879979,   -640781858,   1107692311,   1547132709,  -1928385535,  -2057772805,
+             634887038,    329772618,    942136006,   -864405576,    501883884,   1537141484,  -1180626836,   1123055420,   1090885851,    421662750,   2033111605,   1710917425,  -1118058244,     74321279,    257328195,  -1199940697,
+             208625996,   -442341447,    808119183,   1166827075,   1177417517,  -1856155370,  -1464837036,    -60624923,  -1306220638,    -91104698,  -1434621430,    548899241,     37351476,   1478278431,  -1255061434,    248470035,
+            -104642597,  -1865169521,   1418373655,  -1660810523,  -2129015436,    154612798,    276575732,   1930338442,    179503250,   -929294855,    -39452027,  -1377657544,   1442322193,   1137511318,   -432158653,   -984801987,
+             743099148,  -1118893528,   -904123623,  -1273146363,  -1884800406,   -803169061,   1254123158,   -484252077,    317646844,    404246525,  -1230293916,   1121445742,    -19657507,    652967153,  -1055406692,   -468950719,
+           -1493532921,  -1447624258,  -1369679689,  -1517000228,   -145853307,   1518006526,   1591195514,  -1475557146,   -909722097,   2103182976,   -406830579,  -2124025254,  -1804819507,  -1357512858,    567321869,    409048156,
+             567805180,   1749009386,   1762759722,  -1770324077,   1271140844,    468219092,    955792405,   1911965665,   1876314424,   -718200715,  -1278883927,   1392281730,   -120519585,    851473793,    245054754,    -33369039,
+            -284877584,   -479534880,   -212346563,   -122017521,  -1461429983,   1331007370,   1788621721,   1739036536,   1446350953,  -1985448033,    685528610,  -1386434659,   1368233993,   2021786790,   1596478397,  -1716635278,
+           -2011083017,    171876097,   -311529197,    687812052,    377000657,  -1055547517,  -1499047842,  -1818434951,   -120863666,     33888043,  -1387509273,   -541540700,   1162597745,  -1331415338,   1931708792,   -850270000,
+             663845594,   1536495943,   -322924971,  -1380272203,    261190298,   -204874428,  -2104974031,    883819928,    155808204,  -1454446035,   1323388464,  -1696505728,   1549800285,   1018150463,  -1327715703,  -1582480640,
+            1013659809,  -1820360082,   1666498787,   1406120540,   -196541482,   1248470531,  -1250433281,    836375878,    177646854,  -1927020253,   2145878321,    689712096,   -596605921,    348283199,   1916993096,    481356808,
+            -339687826,   1219340319,    718895887,  -2007521340,  -1859185806,   2042164737,    -58146784,    742449142,   1930754708,    780832111,    715056441,  -1393886151,     -8150527,   -599607443,   -537300865,  -1212516084
+        };
+
+        for (int i = 0; i < refInt.length; ++i) {
+            Assert.assertEquals(refInt[i], mt.nextInt());
+        }
+
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/random/Well44497aTest.java b/src/test/java/org/apache/commons/math/random/Well44497aTest.java
new file mode 100644
index 0000000..6618ac1
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/random/Well44497aTest.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.random;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class Well44497aTest {
+
+    @Test
+    public void testReferenceCode() {
+        int[] base = {
+              740849862,  1202665156,  -199039369,  -259008301,  -291878969, -1164428990, -1565918811,   491009864,
+            -1883086670,  1383450241,  1244617256,   689006653, -1576746370, -1307940314,  1421489086,  1742094000,
+             -595495729,  1047766204,  1875773301, -1637793284,  1379017098,   262792705,   191880010,  -251000180,
+            -1753047622,  -972355720,    90626881,  1644693418,  1503365577,   439653419,  1806361562,  1268823869
+         };
+        int[] init = new int[1391];
+        for (int i = 0; i < init.length; ++i) {
+            init[i] = base[i % base.length] + i;
+        }
+        Well44497a mt = new Well44497a(init);
+        int[] refInt = {
+          -1464956854,  -1524360321,    986845646,   -182050548,   -818943186,  -1744848370,   1392434650,   -182648505,  -2026593838,   1254866610,   -410459761,  -1392048371,   -968730026,   1485793687,   -728749746,   -112685463,
+            275126404,  -1101838984,   1193096287,    443511615,   -510869213,    549869992,   1974458428,  -1217587840,   -335835016,  -2048974745,   1066947099,   -611611187,   1978925459,    688164478,   -463344808,     56995910,
+            699288809,    606392470,    117418673,   1948706703,   -485598135,    385841705,   1725261146,   -919553921,     70643668,   2128611684,   1720197347,    738706713,   1162026860,   -611442152,   1469145601,   2051653750,
+            609067755,  -1971782890,   -971114565,    776260144,   1619791127,  -1547233838,   1502505722,    913168193,   1761269649,     81782996,     62251540,   1519079156,   1239007000,    489633356,   -800433470,  -2107278046,
+            495320431,    269446836,  -2013306553,   1074614697,   1645125348,    584369930,   -405429577,   1211134012,  -2060113546,     -2047824,   -443978800,    271218497,  -1002185964,   1519315874,   -695096464,    -79101601,
+          -1521653608,    192426133,   1159511202,  -1354494985,   -477280535,    583522228,   -661741458,  -1251175621,   -369487281,  -2015449518,  -2102058930,   -645264919,   -925270025,  -1674575999,   1363844609,   -831732660,
+          -1668989157,  -1861246633,     83763283,  -1056074975,   -519054258,  -1546386261,   1691674654,   -885968657,  -1189571815,   2095154843,   1686743191,  -1859471265,   -261593938,   1721982136,   -491120252,   -949699153,
+            642525852,  -2005306625,  -1004765905,    742736856,   1653443876,    788423835,   1536155740,    879514143,  -1510757104,    115238646,     28600662,   1485490803,   1272460710,    523153480,   -766782926,   1332478031,
+            528775440,    302965264,  -2046891123,   1108139271,   1611601128,    550846467,   -439082190,   1244786747,    941120547,    -35568474,   1756370964,    304870369,   1902684028,   -408710726,   1673189520,   1180987663,
+          -1488131864,    158973303,    154514890,  -1387953397,   1453732833,  -1342263302,   -628153633,      4710424,    619931109,    721411332,  -2135645486,   1688696681,   -891749588,  -1641122924,   1397432310,   -865254619,
+          -1635468227,  -1827787970,  -1311416657,  -1022618057,   1411688086,  -1579840139,   -637954674,   2115653281,  -1155985079,  -1043532593,   -374286955,  -1825883832,   -227940643,   1688394137,   -524577925,   -983222470,
+          -1955769926,    626525757,  -2009760930,  -1855453635,   -676923169,    754966926,   -291202391,  -2126042921,  -1477304277,  -1409345382,  -1264640578,   -441993991,    -17611930,  -1576809974,   2137694350,   1299022733,
+           -762509116,  -1087399293,    819303572,    -14571174,   -719035481,  -1644675278,   1492736905,    -15038081,    974773023,   1087127339,   1790024863,  -1493135734,   1936273291,   -442361741,   1639666948,   1147532756,
+            174955156,  -1537685747,    187972574,    275303083,   1420277149,  -1375787574,   1873043153,     38164241,    653451946,    687758113,    899667071,   1722219976,   2146668333,    587401069,    -26582672,   2034645447,
+           1401801794,   1043291001,  -1277898614,   2116116828,   1445274301,    150534325,    469242183,   -937704471,    171074779,   -204638071,   1269913689,   -771734064,    -12280461,  -1182158859,   1704390140,   -263303749,
+           -848503723,  -1822849148,   -634064465,   1130988864,  -1515750310,   -908815400,   1487214333,    994482967,    853103628,   1711185413,   1520342001,   1067859186,   1693632130,   -603831333,    292236742,   -800655385,
+          -1467184928,    221125007,  -1697377800,   1293953144,   1730537111,   1073329737,    519625212,    689636032,   1127394154,  -1496469136,  -1214585810,    822152197,  -1572579275,   -527866383,   -996792678,  -2058452887,
+          -1133767559,    576275042,   1579109209,   -295089371,   1502267384,   -724281876,   -911879875,   1131096177,    333026744,   1238706603,   1067340063,   -745697708,   -973992204,   1560446744,   -664017057,   -616056490,
+           1099714049,    674159948,    383625825,   1411443110,   1862818263,  -1896254899,   1322476914,   -719144734,  -1540101945,    988154902,    781856577,   2013381051,  -2059071359,   -142073207,     60252832,   2052050421,
+           -666391497,    376633738,   1663011481,  -1706886481,  -1870003191,   1003819645,    898131216,    778824906,   -656875645,  -1730811011,  -1751653787,   2056079904,    231977636,   1831419220,   -465545074,  -1505266471,
+           1034419975,   -133864043,   1876779821,   1879792902,   -100100435,   -959264741,   -472668436,    203584096,    -46980157,  -1478047098,   -979669209,    809008494,   1279644171,   2055793632,   1385672419,  -1756428826,
+          -1790481031,  -2089665073,  -1608595011,    457322987,   1267418945,    -19541848,   -796352273,  -1049973752,     30940894,   -539710199,  -1097391703,   -779353550,  -1328320498,   -735447662,   -918513196,   1516945649,
+           1218919237,   -251287485,   1826366637,    353082340,    889839220,    399638904,  -1462573609,   -618450466,   1429903706,   2095548034,   1486594475,  -1053248922,     74346322,   -357998703,   1790710495,   -146359619,
+           1581657509,   -797737661,   -920778913,    608399665,    646679359,   1861775150,  -1014371223,    476735306,  -1577737028,    383018939,   1234592859,    344770283,   -472763155,    187217985,   1245828866,   1936329359,
+             61243025,  -1979390025,    903671173,    302699505,  -1677126111,  -1194113496,    835857595,    706998946,     70931462,   1374113464,  -1464459699,   -231081598,   1366205112,    396990527,  -1615015619,   -968458597,
+            457632575,     24361353,  -1120685182,   2101590608,   1654666456,  -1208442054,    579414359,   1078056578,    217408674,  -1560683025,    815178420,   1219326466,    450032327,    774403237,     54597342,   -664057229,
+            447132403,     50603973,    435640301,  -1224073863,  -1339908037,   1775470944,  -1378119263,  -1375189988,  -1287971162,     29816317,  -1313418882,  -1824967031,    443540716,     11064217,  -1463969487,   1967601549,
+            124474667,   1230898256,  -1741455555,    561643750,    933295231,   -923145874,    245538199,    289478386,    200552280,   -268887021,  -1598221376,   1236123270,    318325803,    773964550,    191670680,    158043961,
+           -762639146,   -416703928,   -721969492,   1664330785,   -584949010,   1509045840,  -2066001147,   1728613092,  -1103375821,  -1262079070,  -2034789427,   -418216342,   -546365126,   1235751589,   1639799329,   2085089663,
+           -697590049,  -2007054256,   -147701903,    209371702,  -1868450893,   1241065116,   1537364837,  -1035970557,    318040217,    150492098,   1841159805,   -491979749,  -1275490577,  -1759443566,   -697538216,  -1589624976,
+           -678703557,   -189067001,   1539472677,  -1396089831,    271512148,    180483983,    483714313,    703861378,   2122114992,   -600097045,    522009268,    160429181,   -744428886,   1541223403,  -1211039718,  -1167643980,
+           1551471162,   -816207368,  -1429258613,   1350901561,   1934120609,   -961643277,   -214772286,  -2128270227,  -1561239720,   1493926966,   1376671007,     94966082,    221846150,   -164351411,    -51309876,    497148497,
+           1233668542,    266257753,   -773473851,    953946385,    420815294,  -1390653175,   1834391782,      4704447,   -891751440,   -744104272,  -1082756642,   1431640408,  -1912055536,   -159789461,   -704946016,   1956368139,
+            642279822,   -374415338,   1562655802,   -272964020,   1071498305,    667364168,  -1546405154,    341389690,   1360662999,    377696332,   -437020076,  -1668574556,   1242655350,   -756555890,    645954261,   1914624235,
+           2134904445,   -247737098,    143667521,    -17668806,   1804148531,    414247300,   1030053929,  -1595215075,    887532426,    553113691,   1173830167,   -303724353,   -280418143,  -1143962122,  -1898518451,     36464746,
+           1189572700,  -1549967582,   1093341440,   -452303819,   -731023001,   1111173883,   1678013973,   -836458212,   -842956392,    212774049,   -845621791,    966282353,   -823130040,    700410571,    619075141,   -304785045,
+          -1816233676,  -1789653997,   -166914694,    690663021,   -669570330,   1098624444,   -987380984,    452844935,  -1089825546,   1221160503,   1217375341,    512281644,  -1106887134,   1665404458,  -1462594714,   -207498587,
+           -789271490,   -723469709,    512055365,   1445951882,   1692473633,   -996873493,   1445046730,    993087194,  -1666188562,   -897427329,   1008869698,   1236029718,   1499207233,   1704947182,  -1815799281,    686399988,
+           -475436580,   1588892458,    884859588,   -471913926,   -487416631,   1323960741,  -1257612174,   -468909314,  -1866654496,  -1417895838,   1707647971,    997140465,  -1358794225,   1929422460,   -605622778,  -1587468566,
+            469149623,   1121515927,    748484204,   1201983830,  -1906623591,     76398473,    261109068,   -796025669,  -1964466661,   1739898262,   -756850622,   1581369453,   1484675811,    484136467,   -705983890,  -1357931714,
+            548520423,    741992908,   1017931838,  -2078503520,   2097871343,    569233897,    -91903627,   1864053450,   -876129714,    336670307,  -1950420274,   -872316480,   -662220291,    275724295,    703565412,   1334248646,
+           -217559198,   1044090753,    743502488,  -1518545318,     20614180,   -768582053,    976522354,    -25129962,   -983639284,     71722595,   -119236393,    368844119,   -795808244,    696073964,   1379765302,    235083623,
+            666280330,  -1313689346,   -643870520,    534522699,   -250414377,  -1239276164,    159264592,  -1119503518,   1168161619,  -1366518946,  -1335653301,    248092140,   1390152547,   2051602724,  -1023547981,  -1479782621,
+          -1785785862,   1609789158,   -919124123,   1703200068,   -852553456,   1573706142,   -376011685,    305068766,  -1231775451,  -1536883494,   -125122369,   -896696968,    852651567,   -458154391,    747781704,   1173040469,
+          -1569200836,    312506093,  -1680530410,    117086271,    794587661,  -1231003908,  -1048955503,   2119305423,   1636729108,   -522378372,   1627421730,    545077470,  -1683264872,   1486496559,  -1793064672,   1908368749,
+          -1226052295,   1399248776,   -588193954,  -1289386125,    534647065,   2126245059,   -238362987,  -1244573058,  -1571832269,  -2052693379,   1494767731,   -528668449,   -980826491,   -151282847,  -1468523556,   1876349941,
+           -301654558,   1467960576,   -741720848,   -612158589,     92376910,    987915105,   1037689578,    793773489,  -1387669541,    349490139,    564784004,  -1161242130,    619703053,   2063233129,    190888106,     81845991,
+          -1482466066,    283234313,    114355492,  -1879406787,  -1283370924,  -1378903370,   -730141747,   1570738286,   -281348873,   2131743196,    795654462,   -497365688,    437612465,   1928618254,   1433118279,  -1801292119,
+          -2059248836,   -221673230,    163637697,   -411319468,    244353317,    786753178,    489172932,    464627154,   1258915212,   -229028334,   -994675463,   1931657329,   1784181437,    -97111947,   1728952452,  -1329133577,
+          -1606156362,   1341196121,   1679632329,   -796545286,  -1021125869,   1427825468,   -214986389,    250791528,   1029777000,     90661677,    602529506,   2068040879,   1483801763,      2332097,   -457467017,    672399614,
+           1542769193,   1781253216,  -1967165705,  -2052227925,  -1248173215,  -1676554121,    292413596,    209649573,   1750689340,   1946874730,   -832845570,   1774178655,   -450175610,   -431901779,    613330756,   1969434259,
+           1251099237,  -1320908513,    -50659188,    273178515,   -296290724,   1195998469,   1329813722,    759419114,   1003396150,   -274557619,   -548886303,  -2055397788,   -766678640,   -464045978,  -1835907569,   -169406709,
+            820751456,   1778613303,  -1582073956,  -1728391771,  -2075389498,  -1606584632,  -1702107251,    -15724560,     45610235,  -1967510298,   -671487775,  -1841110041,   -913365944,    869680052,   -798103472,  -1564096927,
+           -918899909,   -810066882,    428829752,  -1413487973,   -844240890,   1343914280,   -689285374,   1827745702,   -799686631,   1696465705,   -726159000,  -1381157526,   1649221296,   1791106481,  -1872852642,   -485685063,
+           1534949133,  -1611901907,   -581776031,    242740701,   -382394666,    668419384,    388297992,    748818886,    713804061,  -1783774172,  -1823401590,  -1009098384,   2071462929,   1154475522,   1309810666,  -1734475040,
+           1212095416,    988288210,  -1457428115,   1699730041,  -1804729443,  -1922824494,   1000076038,   -226555981,    131425181,  -1071582828,    357680377,   1574190179,    996651958,    965704429,    -47651768,    243931978,
+            808955117,   -652323633,    544967309,  -1199510217,    702795379,    997685748,   1593927308,   2119371055,   1451401230,    -41992913,   2033816081,  -1030495962,   1764010175,    457470691,  -2001190141,   -373358035,
+          -1950331268,  -1291674220,    642934467,  -1825725718,  -1555687487,   1664472129,    -24722338,   1899539596,     78519318,   1662555805,   1744711308,  -2142888582,  -1597853572,    118030659,   1596713428,    404304267,
+          -1350880388,    648702031,   1185458591,   1798138033,    819516445,  -1466759682,   -751277607,   -879817426,  -1931050435,   1465603177,  -1402344216,    768491239,  -1404853657,  -1915685264,  -1845859847,    313163207,
+           1239598382,   1988767047,   -555152530,  -1925665864,   -182399255,  -1392390808,     64861291,   -511875035,   1879964459,    918905020,   -840773616,    459610189,  -1522352470,  -1821396360,    977274705,    -60616465,
+          -1846727880,   1208592937,   -515359427,   1127607806,   -395032287,    491869604,   2053794084,    568321750,   1597027438,   1355613070,  -2069482724,   1899252555,    844726247,   -625112193,   1146099491,  -1037855139,
+           1203928737,   1875686061,    994108281,   1471873396,   2026801570,      4941446,  -1066074241,   -983738686,   2037429697,   -836521112,   -633388883,   1221918725,   2137035208,   -369891832,    372509548,   -110916409,
+             80517712,   -658056946,    727893428,  -1353651002,   -475459562,   -291323023,   1059377566,    591801919,   1018232602,   -348255729,   1863827426,    246032476,  -1026132864,  -1356383176,  -1224690998,    262442981,
+           1257773681,  -1738604660,     77131430,  -1320261233,     -2342727,  -1817187590,  -1883997191,   1367221809,  -1863623746,  -1132606249,    149024763,  -1228275128,   -578030399,    356914163,   2109691820,   -880313621
+        };
+
+        for (int i = 0; i < refInt.length; ++i) {
+            Assert.assertEquals(refInt[i], mt.nextInt());
+        }
+
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/random/Well44497bTest.java b/src/test/java/org/apache/commons/math/random/Well44497bTest.java
new file mode 100644
index 0000000..87f8fb5
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/random/Well44497bTest.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.random;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class Well44497bTest {
+
+    @Test
+    public void testReferenceCode() {
+        int[] base = {
+              740849862,  1202665156,  -199039369,  -259008301,  -291878969, -1164428990, -1565918811,   491009864,
+            -1883086670,  1383450241,  1244617256,   689006653, -1576746370, -1307940314,  1421489086,  1742094000,
+             -595495729,  1047766204,  1875773301, -1637793284,  1379017098,   262792705,   191880010,  -251000180,
+            -1753047622,  -972355720,    90626881,  1644693418,  1503365577,   439653419,  1806361562,  1268823869
+         };
+        int[] init = new int[1391];
+        for (int i = 0; i < init.length; ++i) {
+            init[i] = base[i % base.length] + i;
+        }
+        Well44497b mt = new Well44497b(init);
+        int[] refInt = {
+           -102003638,  -1254584449,    836441550,   1949705484,    653000494,   1579400718,    699652570,   -140738233,   1164288466,    419933874,    366568847,    780567309,   1867405910,   -350557801,   -964350642,  -1323492759,
+            191502468,    398676344,   1568976991,   1005053759,    199053603,     31083944,     74697788,  -1343941248,  -1205631880,  -1637961625,    361813531,  -1706096179,   -403340909,   1666226814,  -2034962600,   1237102662,
+          -1833248535,   1584255126,   1295661745,  -1753848945,   1208145993,    930278953,   -733716134,    192752767,   1692463060,   1727273316,   2122952931,   -809025255,   -992081044,   -895539688,   -419372543,  -1835478922,
+           2089629419,   1646590742,  -1261083717,  -1005462992,   1619627287,  -1437723182,   1619689210,   1319393089,  -1816963183,   -150214444,   -513482220,   1897815796,  -1861960936,  -1766444468,   2034653890,    585657634,
+           1867016559,    696942260,  -1536237241,   -527055447,  -1554805020,  -1063992566,   1024799415,   1782080572,  -1884276362,    129028272,   1427925968,  -1154222271,  -1383146732,  -1580532830,  -1907049616,   -104299169,
+          -1780913000,  -2090815339,  -1789809502,  -1521443849,   1226625769,   1126090676,  -2117094290,   -449575109,   -218982833,   -695554478,     35923022,   1386126825,    -95031305,   -168657023,    436674049,  -1137917876,
+          -2045134053,  -1025629865,    133144659,     64226081,  -1966942130,    700813483,    344058910,   -910646033,   -212789479,    740360859,  -1269028713,   1517763679,   -664178514,   -683718472,    -71951996,     86583727,
+          -1235669348,  -1426987265,   -166598353,    214190040,  -1436967644,    233824411,    710927452,  -1939548641,   -433607408,  -1075939594,  -1549702826,  -1310827917,   -640604762,   -696863672,  -1282162126,   -546470833,
+          -1516734192,   -513809904,   -458526835,    708926727,   -476643096,  -2108375037,     -2870478,  -1460116421,    436587555,   -948939610,   1891375124,   1944216545,    959034236,  -1267038790,  -1695098736,   1853748495,
+           1594424552,   1270099319,   1139585482,   1837434635,   -709909535,   -457524230,   -887118113,   -241703912,  -1888225819,   -751575804,   1122280146,   1194255209,    949350188,    892826516,   -791212042,   -151203035,
+           -859297731,  -1979039938,    323603119,  -1022065097,  -1804294506,   -385802891,  -2127523442,   -720380255,  -1315859127,    999649487,    335041941,  -1732821688,  -1833409827,    535715225,  -1285355653,   1206723386,
+          -1141619270,    759796285,  -1599504546,  -1988521411,   1056668895,   -852564594,   1056509609,  -1831687977,    754168875,  -1301144422,    922880446,  -1502666503,   -949791898,  -1043870198,  -1136941938,  -1649670259,
+           1342769348,   1692605059,   -132279148,  -1108038310,    -14355545,  -1611387086,   1651826569,    877600127,   1356160799,   -759125205,  -1300490081,   -414938486,   -201479285,   1958709363,   1513313540,  -1396836908,
+           1352702612,   1142506253,     52969438,   -365142357,  -1619054179,  -1368514102,   1470750417,  -1420830959,   -843909462,  -1679570143,   1447444607,    234551752,  -1507452115,  -1433234579,   -680890000,   -497305145,
+            860408898,    263376761,   1537070218,   -592353956,   1587852989,   1756653749,  -2081320633,  -1547020311,    723771611,   -883819383,   1899879513,   -268417584,   1058606451,   1665941493,  -1630340612,   -614737477,
+            891313237,   1368950660,  -1166187089,    296322368,  -1908770726,  -2120378408,   1245479677,   1879710487,  -1705947124,   1018371589,  -1715010575,  -1096078094,  -1749891454,   2130888667,    318647750,    554592231,
+           -489121568,  -1809605233,  -1697905160,   -953926536,  -2013960553,   -148884919,   1822739964,  -1466301760,    141999978,   1946526064,   1323036718,    864390149,  -2141665227,   1510602225,   1468408474,   1277039721,
+          -1368096647,    180965986,   2140852057,   -688071899,    819713016,   -154385940,  -1182972611,   1257224305,   1392607672,   1364245931,  -1768434401,    323522132,   -555278604,    474186520,  -1178901665,  -2137343658,
+           1636421121,   1398987084,   1276656225,   1013316006,   -955917865,  -1537149363,   -179145358,    342862050,   1172728007,    736300054,  -1114656959,  -1831840325,  -1882353535,   -442915191,  -1206488416,  -1818534411,
+             25312311,   2100297098,  -1562767719,   1762553519,  -1853194231,  -1152612739,  -2020055792,   -809572150,    848584579,   -535789699,   1817604709,   1343773216,   -602234204,   1739930964,   -833790834,    501215449,
+           -730104057,   1217783189,   -681773267,   -611951354,    978387629,  -1516811237,    974303980,  -1389665696,   2091099075,   -727528826,   2116026151,    271935854,    613242379,  -2100429856,    190004963,  -1629612570,
+          -1362888327,    175094223,   -917873219,  -2008308245,   -401946815,    504218792,  -1966525201,      4106248,    164895454,    226502921,    655865257,   -610528718,    189428750,   1055978898,     17603028,    591024369,
+           1127922501,  -1546639293,   1994174637,   -724136988,   -673919372,  -1665002120,   -612145705,   -793102882,  -1904763558,    757565058,  -2091240021,  -2123324826,  -1518702766,   -802889839,   -223045921,  -1509216579,
+           1195556261,   2079259971,   -903969953,  -1781800655,   1834950463,   -956531922,  -1152550807,  -1116052662,   -348802884,  -1395330117,    -91758501,    -19115285,   1926930669,  -1015793599,    545904386,   1617858191,
+            716963473,   1917089719,   -980914811,   -212646927,  -1634695647,  -1857924568,  -1462257477,   1273750178,   1060328454,   -361604424,    867932749,    451213698,    405780152,   1165233215,   1877230909,   2103114395,
+           1644330815,   1252998537,   1961603970,  -1533101488,   1790456024,    -38226118,  -1306744489,    713676418,  -1535871838,   1378109935,   -338185548,   1647669762,   -477465913,    203482277,  -1949756706,   -503326093,
+           -638704909,    320186309,  -1435581459,    907446649,    -77384645,    537368928,   -335347295,  -1912061924,    547819174,   -225549827,   1089455486,    463516297,   -240127764,    -85895271,   2053179697,   -287394931,
+            921878827,   -933362608,  -1178670275,  -1200942874,   -672571265,    574422382,   1441734039,  -1814493454,    165751640,   -176907245,  -1170992192,  -2123252090,  -1435971541,   1591853830,   -885477992,   -792847559,
+           1359875286,   1038392904,  -2027255124,    687291425,   -165513490,   1391146576,  -1387080955,    794663652,   -807189965,    667820962,   -545384499,  -1371368854,   -689031878,   1504805541,   -752825823,  -1920047745,
+          -1884874017,   -350566320,   -197152911,   -181743050,   -798415949,   -915922276,   1790690149,   -363997181,   1923116185,  -1326427198,  -1621079427,  -1997440997,   1798118127,  -2053110382,   -159879848,  -1286787216,
+           1046436411,   1832030471,   -389092059,     71686169,    -76025260,   1914270607,   1854169353,    872157826,  -1774323792,   -575165717,  -1919931724,   2051498109,  -1176174934,   -883578901,  -1253047270,  -1310573900,
+            245466682,  -1784824328,  -1319912821,   1377340217,   1364313761,   -408687373,    142333378,  -1952335763,  -1703126184,    316314678,   2030555423,    488640834,  -1783293306,   2116925005,   -428337460,    -42966447,
+           -476226114,   -325172903,  -1690748475,    852791569,     26490302,     85251337,  -1374975770,   -376283969,    982639600,    595119792,    376403422,   1574509912,  -1509664496,  -1901241749,    -59019104,    358566667,
+            341667214,    184541206,   -550950854,  -1897143732,   1595753537,  -1236127928,   2014297822,  -2033179270,   -669806121,  -1927596980,   1010735700,   -581795164,   1922398838,  -1456743538,  -1633813803,    323177707,
+           2002098813,  -2099067658,    277393729,   -671911622,   -384463053,   2028267908,    367391785,   1270052637,   -172839030,   -650486693,   -831800809,  -1255138113,   -137512799,   1904317942,     -8229811,    707361898,
+           -276859812,     50417442,   1487081728,   1577776181,   1994451303,   1237303035,   -602016235,  -1905218276,  -1895725672,   1172978849,    801129953,  -1819485071,   -587985848,  -2010386741,  -1645226427,   -850866837,
+            816998708,    357665811,   1955856762,   1617902189,  -1013761306,    146782652,    904185608,   -500146809,   2085848310,   1917713975,  -1823786899,   1994184748,    789196322,   1766857258,   1770685286,     58213029,
+          -1699628994,    346827379,  -1274423227,     -5079670,   -193099487,   1020296939,  -1795904054,  -1951053094,    -43782418,   -375403393,   1026761026,   -207269610,   1364563521,   1578793454,    457809423,   -534138380,
+          -1052938788,  -1897101526,   1449976516,   2052800058,  -1145169719,   1476303269,    370625650,   -325249282,      2165984,   1631432802,   1032336355,  -1292978191,  -1810172401,    725725820,  -1162678778,    702624490,
+           1387673527,    981825943,   -556408212,  -1108487850,  -1782136935,   1582316425,  -1752682164,    307235003,   1386486299,  -1343636074,   1936875586,  -1265279891,   -345847069,    928241171,    239050350,   1859358526,
+           -664776217,   -823267892,    346651710,   -867656288,  -1907921425,   1362445801,    541145461,   -192727350,   1649366606,    244694627,   -488180018,    214008256,   2032125437,  -1678591993,   -264593820,   1309017286,
+           -652451998,   1845366657,   -703990120,   -550186406,   -630285276,   1782372955,   1650564210,  -1127904234,  -1407983860,  -1119422877,  -1565976361,  -1913545385,    549841420,  -1410323732,  -1964467146,    228296551,
+           -421026422,   1929094398,   -266906424,    264810315,  -2008122665,  -1088278148,    141242192,   1871293282,    234634067,   1724159838,   1638271051,   -837713428,   -657941661,    168093988,    708605363,  -1881612509,
+          -1810496006,   -193495322,   1889982309,  -2050702012,   -693694192,  -1249780322,    718733403,    -76349730,   -188217051,    920912090,  -1814078273,   2013358456,  -1948845521,   -198407575,  -1248904632,   1182772565,
+           1236791612,  -1297489171,  -1958468586,   1437011007,    390460941,    113068796,   1247982993,   2102889679,  -1563524844,   -128174212,   -754095070,  -1461699362,    943615640,  -1013270737,    221253920,   1514140013,
+           1596946745,    674222984,    616356702,   1811224435,  -1764322023,  -1653707581,  -1702404459,    390678142,   -209506765,  -1398278531,   -117061517,   1625861343,    659048069,  -1490678943,    846536668,    210715637,
+           1855458786,   1727745280,   1086729456,   1109111683,   -985298098,  -1813777567,   -954599702,  -1522494031,   1166103515,   -191868965,  -1048777852,   -661271058,   1161457421,   1509090409,   -919753558,   -155431193,
+          -1774302994,   -366390263,   2090138916,   -693431491,  -1693888428,   1846774454,    925855693,    474383470,    208889079,    382195164,   -283005634,  -2095134392,    579927985,   1390765326,  -1766119865,    900457129,
+          -1503703236,    974952690,   -107714111,    381338452,   1187256613,   -860560742,    524103620,   1499506130,    197755276,   -790802926,   -406920967,  -1972219791,   -665721155,   -113336203,   1037154436,  -1185441801,
+           -745541706,   -546274471,   1988928457,  -1975403782,  -1167172845,    777779004,  -1560935061,   -140258712,  -1243598232,  -1394149587,   -785002782,    311842991,  -1025469277,   -605350463,  -1251538057,    537203966,
+            597777961,  -1845767072,  -1556349193,  -1491015509,  -1935936671,   2093498487,   1908270236,   -315396187,   1356362300,  -2025658518,    630119678,    276190559,    510123398,  -1266145363,   -170152124,   -151540077,
+           -477900187,   1895894303,   1870333068,  -1169891437,    353366620,   2111175941,   1691245786,   1318765802,    -90993610,    921309517,    118241505,    367005284,   1624861072,   2010785894,    865255951,   1717799691,
+            -80757664,   -644944841,    136999836,   -341686875,  -1908076090,  -1968934200,   -346397811,   -184213520,   -511811333,  -2118173466,  -1086490399,   1795322855,   -635494328,    415716276,    851044432,   -904636831,
+          -1972230341,    -64337858,    571177016,   1248814747,  -1351030778,    457872680,   1843549954,   1718960038,    815088665,   1812961065,    360686952,  -1356586646,   1657802416,   1776192945,   -786723490,   -342254407,
+           -236653811,    771014701,    906386785,   -308057635,   1907957462,    206000440,    -42143480,    900403654,   -917549795,   -310520796,  -1713627766,   2061136240,   -377977839,    891282946,   -821163030,    328143584,
+           1503793080,    551621842,  -2086273683,  -2070526343,     91195293,  -1654389038,  -1035734266,   -336619597,  -1220221027,  -1468468844,   2105626873,   -841372573,   -122707018,  -2013073683,    494461000,  -2054807734,
+            -67946259,   1914163407,   1941835405,  -1027244745,   -768123277,    419129844,   -275750260,   -171533009,     97756174,    -17651409,  -1578102255,    995291430,  -1587462977,    692904675,    951632643,   1882101293,
+          -1546298756,   2018418068,  -1790777661,   1542305514,  -1437624383,    469587009,  -1647853474,  -1318279028,    497228822,    726733469,   1693133452,  -2091185798,   -209017732,    126386499,   1056958932,  -2105494133,
+            754067324,     96463951,     83701151,   1101658289,   1485852701,    553783806,   1898769881,  -1072031442,   1438062141,   1992540265,   1152252136,   1019391719,   -175951257,     -6691216,    989789689,    968359367,
+          -1330392786,   1704963399,   -998432914,   -948060232,  -1921688855,   -975840920,   1360273515,   -872810459,     12676907,  -1908050756,    883609616,     65641549,   -200365398,   1386653304,  -1203665071,   1878689007,
+            426262328,    315375145,   1900325181,    703658494,   -765404895,   1070155172,   1399748900,   -804264234,  -1619419026,   1347225486,    230635292,   1093717835,     14020583,  -2107039873,   -968325341,  -1679158691,
+           1959784097,   1065690797,   1090615161,   1311445364,    865835426,    870016646,    574122879,   1842697922,  -1289210431,  -1914001560,   1672467629,   -900366331,  -1524066872,    136503816,  -1910431892,  -1431958329,
+           -830367152,  -1316233970,   -801974860,   1560669382,    -81784810,    401822577,   -949103202,    943897151,   -722666726,    -96825841,  -1092898846,    230567004,    -70355840,  -1398069192,   -312953142,   1475420133,
+           -622491023,   1661205388,    -19071322,      6024591,   1473041593,   2053897978,  -1346768903,   1484764721,  -1552461890,   1287146711,   1613069307,    902497864,  -1504480063,    375292915,   -836353108,   2047602411
+        };
+
+        for (int i = 0; i < refInt.length; ++i) {
+            Assert.assertEquals(refInt[i], mt.nextInt());
+        }
+
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/random/Well512aTest.java b/src/test/java/org/apache/commons/math/random/Well512aTest.java
new file mode 100644
index 0000000..37c5448
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/random/Well512aTest.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.random;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class Well512aTest {
+
+    @Test
+    public void testReferenceCode() {
+        Well512a mt = new Well512a(new int[] {
+            740849862,  1202665156,  -199039369,  -259008301,  -291878969, -1164428990, -1565918811,   491009864,
+          -1883086670,  1383450241,  1244617256,   689006653, -1576746370, -1307940314,  1421489086,  1742094000
+       });
+        int[] refInt = {
+            1634813289,   1876773016,   -973836208,  -2130023652,  -1045460084,  -1834384857,   1691032973,    609714289,
+            2033920362,    555915483,      6680992,   1958127415,   1866469645,  -1471336965,   2049178762,   -192324811,
+           -2056050066,    810879705,   1405046309,   -781317118,   1012782311,  -1045081032,    728377508,   1473511660,
+             290489070,    326666761,   2018299979,  -1876688058,   1239968501,   1464625040,   2025151042,   -101397407,
+            1387902041,    210959839,   1366359326,   -476473433,    153180037,  -1607631523,   -506743495,     17888738,
+             313865008,   -340504498,    586684079,   1243699375,    753162229,   -646761694,   -739189655,   -210120185,
+           -1856358726,   -628255542,  -1812798197,   1416288088,   1077967722,   -846846208,   1379850409,   -580183344,
+              -1858959,    210859778,    295841424,   1492774865,  -1415543680,   -344870570,  -1942779197,   1549510646,
+            -389544849,    314254218,     11784988,  -1311757368,   1719514841,   -764610517,   1296788970,   -994707050,
+             783854563,    422654144,    387639079,   1219688425,   2144352572,   -834212874,  -1036550358,    935909479,
+            -568610842,   1327498837,   -588933178,   1910065754,    -40851599,   -182063170,   1302731458,    541311559,
+           -1647345522,    805224371,  -1721196679,   1518507830,   -952689880,   -433276260,    509675254,   -777259954,
+            1277810106,    284054896,    936042202,   2036836351,   1956412426,  -1186403024,    287795400,   2135311211,
+             720485927,   1500695024,   -281656583,  -1277937322,  -1628968482,   1242814831,  -2030700974,   1473867890,
+             440813549,  -1357033971,     28384076,   1602731216,   -641465746,   -609054347,    635938444,   1472898176,
+            1476894555,   -747974186,  -1590337055,   -884242108,   -389736197,  -2066984505,   1087103272,  -1236446290,
+              31657463,   1835715432,   -468439078,  -2132633204,   -434609235,    258308151,   1851926761,  -1630139159,
+           -1344617241,   1969204215,    619463174,   -174392624,    207475487,  -1619828078,   1327980298,    -83968178,
+             445951782,  -1786230541,      6279288,   -580982231,   1550645552,   2006533941,    275746007,    455676647,
+            2019637349,   1115547704,  -1313120106,   -516213449,     73752461,  -1382448112,    398589620,   1319888048,
+           -1595572334,   1566934536,  -1735685764,  -1509545339,   1458173912,   -549395819,   -618827040,   1516624531,
+            1900757187,  -1454200688,    965524719,    488355065,  -1869294316,   -810641680,  -2059428251,   1454656431,
+            1329120541,   -232185900,   -994996943,   1855980910,   -452077812,   1565630611,    759842266,   1241435187,
+           -1390456063,   1946400597,  -2032319771,    683667881,    905911106,   1983310786,    120010546,    526018017,
+           -1946881912,    205004987,  -1307250612,   2130980818,   2052864161,    189839787,   1789478047,    406168885,
+           -1145186347,      8507675,   1277188815,   1492619042,   2009819675,  -1627411598,   -851016743,  -1828234956,
+            1962622506,   2140398255,    236935165,   -337237772,   1263419111,    516775236,   -335741025,   1391328225,
+             455979249,  -1457534664,   -657606241,    485648133,   1762116343,   1194889600,    817834937,    321150162,
+             131159182,    290277758,  -1876924740,  -1770401129,   1291602973,  -1003642974,  -1580211929,   1520422021,
+            -399171579,    -24315308,    453805396,   -659197747,   -205656847,    466526550,   1444397201,   1178091401,
+           -1157268826,   -602394028,  -1370668795,   1614896435,   1699071659,   1864753793,   1888518358,  -1721244514,
+            1812776767,    668822227,   -297283057,   2130183333,  -1169618692,    912860240,  -2028253096,   1244694278
+        };
+
+        for (int i = 0; i < refInt.length; ++i) {
+            Assert.assertEquals(refInt[i], mt.nextInt());
+        }
+
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/special/BetaTest.java b/src/test/java/org/apache/commons/math/special/BetaTest.java
new file mode 100644
index 0000000..76642bc
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/special/BetaTest.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.special;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.TestUtils;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+public class BetaTest extends TestCase {
+    /**
+     * Constructor for BetaTest.
+     * @param name
+     */
+    public BetaTest(String name) {
+        super(name);
+    }
+
+    private void testRegularizedBeta(double expected, double x, double a,
+        double b)
+    {
+        try {
+            double actual = Beta.regularizedBeta(x, a, b);
+            TestUtils.assertEquals(expected, actual, 10e-15);
+        } catch(MathException ex){
+            fail(ex.getMessage());
+        }
+    }
+
+    private void testLogBeta(double expected, double a, double b) {
+        double actual = Beta.logBeta(a, b);
+        TestUtils.assertEquals(expected, actual, 10e-15);
+    }
+
+    public void testRegularizedBetaNanPositivePositive() {
+        testRegularizedBeta(Double.NaN, Double.NaN, 1.0, 1.0);
+    }
+
+    public void testRegularizedBetaPositiveNanPositive() {
+        testRegularizedBeta(Double.NaN, 0.5, Double.NaN, 1.0);
+    }
+
+    public void testRegularizedBetaPositivePositiveNan() {
+        testRegularizedBeta(Double.NaN, 0.5, 1.0, Double.NaN);
+    }
+
+    public void testRegularizedBetaNegativePositivePositive() {
+        testRegularizedBeta(Double.NaN, -0.5, 1.0, 2.0);
+    }
+
+    public void testRegularizedBetaPositiveNegativePositive() {
+        testRegularizedBeta(Double.NaN, 0.5, -1.0, 2.0);
+    }
+
+    public void testRegularizedBetaPositivePositiveNegative() {
+        testRegularizedBeta(Double.NaN, 0.5, 1.0, -2.0);
+    }
+
+    public void testRegularizedBetaZeroPositivePositive() {
+        testRegularizedBeta(0.0, 0.0, 1.0, 2.0);
+    }
+
+    public void testRegularizedBetaPositiveZeroPositive() {
+        testRegularizedBeta(Double.NaN, 0.5, 0.0, 2.0);
+    }
+
+    public void testRegularizedBetaPositivePositiveZero() {
+        testRegularizedBeta(Double.NaN, 0.5, 1.0, 0.0);
+    }
+
+    public void testRegularizedBetaPositivePositivePositive() {
+        testRegularizedBeta(0.75, 0.5, 1.0, 2.0);
+    }
+
+    public void testLogBetaNanPositive() {
+        testLogBeta(Double.NaN, Double.NaN, 2.0);
+    }
+
+    public void testLogBetaPositiveNan() {
+        testLogBeta(Double.NaN, 1.0, Double.NaN);
+    }
+
+    public void testLogBetaNegativePositive() {
+        testLogBeta(Double.NaN, -1.0, 2.0);
+    }
+
+    public void testLogBetaPositiveNegative() {
+        testLogBeta(Double.NaN, 1.0, -2.0);
+    }
+
+    public void testLogBetaZeroPositive() {
+        testLogBeta(Double.NaN, 0.0, 2.0);
+    }
+
+    public void testLogBetaPositiveZero() {
+        testLogBeta(Double.NaN, 1.0, 0.0);
+    }
+
+    public void testLogBetaPositivePositive() {
+        testLogBeta(-0.693147180559945, 1.0, 2.0);
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/special/ErfTest.java b/src/test/java/org/apache/commons/math/special/ErfTest.java
new file mode 100644
index 0000000..cdde1b6
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/special/ErfTest.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.special;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.util.FastMath;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Revision: 1054184 $ $Date: 2011-01-01 02:23:02 +0100 (sam. 01 janv. 2011) $
+ */
+public class ErfTest extends TestCase {
+
+    public void testErf0() throws MathException {
+        double actual = Erf.erf(0.0);
+        double expected = 0.0;
+        assertEquals(expected, actual, 1.0e-15);
+        assertEquals(1 - expected, Erf.erfc(0.0), 1.0e-15);
+    }
+
+    public void testErf1960() throws MathException {
+        double x = 1.960 / FastMath.sqrt(2.0);
+        double actual = Erf.erf(x);
+        double expected = 0.95;
+        assertEquals(expected, actual, 1.0e-5);
+        assertEquals(1 - actual, Erf.erfc(x), 1.0e-15);
+
+        actual = Erf.erf(-x);
+        expected = -expected;
+        assertEquals(expected, actual, 1.0e-5);
+        assertEquals(1 - actual, Erf.erfc(-x), 1.0e-15);
+    }
+
+    public void testErf2576() throws MathException {
+        double x = 2.576 / FastMath.sqrt(2.0);
+        double actual = Erf.erf(x);
+        double expected = 0.99;
+        assertEquals(expected, actual, 1.0e-5);
+        assertEquals(1 - actual, Erf.erfc(x), 1e-15);
+
+        actual = Erf.erf(-x);
+        expected = -expected;
+        assertEquals(expected, actual, 1.0e-5);
+        assertEquals(1 - actual, Erf.erfc(-x), 1.0e-15);
+    }
+
+    public void testErf2807() throws MathException {
+        double x = 2.807 / FastMath.sqrt(2.0);
+        double actual = Erf.erf(x);
+        double expected = 0.995;
+        assertEquals(expected, actual, 1.0e-5);
+        assertEquals(1 - actual, Erf.erfc(x), 1.0e-15);
+
+        actual = Erf.erf(-x);
+        expected = -expected;
+        assertEquals(expected, actual, 1.0e-5);
+        assertEquals(1 - actual, Erf.erfc(-x), 1.0e-15);
+    }
+
+    public void testErf3291() throws MathException {
+        double x = 3.291 / FastMath.sqrt(2.0);
+        double actual = Erf.erf(x);
+        double expected = 0.999;
+        assertEquals(expected, actual, 1.0e-5);
+        assertEquals(1 - expected, Erf.erfc(x), 1.0e-5);
+
+        actual = Erf.erf(-x);
+        expected = -expected;
+        assertEquals(expected, actual, 1.0e-5);
+        assertEquals(1 - expected, Erf.erfc(-x), 1.0e-5);
+    }
+    
+    /**
+     * MATH-301, MATH-456
+     */
+    public void testLargeValues() throws Exception {
+        for (int i = 1; i < 200; i*=10) {
+            double result = Erf.erf(i);
+            assertFalse(Double.isNaN(result));
+            assertTrue(result > 0 && result <= 1);
+            result = Erf.erf(-i);
+            assertFalse(Double.isNaN(result));
+            assertTrue(result >= -1 && result < 0);
+            result = Erf.erfc(i);
+            assertFalse(Double.isNaN(result));
+            assertTrue(result >= 0 && result < 1);
+            result = Erf.erfc(-i);
+            assertFalse(Double.isNaN(result));
+            assertTrue(result >= 1 && result <= 2);    
+        }
+        assertEquals(-1, Erf.erf(Double.NEGATIVE_INFINITY), 0);
+        assertEquals(1, Erf.erf(Double.POSITIVE_INFINITY), 0);
+        assertEquals(2, Erf.erfc(Double.NEGATIVE_INFINITY), 0);
+        assertEquals(0, Erf.erfc(Double.POSITIVE_INFINITY), 0);
+    }
+    
+    /**
+     * Compare Erf.erf against reference values computed using GCC 4.2.1 (Apple OSX packaged version)
+     * erfl (extended precision erf).
+     */
+    public void testErfGnu() throws Exception {
+        final double tol = 1E-15;
+        final double[] gnuValues = new double[] {-1, -1, -1, -1, -1, 
+        -1, -1, -1, -0.99999999999999997848, 
+        -0.99999999999999264217, -0.99999999999846254017, -0.99999999980338395581, -0.99999998458274209971, 
+        -0.9999992569016276586, -0.99997790950300141459, -0.99959304798255504108, -0.99532226501895273415, 
+        -0.96610514647531072711, -0.84270079294971486948, -0.52049987781304653809,  0, 
+         0.52049987781304653809, 0.84270079294971486948, 0.96610514647531072711, 0.99532226501895273415, 
+         0.99959304798255504108, 0.99997790950300141459, 0.9999992569016276586, 0.99999998458274209971, 
+         0.99999999980338395581, 0.99999999999846254017, 0.99999999999999264217, 0.99999999999999997848, 
+         1,  1,  1,  1, 
+         1,  1,  1,  1};
+        double x = -10d;
+        for (int i = 0; i < 41; i++) {
+            assertEquals(gnuValues[i], Erf.erf(x), tol);
+            x += 0.5d;
+        }
+    }
+    
+    /**
+     * Compare Erf.erfc against reference values computed using GCC 4.2.1 (Apple OSX packaged version)
+     * erfcl (extended precision erfc).
+     */
+    public void testErfcGnu() throws Exception {
+        final double tol = 1E-15;
+        final double[] gnuValues = new double[] { 2,  2,  2,  2,  2, 
+        2,  2,  2, 1.9999999999999999785, 
+        1.9999999999999926422, 1.9999999999984625402, 1.9999999998033839558, 1.9999999845827420998, 
+        1.9999992569016276586, 1.9999779095030014146, 1.9995930479825550411, 1.9953222650189527342, 
+        1.9661051464753107271, 1.8427007929497148695, 1.5204998778130465381,  1, 
+        0.47950012218695346194, 0.15729920705028513051, 0.033894853524689272893, 0.0046777349810472658333, 
+        0.00040695201744495893941, 2.2090496998585441366E-05, 7.4309837234141274516E-07, 1.5417257900280018858E-08, 
+        1.966160441542887477E-10, 1.5374597944280348501E-12, 7.3578479179743980661E-15, 2.1519736712498913103E-17, 
+        3.8421483271206474691E-20, 4.1838256077794144006E-23, 2.7766493860305691016E-26, 1.1224297172982927079E-29, 
+        2.7623240713337714448E-33, 4.1370317465138102353E-37, 3.7692144856548799402E-41, 2.0884875837625447567E-45};
+        double x = -10d;
+        for (int i = 0; i < 41; i++) {
+            assertEquals(gnuValues[i], Erf.erfc(x), tol);
+            x += 0.5d;
+        }
+    }
+    
+    /**
+     * Tests erfc against reference data computed using Maple reported in Marsaglia, G,, 
+     * "Evaluating the Normal Distribution," Journal of Statistical Software, July, 2004.
+     * http//www.jstatsoft.org/v11/a05/paper
+     */
+    public void testErfcMaple() throws Exception {
+        double[][] ref = new double[][]
+                        {{0.1, 4.60172162722971e-01},
+                         {1.2, 1.15069670221708e-01},
+                         {2.3, 1.07241100216758e-02},
+                         {3.4, 3.36929265676881e-04},
+                         {4.5, 3.39767312473006e-06},
+                         {5.6, 1.07175902583109e-08}, 
+                         {6.7, 1.04209769879652e-11},
+                         {7.8, 3.09535877195870e-15},
+                         {8.9, 2.79233437493966e-19},
+                         {10.0, 7.61985302416053e-24},
+                         {11.1, 6.27219439321703e-29},
+                         {12.2, 1.55411978638959e-34}, 
+                         {13.3, 1.15734162836904e-40},
+                         {14.4, 2.58717592540226e-47},
+                         {15.5, 1.73446079179387e-54},
+                         {16.6, 3.48454651995041e-62}
+        };
+        for (int i = 0; i < 15; i++) {
+            final double result = 0.5*Erf.erfc(ref[i][0]/Math.sqrt(2));
+            assertEquals(ref[i][1], result, 1E-15);
+            TestUtils.assertRelativelyEquals(ref[i][1], result, 1E-13);
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/special/GammaTest.java b/src/test/java/org/apache/commons/math/special/GammaTest.java
new file mode 100644
index 0000000..3a6d769
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/special/GammaTest.java
@@ -0,0 +1,151 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.special;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.util.FastMath;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ */
+public class GammaTest extends TestCase {
+
+    public GammaTest(String name) {
+        super(name);
+    }
+
+    private void testRegularizedGamma(double expected, double a, double x) {
+        try {
+            double actualP = Gamma.regularizedGammaP(a, x);
+            double actualQ = Gamma.regularizedGammaQ(a, x);
+            TestUtils.assertEquals(expected, actualP, 10e-15);
+            TestUtils.assertEquals(actualP, 1.0 - actualQ, 10e-15);
+        } catch(MathException ex){
+            fail(ex.getMessage());
+        }
+    }
+
+    private void testLogGamma(double expected, double x) {
+        double actual = Gamma.logGamma(x);
+        TestUtils.assertEquals(expected, actual, 10e-15);
+    }
+
+    public void testRegularizedGammaNanPositive() {
+        testRegularizedGamma(Double.NaN, Double.NaN, 1.0);
+    }
+
+    public void testRegularizedGammaPositiveNan() {
+        testRegularizedGamma(Double.NaN, 1.0, Double.NaN);
+    }
+
+    public void testRegularizedGammaNegativePositive() {
+        testRegularizedGamma(Double.NaN, -1.5, 1.0);
+    }
+
+    public void testRegularizedGammaPositiveNegative() {
+        testRegularizedGamma(Double.NaN, 1.0, -1.0);
+    }
+
+    public void testRegularizedGammaZeroPositive() {
+        testRegularizedGamma(Double.NaN, 0.0, 1.0);
+    }
+
+    public void testRegularizedGammaPositiveZero() {
+        testRegularizedGamma(0.0, 1.0, 0.0);
+    }
+
+    public void testRegularizedGammaPositivePositive() {
+        testRegularizedGamma(0.632120558828558, 1.0, 1.0);
+    }
+
+    public void testLogGammaNan() {
+        testLogGamma(Double.NaN, Double.NaN);
+    }
+
+    public void testLogGammaNegative() {
+        testLogGamma(Double.NaN, -1.0);
+    }
+
+    public void testLogGammaZero() {
+        testLogGamma(Double.NaN, 0.0);
+    }
+
+    public void testLogGammaPositive() {
+        testLogGamma(0.6931471805599457, 3.0);
+    }
+
+    public void testDigammaLargeArgs() {
+        double eps = 1e-8;
+        assertEquals(4.6001618527380874002, Gamma.digamma(100), eps);
+        assertEquals(3.9019896734278921970, Gamma.digamma(50), eps);
+        assertEquals(2.9705239922421490509, Gamma.digamma(20), eps);
+        assertEquals(2.9958363947076465821, Gamma.digamma(20.5), eps);
+        assertEquals(2.2622143570941481605, Gamma.digamma(10.1), eps);
+        assertEquals(2.1168588189004379233, Gamma.digamma(8.8), eps);
+        assertEquals(1.8727843350984671394, Gamma.digamma(7), eps);
+        assertEquals(0.42278433509846713939, Gamma.digamma(2), eps);
+        assertEquals(-100.56088545786867450, Gamma.digamma(0.01), eps);
+        assertEquals(-4.0390398965921882955, Gamma.digamma(-0.8), eps);
+        assertEquals(4.2003210041401844726, Gamma.digamma(-6.3), eps);
+    }
+
+    public void testDigammaSmallArgs() {
+        // values for negative powers of 10 from 1 to 30 as computed by webMathematica with 20 digits
+        // see functions.wolfram.com
+        double[] expected = {-10.423754940411076795, -100.56088545786867450, -1000.5755719318103005,
+                -10000.577051183514335, -100000.57719921568107, -1.0000005772140199687e6, -1.0000000577215500408e7,
+                -1.0000000057721564845e8, -1.0000000005772156633e9, -1.0000000000577215665e10, -1.0000000000057721566e11,
+                -1.0000000000005772157e12, -1.0000000000000577216e13, -1.0000000000000057722e14, -1.0000000000000005772e15, -1e+16,
+                -1e+17, -1e+18, -1e+19, -1e+20, -1e+21, -1e+22, -1e+23, -1e+24, -1e+25, -1e+26,
+                -1e+27, -1e+28, -1e+29, -1e+30};
+        for (double n = 1; n < 30; n++) {
+            checkRelativeError(String.format("Test %.0f: ", n), expected[(int) (n - 1)], Gamma.digamma(FastMath.pow(10.0, -n)), 1e-8);
+        }
+    }
+
+    public void testTrigamma() {
+        double eps = 1e-8;
+        // computed using webMathematica.  For example, to compute trigamma($i) = Polygamma(1, $i), use
+        //
+        // http://functions.wolfram.com/webMathematica/Evaluated.jsp?name=PolyGamma2&plottype=0&vars={%221%22,%22$i%22}&digits=20
+        double[] data = {
+                1e-4, 1.0000000164469368793e8,
+                1e-3, 1.0000016425331958690e6,
+                1e-2, 10001.621213528313220,
+                1e-1, 101.43329915079275882,
+                1, 1.6449340668482264365,
+                2, 0.64493406684822643647,
+                3, 0.39493406684822643647,
+                4, 0.28382295573711532536,
+                5, 0.22132295573711532536,
+                10, 0.10516633568168574612,
+                20, 0.051270822935203119832,
+                50, 0.020201333226697125806,
+                100, 0.010050166663333571395
+        };
+        for (int i = data.length - 2; i >= 0; i -= 2) {
+            assertEquals(String.format("trigamma %.0f", data[i]), data[i + 1], Gamma.trigamma(data[i]), eps);
+        }
+    }
+
+    private void checkRelativeError(String msg, double expected, double actual, double tolerance) {
+        assertEquals(msg, expected, actual, FastMath.abs(tolerance * actual));
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/stat/CertifiedDataTest.java b/src/test/java/org/apache/commons/math/stat/CertifiedDataTest.java
new file mode 100644
index 0000000..749f6cc
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/CertifiedDataTest.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;
+
+import java.io.BufferedReader;
+import java.io.InputStreamReader;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.math.stat.descriptive.DescriptiveStatistics;
+import org.apache.commons.math.stat.descriptive.SummaryStatistics;
+
+/**
+ * Certified data test cases.
+ * @version $Revision: 902201 $ $Date: 2010-01-22 19:18:16 +0100 (ven. 22 janv. 2010) $
+ */
+public class CertifiedDataTest extends TestCase  {
+
+    protected double mean = Double.NaN;
+
+    protected double std = Double.NaN;
+
+    /**
+     * Certified Data Test Constructor
+     * @param name
+     */
+    public CertifiedDataTest(String name) {
+        super(name);
+    }
+
+    /**
+     * Test SummaryStatistics - implementations that do not store the data
+     * and use single pass algorithms to compute statistics
+    */
+    public void testSummaryStatistics() throws Exception {
+        SummaryStatistics u = new SummaryStatistics();
+        loadStats("data/PiDigits.txt", u);
+        assertEquals("PiDigits: std", std, u.getStandardDeviation(), 1E-13);
+        assertEquals("PiDigits: mean", mean, u.getMean(), 1E-13);
+
+        loadStats("data/Mavro.txt", u);
+        assertEquals("Mavro: std", std, u.getStandardDeviation(), 1E-14);
+        assertEquals("Mavro: mean", mean, u.getMean(), 1E-14);
+
+        loadStats("data/Michelso.txt", u);
+        assertEquals("Michelso: std", std, u.getStandardDeviation(), 1E-13);
+        assertEquals("Michelso: mean", mean, u.getMean(), 1E-13);
+
+        loadStats("data/NumAcc1.txt", u);
+        assertEquals("NumAcc1: std", std, u.getStandardDeviation(), 1E-14);
+        assertEquals("NumAcc1: mean", mean, u.getMean(), 1E-14);
+
+        loadStats("data/NumAcc2.txt", u);
+        assertEquals("NumAcc2: std", std, u.getStandardDeviation(), 1E-14);
+        assertEquals("NumAcc2: mean", mean, u.getMean(), 1E-14);
+    }
+
+    /**
+     * Test DescriptiveStatistics - implementations that store full array of
+     * values and execute multi-pass algorithms
+     */
+    public void testDescriptiveStatistics() throws Exception {
+
+        DescriptiveStatistics u = new DescriptiveStatistics();
+
+        loadStats("data/PiDigits.txt", u);
+        assertEquals("PiDigits: std", std, u.getStandardDeviation(), 1E-14);
+        assertEquals("PiDigits: mean", mean, u.getMean(), 1E-14);
+
+        loadStats("data/Mavro.txt", u);
+        assertEquals("Mavro: std", std, u.getStandardDeviation(), 1E-14);
+        assertEquals("Mavro: mean", mean, u.getMean(), 1E-14);
+
+        loadStats("data/Michelso.txt", u);
+        assertEquals("Michelso: std", std, u.getStandardDeviation(), 1E-14);
+        assertEquals("Michelso: mean", mean, u.getMean(), 1E-14);
+
+        loadStats("data/NumAcc1.txt", u);
+        assertEquals("NumAcc1: std", std, u.getStandardDeviation(), 1E-14);
+        assertEquals("NumAcc1: mean", mean, u.getMean(), 1E-14);
+
+        loadStats("data/NumAcc2.txt", u);
+        assertEquals("NumAcc2: std", std, u.getStandardDeviation(), 1E-14);
+        assertEquals("NumAcc2: mean", mean, u.getMean(), 1E-14);
+    }
+
+    /**
+     * loads a DescriptiveStatistics off of a test file
+     * @param file
+     * @param statistical summary
+     */
+    private void loadStats(String resource, Object u) throws Exception {
+
+        DescriptiveStatistics d = null;
+        SummaryStatistics s = null;
+        if (u instanceof DescriptiveStatistics) {
+            d = (DescriptiveStatistics) u;
+        } else {
+            s = (SummaryStatistics) u;
+        }
+        u.getClass().getDeclaredMethod(
+                "clear", new Class[]{}).invoke(u, new Object[]{});
+        mean = Double.NaN;
+        std = Double.NaN;
+
+        BufferedReader in =
+            new BufferedReader(
+                    new InputStreamReader(
+                            CertifiedDataTest.class.getResourceAsStream(resource)));
+
+        String line = null;
+
+        for (int j = 0; j < 60; j++) {
+            line = in.readLine();
+            if (j == 40) {
+                mean =
+                    Double.parseDouble(
+                            line.substring(line.lastIndexOf(":") + 1).trim());
+            }
+            if (j == 41) {
+                std =
+                    Double.parseDouble(
+                            line.substring(line.lastIndexOf(":") + 1).trim());
+            }
+        }
+
+        line = in.readLine();
+
+        while (line != null) {
+            if (d != null) {
+                d.addValue(Double.parseDouble(line.trim()));
+            }  else {
+                s.addValue(Double.parseDouble(line.trim()));
+            }
+            line = in.readLine();
+        }
+
+        in.close();
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/stat/FrequencyTest.java b/src/test/java/org/apache/commons/math/stat/FrequencyTest.java
new file mode 100644
index 0000000..4f49acc
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/FrequencyTest.java
@@ -0,0 +1,287 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.Iterator;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.math.TestUtils;
+
+/**
+ * Test cases for the {@link Frequency} class.
+ *
+ * @version $Revision: 1044981 $ $Date: 2010-12-13 01:43:57 +0100 (lun. 13 déc. 2010) $
+ */
+
+public final class FrequencyTest extends TestCase {
+    private long oneL = 1;
+    private long twoL = 2;
+    private long threeL = 3;
+    private int oneI = 1;
+    private int twoI = 2;
+    private int threeI=3;
+    private double tolerance = 10E-15;
+    private Frequency f = null;
+
+    public FrequencyTest(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setUp() {
+        f = new Frequency();
+    }
+
+    /** test freq counts */
+    @SuppressWarnings("deprecation")
+    public void testCounts() {
+        assertEquals("total count",0,f.getSumFreq());
+        f.addValue(oneL);
+        f.addValue(twoL);
+        f.addValue(1);
+        f.addValue(oneI);
+        assertEquals("one frequency count",3,f.getCount(1));
+        assertEquals("two frequency count",1,f.getCount(2));
+        assertEquals("three frequency count",0,f.getCount(3));
+        assertEquals("total count",4,f.getSumFreq());
+        assertEquals("zero cumulative frequency", 0, f.getCumFreq(0));
+        assertEquals("one cumulative frequency", 3,  f.getCumFreq(1));
+        assertEquals("two cumulative frequency", 4,  f.getCumFreq(2));
+        assertEquals("Integer argument cum freq",4, f.getCumFreq(Integer.valueOf(2)));
+        assertEquals("five cumulative frequency", 4,  f.getCumFreq(5));
+        assertEquals("foo cumulative frequency", 0,  f.getCumFreq("foo"));
+
+        f.clear();
+        assertEquals("total count",0,f.getSumFreq());
+
+        // userguide examples -------------------------------------------------------------------
+        f.addValue("one");
+        f.addValue("One");
+        f.addValue("oNe");
+        f.addValue("Z");
+        assertEquals("one cumulative frequency", 1 ,  f.getCount("one"));
+        assertEquals("Z cumulative pct", 0.5,  f.getCumPct("Z"), tolerance);
+        assertEquals("z cumulative pct", 1.0,  f.getCumPct("z"), tolerance);
+        assertEquals("Ot cumulative pct", 0.25,  f.getCumPct("Ot"), tolerance);
+        f.clear();
+
+        f = null;
+        Frequency f = new Frequency();
+        f.addValue(1);
+        f.addValue(Integer.valueOf(1));
+        f.addValue(Long.valueOf(1));
+        f.addValue(2);
+        f.addValue(Integer.valueOf(-1));
+        assertEquals("1 count", 3, f.getCount(1));
+        assertEquals("1 count", 3, f.getCount(Integer.valueOf(1)));
+        assertEquals("0 cum pct", 0.2, f.getCumPct(0), tolerance);
+        assertEquals("1 pct", 0.6, f.getPct(Integer.valueOf(1)), tolerance);
+        assertEquals("-2 cum pct", 0, f.getCumPct(-2), tolerance);
+        assertEquals("10 cum pct", 1, f.getCumPct(10), tolerance);
+
+        f = null;
+        f = new Frequency(String.CASE_INSENSITIVE_ORDER);
+        f.addValue("one");
+        f.addValue("One");
+        f.addValue("oNe");
+        f.addValue("Z");
+        assertEquals("one count", 3 ,  f.getCount("one"));
+        assertEquals("Z cumulative pct -- case insensitive", 1 ,  f.getCumPct("Z"), tolerance);
+        assertEquals("z cumulative pct -- case insensitive", 1 ,  f.getCumPct("z"), tolerance);
+
+        f = null;
+        f = new Frequency();
+        assertEquals(0L, f.getCount('a'));
+        assertEquals(0L, f.getCumFreq('b'));
+        TestUtils.assertEquals(Double.NaN, f.getPct('a'), 0.0);
+        TestUtils.assertEquals(Double.NaN, f.getCumPct('b'), 0.0);
+        f.addValue('a');
+        f.addValue('b');
+        f.addValue('c');
+        f.addValue('d');
+        assertEquals(1L, f.getCount('a'));
+        assertEquals(2L, f.getCumFreq('b'));
+        assertEquals(0.25, f.getPct('a'), 0.0);
+        assertEquals(0.5, f.getCumPct('b'), 0.0);
+        assertEquals(1.0, f.getCumPct('e'), 0.0);
+    }
+
+    /** test pcts */
+    @SuppressWarnings("deprecation")
+    public void testPcts() {
+        f.addValue(oneL);
+        f.addValue(twoL);
+        f.addValue(oneI);
+        f.addValue(twoI);
+        f.addValue(threeL);
+        f.addValue(threeL);
+        f.addValue(3);
+        f.addValue(threeI);
+        assertEquals("one pct",0.25,f.getPct(1),tolerance);
+        assertEquals("two pct",0.25,f.getPct(Long.valueOf(2)),tolerance);
+        assertEquals("three pct",0.5,f.getPct(threeL),tolerance);
+        // MATH-329
+        assertEquals("three (Object) pct",0.5,f.getPct((Object) (Integer.valueOf(3))),tolerance);
+        assertEquals("five pct",0,f.getPct(5),tolerance);
+        assertEquals("foo pct",0,f.getPct("foo"),tolerance);
+        assertEquals("one cum pct",0.25,f.getCumPct(1),tolerance);
+        assertEquals("two cum pct",0.50,f.getCumPct(Long.valueOf(2)),tolerance);
+        assertEquals("Integer argument",0.50,f.getCumPct(Integer.valueOf(2)),tolerance);
+        assertEquals("three cum pct",1.0,f.getCumPct(threeL),tolerance);
+        assertEquals("five cum pct",1.0,f.getCumPct(5),tolerance);
+        assertEquals("zero cum pct",0.0,f.getCumPct(0),tolerance);
+        assertEquals("foo cum pct",0,f.getCumPct("foo"),tolerance);
+    }
+
+    /** test adding incomparable values */
+    @SuppressWarnings("deprecation")
+    public void testAdd() {
+        char aChar = 'a';
+        char bChar = 'b';
+        String aString = "a";
+        f.addValue(aChar);
+        f.addValue(bChar);
+        try {
+            f.addValue(aString);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            f.addValue(2);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        assertEquals("a pct",0.5,f.getPct(aChar),tolerance);
+        assertEquals("b cum pct",1.0,f.getCumPct(bChar),tolerance);
+        assertEquals("a string pct",0.0,f.getPct(aString),tolerance);
+        assertEquals("a string cum pct",0.0,f.getCumPct(aString),tolerance);
+
+        f = new Frequency();
+        f.addValue("One");
+        try {
+            f.addValue(new Integer("One"));
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+
+    // Check what happens when non-Comparable objects are added
+    @SuppressWarnings("deprecation")
+    public void testAddNonComparable(){
+        try {
+            f.addValue(new Object()); // This was previously OK
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+        }
+        f.clear();
+        f.addValue(1);
+        try {
+            f.addValue(new Object());
+            fail("Expected IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    /** test empty table */
+    public void testEmptyTable() {
+        assertEquals("freq sum, empty table", 0, f.getSumFreq());
+        assertEquals("count, empty table", 0, f.getCount(0));
+        assertEquals("count, empty table",0, f.getCount(Integer.valueOf(0)));
+        assertEquals("cum freq, empty table", 0, f.getCumFreq(0));
+        assertEquals("cum freq, empty table", 0, f.getCumFreq("x"));
+        assertTrue("pct, empty table", Double.isNaN(f.getPct(0)));
+        assertTrue("pct, empty table", Double.isNaN(f.getPct(Integer.valueOf(0))));
+        assertTrue("cum pct, empty table", Double.isNaN(f.getCumPct(0)));
+        assertTrue("cum pct, empty table", Double.isNaN(f.getCumPct(Integer.valueOf(0))));
+    }
+
+    /**
+     * Tests toString()
+     */
+    public void testToString(){
+        f.addValue(oneL);
+        f.addValue(twoL);
+        f.addValue(oneI);
+        f.addValue(twoI);
+
+        String s = f.toString();
+        //System.out.println(s);
+        assertNotNull(s);
+        BufferedReader reader = new BufferedReader(new StringReader(s));
+        try {
+            String line = reader.readLine(); // header line
+            assertNotNull(line);
+
+            line = reader.readLine(); // one's or two's line
+            assertNotNull(line);
+
+            line = reader.readLine(); // one's or two's line
+            assertNotNull(line);
+
+            line = reader.readLine(); // no more elements
+            assertNull(line);
+        } catch(IOException ex){
+            fail(ex.getMessage());
+        }
+    }
+    @SuppressWarnings("deprecation")
+    public void testIntegerValues() {
+        Comparable<?> obj1 = null;
+        obj1 = Integer.valueOf(1);
+        Integer int1 = Integer.valueOf(1);
+        f.addValue(obj1);
+        f.addValue(int1);
+        f.addValue(2);
+        f.addValue(Long.valueOf(2));
+        assertEquals("Integer 1 count", 2, f.getCount(1));
+        assertEquals("Integer 1 count", 2, f.getCount(Integer.valueOf(1)));
+        assertEquals("Integer 1 count", 2, f.getCount(Long.valueOf(1)));
+        assertEquals("Integer 1 cumPct", 0.5, f.getCumPct(1), tolerance);
+        assertEquals("Integer 1 cumPct", 0.5, f.getCumPct(Long.valueOf(1)), tolerance);
+        assertEquals("Integer 1 cumPct", 0.5, f.getCumPct(Integer.valueOf(1)), tolerance);
+        Iterator<?> it = f.valuesIterator();
+        while (it.hasNext()) {
+            assertTrue(it.next() instanceof Long);
+        }
+    }
+
+    public void testSerial() {
+        f.addValue(oneL);
+        f.addValue(twoL);
+        f.addValue(oneI);
+        f.addValue(twoI);
+        assertEquals(f, TestUtils.serializeAndRecover(f));
+    }
+    
+    public void testGetUniqueCount() {
+        assertEquals(0, f.getUniqueCount());
+        f.addValue(oneL);
+        assertEquals(1, f.getUniqueCount());
+        f.addValue(oneL);
+        assertEquals(1, f.getUniqueCount());
+        f.addValue(twoI);
+        assertEquals(2, f.getUniqueCount());
+    }
+}
+
diff --git a/src/test/java/org/apache/commons/math/stat/StatUtilsTest.java b/src/test/java/org/apache/commons/math/stat/StatUtilsTest.java
new file mode 100644
index 0000000..b41a3aa
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/StatUtilsTest.java
@@ -0,0 +1,468 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 junit.framework.TestCase;
+
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.stat.descriptive.DescriptiveStatistics;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Test cases for the {@link StatUtils} class.
+ * @version $Revision: 1060124 $ $Date: 2011-01-18 00:00:52 +0100 (mar. 18 janv. 2011) $
+ */
+
+public final class StatUtilsTest extends TestCase {
+
+    private double one = 1;
+    private float two = 2;
+    private int three = 3;
+    private double mean = 2;
+    private double sumSq = 18;
+    private double sum = 8;
+    private double var = 0.666666666666666666667;
+    private double min = 1;
+    private double max = 3;
+    private double tolerance = 10E-15;
+    private double nan = Double.NaN;
+
+    public StatUtilsTest(String name) {
+        super(name);
+    }
+
+    /** test stats */
+    public void testStats() {
+        double[] values = new double[] { one, two, two, three };
+        assertEquals("sum", sum, StatUtils.sum(values), tolerance);
+        assertEquals("sumsq", sumSq, StatUtils.sumSq(values), tolerance);
+        assertEquals("var", var, StatUtils.variance(values), tolerance);
+        assertEquals("var with mean", var, StatUtils.variance(values, mean), tolerance);
+        assertEquals("mean", mean, StatUtils.mean(values), tolerance);
+        assertEquals("min", min, StatUtils.min(values), tolerance);
+        assertEquals("max", max, StatUtils.max(values), tolerance);
+    }
+
+    public void testN0andN1Conditions() throws Exception {
+        double[] values = new double[0];
+
+        assertTrue(
+            "Mean of n = 0 set should be NaN",
+            Double.isNaN(StatUtils.mean(values)));
+        assertTrue(
+            "Variance of n = 0 set should be NaN",
+            Double.isNaN(StatUtils.variance(values)));
+
+        values = new double[] { one };
+
+        assertTrue(
+            "Mean of n = 1 set should be value of single item n1",
+            StatUtils.mean(values) == one);
+        assertTrue(
+            "Variance of n = 1 set should be zero",
+            StatUtils.variance(values) == 0);
+    }
+
+    public void testArrayIndexConditions() throws Exception {
+        double[] values = { 1.0, 2.0, 3.0, 4.0 };
+
+        assertEquals(
+            "Sum not expected",
+            5.0,
+            StatUtils.sum(values, 1, 2),
+            Double.MIN_VALUE);
+        assertEquals(
+            "Sum not expected",
+            3.0,
+            StatUtils.sum(values, 0, 2),
+            Double.MIN_VALUE);
+        assertEquals(
+            "Sum not expected",
+            7.0,
+            StatUtils.sum(values, 2, 2),
+            Double.MIN_VALUE);
+
+        try {
+            StatUtils.sum(values, 2, 3);
+            fail("Expected RuntimeException");
+        } catch (RuntimeException e) {
+            // expected
+        }
+
+        try {
+            StatUtils.sum(values, -1, 2);
+            fail("Expected RuntimeException");
+        } catch (RuntimeException e) {
+            // expected
+        }
+
+    }
+
+    public void testSumSq() {
+        double[] x = null;
+
+        // test null
+        try {
+            StatUtils.sumSq(x);
+            fail("null is not a valid data array.");
+        } catch (IllegalArgumentException ex) {
+            // success
+        }
+
+        try {
+            StatUtils.sumSq(x, 0, 4);
+            fail("null is not a valid data array.");
+        } catch (IllegalArgumentException ex) {
+            // success
+        }
+
+        // test empty
+        x = new double[] {};
+        TestUtils.assertEquals(Double.NaN, StatUtils.sumSq(x), tolerance);
+        TestUtils.assertEquals(Double.NaN, StatUtils.sumSq(x, 0, 0), tolerance);
+
+        // test one
+        x = new double[] {two};
+        TestUtils.assertEquals(4, StatUtils.sumSq(x), tolerance);
+        TestUtils.assertEquals(4, StatUtils.sumSq(x, 0, 1), tolerance);
+
+        // test many
+        x = new double[] {one, two, two, three};
+        TestUtils.assertEquals(18, StatUtils.sumSq(x), tolerance);
+        TestUtils.assertEquals(8, StatUtils.sumSq(x, 1, 2), tolerance);
+    }
+
+    public void testProduct() {
+        double[] x = null;
+
+        // test null
+        try {
+            StatUtils.product(x);
+            fail("null is not a valid data array.");
+        } catch (IllegalArgumentException ex) {
+            // success
+        }
+
+        try {
+            StatUtils.product(x, 0, 4);
+            fail("null is not a valid data array.");
+        } catch (IllegalArgumentException ex) {
+            // success
+        }
+
+        // test empty
+        x = new double[] {};
+        TestUtils.assertEquals(Double.NaN, StatUtils.product(x), tolerance);
+        TestUtils.assertEquals(Double.NaN, StatUtils.product(x, 0, 0), tolerance);
+
+        // test one
+        x = new double[] {two};
+        TestUtils.assertEquals(two, StatUtils.product(x), tolerance);
+        TestUtils.assertEquals(two, StatUtils.product(x, 0, 1), tolerance);
+
+        // test many
+        x = new double[] {one, two, two, three};
+        TestUtils.assertEquals(12, StatUtils.product(x), tolerance);
+        TestUtils.assertEquals(4, StatUtils.product(x, 1, 2), tolerance);
+    }
+
+    public void testSumLog() {
+        double[] x = null;
+
+        // test null
+        try {
+            StatUtils.sumLog(x);
+            fail("null is not a valid data array.");
+        } catch (IllegalArgumentException ex) {
+            // success
+        }
+
+        try {
+            StatUtils.sumLog(x, 0, 4);
+            fail("null is not a valid data array.");
+        } catch (IllegalArgumentException ex) {
+            // success
+        }
+
+        // test empty
+        x = new double[] {};
+        TestUtils.assertEquals(Double.NaN, StatUtils.sumLog(x), tolerance);
+        TestUtils.assertEquals(Double.NaN, StatUtils.sumLog(x, 0, 0), tolerance);
+
+        // test one
+        x = new double[] {two};
+        TestUtils.assertEquals(FastMath.log(two), StatUtils.sumLog(x), tolerance);
+        TestUtils.assertEquals(FastMath.log(two), StatUtils.sumLog(x, 0, 1), tolerance);
+
+        // test many
+        x = new double[] {one, two, two, three};
+        TestUtils.assertEquals(FastMath.log(one) + 2.0 * FastMath.log(two) + FastMath.log(three), StatUtils.sumLog(x), tolerance);
+        TestUtils.assertEquals(2.0 * FastMath.log(two), StatUtils.sumLog(x, 1, 2), tolerance);
+    }
+
+    public void testMean() {
+        double[] x = null;
+
+        try {
+            StatUtils.mean(x, 0, 4);
+            fail("null is not a valid data array.");
+        } catch (IllegalArgumentException ex) {
+            // success
+        }
+
+        // test empty
+        x = new double[] {};
+        TestUtils.assertEquals(Double.NaN, StatUtils.mean(x, 0, 0), tolerance);
+
+        // test one
+        x = new double[] {two};
+        TestUtils.assertEquals(two, StatUtils.mean(x, 0, 1), tolerance);
+
+        // test many
+        x = new double[] {one, two, two, three};
+        TestUtils.assertEquals(2.5, StatUtils.mean(x, 2, 2), tolerance);
+    }
+
+    public void testVariance() {
+        double[] x = null;
+
+        try {
+            StatUtils.variance(x, 0, 4);
+            fail("null is not a valid data array.");
+        } catch (IllegalArgumentException ex) {
+            // success
+        }
+
+        // test empty
+        x = new double[] {};
+        TestUtils.assertEquals(Double.NaN, StatUtils.variance(x, 0, 0), tolerance);
+
+        // test one
+        x = new double[] {two};
+        TestUtils.assertEquals(0.0, StatUtils.variance(x, 0, 1), tolerance);
+
+        // test many
+        x = new double[] {one, two, two, three};
+        TestUtils.assertEquals(0.5, StatUtils.variance(x, 2, 2), tolerance);
+
+        // test precomputed mean
+        x = new double[] {one, two, two, three};
+        TestUtils.assertEquals(0.5, StatUtils.variance(x,2.5, 2, 2), tolerance);
+    }
+
+    public void testMax() {
+        double[] x = null;
+
+        try {
+            StatUtils.max(x, 0, 4);
+            fail("null is not a valid data array.");
+        } catch (IllegalArgumentException ex) {
+            // success
+        }
+
+        // test empty
+        x = new double[] {};
+        TestUtils.assertEquals(Double.NaN, StatUtils.max(x, 0, 0), tolerance);
+
+        // test one
+        x = new double[] {two};
+        TestUtils.assertEquals(two, StatUtils.max(x, 0, 1), tolerance);
+
+        // test many
+        x = new double[] {one, two, two, three};
+        TestUtils.assertEquals(three, StatUtils.max(x, 1, 3), tolerance);
+
+        // test first nan is ignored
+        x = new double[] {nan, two, three};
+        TestUtils.assertEquals(three, StatUtils.max(x), tolerance);
+
+        // test middle nan is ignored
+        x = new double[] {one, nan, three};
+        TestUtils.assertEquals(three, StatUtils.max(x), tolerance);
+
+        // test last nan is ignored
+        x = new double[] {one, two, nan};
+        TestUtils.assertEquals(two, StatUtils.max(x), tolerance);
+
+        // test all nan returns nan
+        x = new double[] {nan, nan, nan};
+        TestUtils.assertEquals(nan, StatUtils.max(x), tolerance);
+    }
+
+    public void testMin() {
+        double[] x = null;
+
+        try {
+            StatUtils.min(x, 0, 4);
+            fail("null is not a valid data array.");
+        } catch (IllegalArgumentException ex) {
+            // success
+        }
+
+        // test empty
+        x = new double[] {};
+        TestUtils.assertEquals(Double.NaN, StatUtils.min(x, 0, 0), tolerance);
+
+        // test one
+        x = new double[] {two};
+        TestUtils.assertEquals(two, StatUtils.min(x, 0, 1), tolerance);
+
+        // test many
+        x = new double[] {one, two, two, three};
+        TestUtils.assertEquals(two, StatUtils.min(x, 1, 3), tolerance);
+
+        // test first nan is ignored
+        x = new double[] {nan, two, three};
+        TestUtils.assertEquals(two, StatUtils.min(x), tolerance);
+
+        // test middle nan is ignored
+        x = new double[] {one, nan, three};
+        TestUtils.assertEquals(one, StatUtils.min(x), tolerance);
+
+        // test last nan is ignored
+        x = new double[] {one, two, nan};
+        TestUtils.assertEquals(one, StatUtils.min(x), tolerance);
+
+        // test all nan returns nan
+        x = new double[] {nan, nan, nan};
+        TestUtils.assertEquals(nan, StatUtils.min(x), tolerance);
+    }
+
+    public void testPercentile() {
+        double[] x = null;
+
+        // test null
+        try {
+            StatUtils.percentile(x, .25);
+            fail("null is not a valid data array.");
+        } catch (IllegalArgumentException ex) {
+            // success
+        }
+
+        try {
+            StatUtils.percentile(x, 0, 4, 0.25);
+            fail("null is not a valid data array.");
+        } catch (IllegalArgumentException ex) {
+            // success
+        }
+
+        // test empty
+        x = new double[] {};
+        TestUtils.assertEquals(Double.NaN, StatUtils.percentile(x, 25), tolerance);
+        TestUtils.assertEquals(Double.NaN, StatUtils.percentile(x, 0, 0, 25), tolerance);
+
+        // test one
+        x = new double[] {two};
+        TestUtils.assertEquals(two, StatUtils.percentile(x, 25), tolerance);
+        TestUtils.assertEquals(two, StatUtils.percentile(x, 0, 1, 25), tolerance);
+
+        // test many
+        x = new double[] {one, two, two, three};
+        TestUtils.assertEquals(2.5, StatUtils.percentile(x, 70), tolerance);
+        TestUtils.assertEquals(2.5, StatUtils.percentile(x, 1, 3, 62.5), tolerance);
+    }
+
+    public void testDifferenceStats() throws Exception {
+        double sample1[] = {1d, 2d, 3d, 4d};
+        double sample2[] = {1d, 3d, 4d, 2d};
+        double diff[] = {0d, -1d, -1d, 2d};
+        double small[] = {1d, 4d};
+        double meanDifference = StatUtils.meanDifference(sample1, sample2);
+        assertEquals(StatUtils.sumDifference(sample1, sample2), StatUtils.sum(diff), tolerance);
+        assertEquals(meanDifference, StatUtils.mean(diff), tolerance);
+        assertEquals(StatUtils.varianceDifference(sample1, sample2, meanDifference),
+                StatUtils.variance(diff), tolerance);
+        try {
+            StatUtils.meanDifference(sample1, small);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            StatUtils.varianceDifference(sample1, small, meanDifference);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            double[] single = {1.0};
+            StatUtils.varianceDifference(single, single, meanDifference);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+
+    public void testGeometricMean() throws Exception {
+        double[] test = null;
+        try {
+            StatUtils.geometricMean(test);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        test = new double[] {2, 4, 6, 8};
+        assertEquals(FastMath.exp(0.25d * StatUtils.sumLog(test)),
+                StatUtils.geometricMean(test), Double.MIN_VALUE);
+        assertEquals(FastMath.exp(0.5 * StatUtils.sumLog(test, 0, 2)),
+                StatUtils.geometricMean(test, 0, 2), Double.MIN_VALUE);
+    }
+    
+    
+    /**
+     * Run the test with the values 50 and 100 and assume standardized values    
+     */
+
+    public void testNormalize1() {
+        double sample[] = { 50, 100 };
+        double expectedSample[] = { -25 / Math.sqrt(1250), 25 / Math.sqrt(1250) };
+        double[] out = StatUtils.normalize(sample);
+        for (int i = 0; i < out.length; i++) {
+            assertEquals(out[i], expectedSample[i]);
+        }
+
+    }
+
+    /**
+     * Run with 77 random values, assuming that the outcome has a mean of 0 and a standard deviation of 1 with a
+     * precision of 1E-10.
+     */
+
+    public void testNormalize2() {
+        // create an sample with 77 values    
+        int length = 77;
+        double sample[] = new double[length];
+        for (int i = 0; i < length; i++) {
+            sample[i] = Math.random();
+        }
+        // normalize this sample
+        double standardizedSample[] = StatUtils.normalize(sample);
+
+        DescriptiveStatistics stats = new DescriptiveStatistics();
+        // Add the data from the array
+        for (int i = 0; i < length; i++) {
+            stats.addValue(standardizedSample[i]);
+        }
+        // the calculations do have a limited precision    
+        double distance = 1E-10;
+        // check the mean an standard deviation
+        assertEquals(0.0, stats.getMean(), distance);
+        assertEquals(1.0, stats.getStandardDeviation(), distance);
+
+    }
+    
+}
diff --git a/src/test/java/org/apache/commons/math/stat/clustering/EuclideanIntegerPointTest.java b/src/test/java/org/apache/commons/math/stat/clustering/EuclideanIntegerPointTest.java
new file mode 100644
index 0000000..762bb55
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/clustering/EuclideanIntegerPointTest.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.clustering;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.util.FastMath;
+import org.junit.Test;
+
+public class EuclideanIntegerPointTest {
+
+    @Test
+    public void testArrayIsReference() {
+        int[] array = { -3, -2, -1, 0, 1 };
+        assertTrue(array == new EuclideanIntegerPoint(array).getPoint());
+    }
+
+    @Test
+    public void testDistance() {
+        EuclideanIntegerPoint e1 = new EuclideanIntegerPoint(new int[] { -3, -2, -1, 0, 1 });
+        EuclideanIntegerPoint e2 = new EuclideanIntegerPoint(new int[] {  1,  0, -1, 1, 1 });
+        assertEquals(FastMath.sqrt(21.0), e1.distanceFrom(e2), 1.0e-15);
+        assertEquals(0.0, e1.distanceFrom(e1), 1.0e-15);
+        assertEquals(0.0, e2.distanceFrom(e2), 1.0e-15);
+    }
+
+    @Test
+    public void testCentroid() {
+        List<EuclideanIntegerPoint> list = new ArrayList<EuclideanIntegerPoint>();
+        list.add(new EuclideanIntegerPoint(new int[] {  1,  3 }));
+        list.add(new EuclideanIntegerPoint(new int[] {  2,  2 }));
+        list.add(new EuclideanIntegerPoint(new int[] {  3,  3 }));
+        list.add(new EuclideanIntegerPoint(new int[] {  2,  4 }));
+        EuclideanIntegerPoint c = list.get(0).centroidOf(list);
+        assertEquals(2, c.getPoint()[0]);
+        assertEquals(3, c.getPoint()[1]);
+    }
+
+    @Test
+    public void testSerial() {
+        EuclideanIntegerPoint p = new EuclideanIntegerPoint(new int[] { -3, -2, -1, 0, 1 });
+        assertEquals(p, TestUtils.serializeAndRecover(p));
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/stat/clustering/KMeansPlusPlusClustererTest.java b/src/test/java/org/apache/commons/math/stat/clustering/KMeansPlusPlusClustererTest.java
new file mode 100644
index 0000000..33b2d8a
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/clustering/KMeansPlusPlusClustererTest.java
@@ -0,0 +1,169 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.stat.clustering;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Random;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class KMeansPlusPlusClustererTest {
+
+    @Test
+    public void dimension2() {
+        KMeansPlusPlusClusterer<EuclideanIntegerPoint> transformer =
+            new KMeansPlusPlusClusterer<EuclideanIntegerPoint>(new Random(1746432956321l));
+        EuclideanIntegerPoint[] points = new EuclideanIntegerPoint[] {
+
+                // first expected cluster
+                new EuclideanIntegerPoint(new int[] { -15,  3 }),
+                new EuclideanIntegerPoint(new int[] { -15,  4 }),
+                new EuclideanIntegerPoint(new int[] { -15,  5 }),
+                new EuclideanIntegerPoint(new int[] { -14,  3 }),
+                new EuclideanIntegerPoint(new int[] { -14,  5 }),
+                new EuclideanIntegerPoint(new int[] { -13,  3 }),
+                new EuclideanIntegerPoint(new int[] { -13,  4 }),
+                new EuclideanIntegerPoint(new int[] { -13,  5 }),
+
+                // second expected cluster
+                new EuclideanIntegerPoint(new int[] { -1,  0 }),
+                new EuclideanIntegerPoint(new int[] { -1, -1 }),
+                new EuclideanIntegerPoint(new int[] {  0, -1 }),
+                new EuclideanIntegerPoint(new int[] {  1, -1 }),
+                new EuclideanIntegerPoint(new int[] {  1, -2 }),
+
+                // third expected cluster
+                new EuclideanIntegerPoint(new int[] { 13,  3 }),
+                new EuclideanIntegerPoint(new int[] { 13,  4 }),
+                new EuclideanIntegerPoint(new int[] { 14,  4 }),
+                new EuclideanIntegerPoint(new int[] { 14,  7 }),
+                new EuclideanIntegerPoint(new int[] { 16,  5 }),
+                new EuclideanIntegerPoint(new int[] { 16,  6 }),
+                new EuclideanIntegerPoint(new int[] { 17,  4 }),
+                new EuclideanIntegerPoint(new int[] { 17,  7 })
+
+        };
+        List<Cluster<EuclideanIntegerPoint>> clusters =
+            transformer.cluster(Arrays.asList(points), 3, 10);
+
+        assertEquals(3, clusters.size());
+        boolean cluster1Found = false;
+        boolean cluster2Found = false;
+        boolean cluster3Found = false;
+        for (Cluster<EuclideanIntegerPoint> cluster : clusters) {
+            int[] center = cluster.getCenter().getPoint();
+            if (center[0] < 0) {
+                cluster1Found = true;
+                assertEquals(8, cluster.getPoints().size());
+                assertEquals(-14, center[0]);
+                assertEquals( 4, center[1]);
+            } else if (center[1] < 0) {
+                cluster2Found = true;
+                assertEquals(5, cluster.getPoints().size());
+                assertEquals( 0, center[0]);
+                assertEquals(-1, center[1]);
+            } else {
+                cluster3Found = true;
+                assertEquals(8, cluster.getPoints().size());
+                assertEquals(15, center[0]);
+                assertEquals(5, center[1]);
+            }
+        }
+        assertTrue(cluster1Found);
+        assertTrue(cluster2Found);
+        assertTrue(cluster3Found);
+
+    }
+
+    /**
+     * JIRA: MATH-305
+     *
+     * Two points, one cluster, one iteration
+     */
+    @Test
+    public void testPerformClusterAnalysisDegenerate() {
+        KMeansPlusPlusClusterer<EuclideanIntegerPoint> transformer = new KMeansPlusPlusClusterer<EuclideanIntegerPoint>(
+                new Random(1746432956321l));
+        EuclideanIntegerPoint[] points = new EuclideanIntegerPoint[] {
+                new EuclideanIntegerPoint(new int[] { 1959, 325100 }),
+                new EuclideanIntegerPoint(new int[] { 1960, 373200 }), };
+        List<Cluster<EuclideanIntegerPoint>> clusters = transformer.cluster(Arrays.asList(points), 1, 1);
+        assertEquals(1, clusters.size());
+        assertEquals(2, (clusters.get(0).getPoints().size()));
+        EuclideanIntegerPoint pt1 = new EuclideanIntegerPoint(new int[] { 1959, 325100 });
+        EuclideanIntegerPoint pt2 = new EuclideanIntegerPoint(new int[] { 1960, 373200 });
+        assertTrue(clusters.get(0).getPoints().contains(pt1));
+        assertTrue(clusters.get(0).getPoints().contains(pt2));
+
+    }
+
+    @Test
+    public void testCertainSpace() {
+        KMeansPlusPlusClusterer.EmptyClusterStrategy[] strategies = {
+            KMeansPlusPlusClusterer.EmptyClusterStrategy.LARGEST_VARIANCE,
+            KMeansPlusPlusClusterer.EmptyClusterStrategy.LARGEST_POINTS_NUMBER,
+            KMeansPlusPlusClusterer.EmptyClusterStrategy.FARTHEST_POINT
+        };
+        for (KMeansPlusPlusClusterer.EmptyClusterStrategy strategy : strategies) {
+            KMeansPlusPlusClusterer<EuclideanIntegerPoint> transformer =
+                new KMeansPlusPlusClusterer<EuclideanIntegerPoint>(new Random(1746432956321l), strategy);
+            int numberOfVariables = 27;
+            // initialise testvalues
+            int position1 = 1;
+            int position2 = position1 + numberOfVariables;
+            int position3 = position2 + numberOfVariables;
+            int position4 = position3 + numberOfVariables;
+            // testvalues will be multiplied
+            int multiplier = 1000000;
+
+            EuclideanIntegerPoint[] breakingPoints = new EuclideanIntegerPoint[numberOfVariables];
+            // define the space which will break the cluster algorithm
+            for (int i = 0; i < numberOfVariables; i++) {
+                int points[] = { position1, position2, position3, position4 };
+                // multiply the values
+                for (int j = 0; j < points.length; j++) {
+                    points[j] = points[j] * multiplier;
+                }
+                EuclideanIntegerPoint euclideanIntegerPoint = new EuclideanIntegerPoint(points);
+                breakingPoints[i] = euclideanIntegerPoint;
+                position1 = position1 + numberOfVariables;
+                position2 = position2 + numberOfVariables;
+                position3 = position3 + numberOfVariables;
+                position4 = position4 + numberOfVariables;
+            }
+
+            for (int n = 2; n < 27; ++n) {
+                List<Cluster<EuclideanIntegerPoint>> clusters =
+                    transformer.cluster(Arrays.asList(breakingPoints), n, 100);
+                Assert.assertEquals(n, clusters.size());
+                int sum = 0;
+                for (Cluster<EuclideanIntegerPoint> cluster : clusters) {
+                    sum += cluster.getPoints().size();
+                }
+                Assert.assertEquals(numberOfVariables, sum);
+            }
+        }
+
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/stat/correlation/CovarianceTest.java b/src/test/java/org/apache/commons/math/stat/correlation/CovarianceTest.java
new file mode 100644
index 0000000..b603bb9
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/correlation/CovarianceTest.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.stat.correlation;
+
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.linear.RealMatrix;
+import org.apache.commons.math.linear.Array2DRowRealMatrix;
+import org.apache.commons.math.stat.descriptive.moment.Variance;
+
+import junit.framework.TestCase;
+
+public class CovarianceTest extends TestCase {
+
+    protected final double[] longleyData = new double[] {
+            60323,83.0,234289,2356,1590,107608,1947,
+            61122,88.5,259426,2325,1456,108632,1948,
+            60171,88.2,258054,3682,1616,109773,1949,
+            61187,89.5,284599,3351,1650,110929,1950,
+            63221,96.2,328975,2099,3099,112075,1951,
+            63639,98.1,346999,1932,3594,113270,1952,
+            64989,99.0,365385,1870,3547,115094,1953,
+            63761,100.0,363112,3578,3350,116219,1954,
+            66019,101.2,397469,2904,3048,117388,1955,
+            67857,104.6,419180,2822,2857,118734,1956,
+            68169,108.4,442769,2936,2798,120445,1957,
+            66513,110.8,444546,4681,2637,121950,1958,
+            68655,112.6,482704,3813,2552,123366,1959,
+            69564,114.2,502601,3931,2514,125368,1960,
+            69331,115.7,518173,4806,2572,127852,1961,
+            70551,116.9,554894,4007,2827,130081,1962
+        };
+
+    protected final double[] swissData = new double[] {
+            80.2,17.0,15,12,9.96,
+            83.1,45.1,6,9,84.84,
+            92.5,39.7,5,5,93.40,
+            85.8,36.5,12,7,33.77,
+            76.9,43.5,17,15,5.16,
+            76.1,35.3,9,7,90.57,
+            83.8,70.2,16,7,92.85,
+            92.4,67.8,14,8,97.16,
+            82.4,53.3,12,7,97.67,
+            82.9,45.2,16,13,91.38,
+            87.1,64.5,14,6,98.61,
+            64.1,62.0,21,12,8.52,
+            66.9,67.5,14,7,2.27,
+            68.9,60.7,19,12,4.43,
+            61.7,69.3,22,5,2.82,
+            68.3,72.6,18,2,24.20,
+            71.7,34.0,17,8,3.30,
+            55.7,19.4,26,28,12.11,
+            54.3,15.2,31,20,2.15,
+            65.1,73.0,19,9,2.84,
+            65.5,59.8,22,10,5.23,
+            65.0,55.1,14,3,4.52,
+            56.6,50.9,22,12,15.14,
+            57.4,54.1,20,6,4.20,
+            72.5,71.2,12,1,2.40,
+            74.2,58.1,14,8,5.23,
+            72.0,63.5,6,3,2.56,
+            60.5,60.8,16,10,7.72,
+            58.3,26.8,25,19,18.46,
+            65.4,49.5,15,8,6.10,
+            75.5,85.9,3,2,99.71,
+            69.3,84.9,7,6,99.68,
+            77.3,89.7,5,2,100.00,
+            70.5,78.2,12,6,98.96,
+            79.4,64.9,7,3,98.22,
+            65.0,75.9,9,9,99.06,
+            92.2,84.6,3,3,99.46,
+            79.3,63.1,13,13,96.83,
+            70.4,38.4,26,12,5.62,
+            65.7,7.7,29,11,13.79,
+            72.7,16.7,22,13,11.22,
+            64.4,17.6,35,32,16.92,
+            77.6,37.6,15,7,4.97,
+            67.6,18.7,25,7,8.65,
+            35.0,1.2,37,53,42.34,
+            44.7,46.6,16,29,50.43,
+            42.8,27.7,22,29,58.33
+        };
+
+
+    /**
+     * Test Longley dataset against R.
+     * Data Source: J. Longley (1967) "An Appraisal of Least Squares
+     * Programs for the Electronic Computer from the Point of View of the User"
+     * Journal of the American Statistical Association, vol. 62. September,
+     * pp. 819-841.
+     *
+     * Data are from NIST:
+     * http://www.itl.nist.gov/div898/strd/lls/data/LINKS/DATA/Longley.dat
+     */
+    public void testLongly() {
+        RealMatrix matrix = createRealMatrix(longleyData, 16, 7);
+        RealMatrix covarianceMatrix = new Covariance(matrix).getCovarianceMatrix();
+        double[] rData = new double[] {
+         12333921.73333333246, 3.679666000000000e+04, 343330206.333333313,
+         1649102.666666666744, 1117681.066666666651, 23461965.733333334, 16240.93333333333248,
+         36796.66000000000, 1.164576250000000e+02, 1063604.115416667,
+         6258.666250000000, 3490.253750000000, 73503.000000000, 50.92333333333334,
+         343330206.33333331347, 1.063604115416667e+06, 9879353659.329166412,
+         56124369.854166664183, 30880428.345833335072, 685240944.600000024, 470977.90000000002328,
+         1649102.66666666674, 6.258666250000000e+03, 56124369.854166664,
+         873223.429166666698, -115378.762499999997, 4462741.533333333, 2973.03333333333330,
+         1117681.06666666665, 3.490253750000000e+03, 30880428.345833335,
+         -115378.762499999997, 484304.095833333326, 1764098.133333333, 1382.43333333333339,
+         23461965.73333333433, 7.350300000000000e+04, 685240944.600000024,
+         4462741.533333333209, 1764098.133333333302, 48387348.933333330, 32917.40000000000146,
+         16240.93333333333, 5.092333333333334e+01, 470977.900000000,
+         2973.033333333333, 1382.433333333333, 32917.40000000, 22.66666666666667
+        };
+
+        TestUtils.assertEquals("covariance matrix", createRealMatrix(rData, 7, 7), covarianceMatrix, 10E-9);
+
+    }
+
+    /**
+     * Test R Swiss fertility dataset against R.
+     * Data Source: R datasets package
+     */
+    public void testSwissFertility() {
+         RealMatrix matrix = createRealMatrix(swissData, 47, 5);
+         RealMatrix covarianceMatrix = new Covariance(matrix).getCovarianceMatrix();
+         double[] rData = new double[] {
+           156.0424976873265, 100.1691489361702, -64.36692876965772, -79.7295097132285, 241.5632030527289,
+           100.169148936170251, 515.7994172062905, -124.39283071230344, -139.6574005550416, 379.9043755781684,
+           -64.3669287696577, -124.3928307123034, 63.64662349676226, 53.5758556891767, -190.5606105457909,
+           -79.7295097132285, -139.6574005550416, 53.57585568917669, 92.4560592044403, -61.6988297872340,
+            241.5632030527289, 379.9043755781684, -190.56061054579092, -61.6988297872340, 1739.2945371877890
+         };
+
+         TestUtils.assertEquals("covariance matrix", createRealMatrix(rData, 5, 5), covarianceMatrix, 10E-13);
+    }
+
+    /**
+     * Constant column
+     */
+    public void testConstant() {
+        double[] noVariance = new double[] {1, 1, 1, 1};
+        double[] values = new double[] {1, 2, 3, 4};
+        assertEquals(0d, new Covariance().covariance(noVariance, values, true), Double.MIN_VALUE);
+        assertEquals(0d, new Covariance().covariance(noVariance, noVariance, true), Double.MIN_VALUE);
+    }
+
+
+    /**
+     * Insufficient data
+     */
+    public void testInsufficientData() {
+        double[] one = new double[] {1};
+        double[] two = new double[] {2};
+        try {
+            new Covariance().covariance(one, two, false);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+        RealMatrix matrix = new Array2DRowRealMatrix(new double[][] {{0},{1}});
+        try {
+            new Covariance(matrix);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+    }
+
+    /**
+     * Verify that diagonal entries are consistent with Variance computation and matrix matches
+     * column-by-column covariances
+     */
+    public void testConsistency() {
+        final RealMatrix matrix = createRealMatrix(swissData, 47, 5);
+        final RealMatrix covarianceMatrix = new Covariance(matrix).getCovarianceMatrix();
+
+        // Variances on the diagonal
+        Variance variance = new Variance();
+        for (int i = 0; i < 5; i++) {
+            assertEquals(variance.evaluate(matrix.getColumn(i)), covarianceMatrix.getEntry(i,i), 10E-14);
+        }
+
+        // Symmetry, column-consistency
+        assertEquals(covarianceMatrix.getEntry(2, 3),
+                new Covariance().covariance(matrix.getColumn(2), matrix.getColumn(3), true), 10E-14);
+        assertEquals(covarianceMatrix.getEntry(2, 3), covarianceMatrix.getEntry(3, 2), Double.MIN_VALUE);
+
+        // All columns same -> all entries = column variance
+        RealMatrix repeatedColumns = new Array2DRowRealMatrix(47, 3);
+        for (int i = 0; i < 3; i++) {
+            repeatedColumns.setColumnMatrix(i, matrix.getColumnMatrix(0));
+        }
+        RealMatrix repeatedCovarianceMatrix = new Covariance(repeatedColumns).getCovarianceMatrix();
+        double columnVariance = variance.evaluate(matrix.getColumn(0));
+        for (int i = 0; i < 3; i++) {
+            for (int j = 0; j < 3; j++) {
+                assertEquals(columnVariance, repeatedCovarianceMatrix.getEntry(i, j), 10E-14);
+            }
+        }
+
+        // Check bias-correction defaults
+        double[][] data = matrix.getData();
+        TestUtils.assertEquals("Covariances",
+                covarianceMatrix, new Covariance().computeCovarianceMatrix(data),Double.MIN_VALUE);
+        TestUtils.assertEquals("Covariances",
+                covarianceMatrix, new Covariance().computeCovarianceMatrix(data, true),Double.MIN_VALUE);
+
+        double[] x = data[0];
+        double[] y = data[1];
+        assertEquals(new Covariance().covariance(x, y),
+                new Covariance().covariance(x, y, true), Double.MIN_VALUE);
+    }
+
+    protected RealMatrix createRealMatrix(double[] data, int nRows, int nCols) {
+        double[][] matrixData = new double[nRows][nCols];
+        int ptr = 0;
+        for (int i = 0; i < nRows; i++) {
+            System.arraycopy(data, ptr, matrixData[i], 0, nCols);
+            ptr += nCols;
+        }
+        return new Array2DRowRealMatrix(matrixData);
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/stat/correlation/PearsonsCorrelationTest.java b/src/test/java/org/apache/commons/math/stat/correlation/PearsonsCorrelationTest.java
new file mode 100644
index 0000000..bf34ef2
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/correlation/PearsonsCorrelationTest.java
@@ -0,0 +1,308 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.stat.correlation;
+
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.distribution.TDistribution;
+import org.apache.commons.math.distribution.TDistributionImpl;
+import org.apache.commons.math.linear.RealMatrix;
+import org.apache.commons.math.linear.BlockRealMatrix;
+import org.apache.commons.math.util.FastMath;
+
+import junit.framework.TestCase;
+
+public class PearsonsCorrelationTest extends TestCase {
+
+    protected final double[] longleyData = new double[] {
+            60323,83.0,234289,2356,1590,107608,1947,
+            61122,88.5,259426,2325,1456,108632,1948,
+            60171,88.2,258054,3682,1616,109773,1949,
+            61187,89.5,284599,3351,1650,110929,1950,
+            63221,96.2,328975,2099,3099,112075,1951,
+            63639,98.1,346999,1932,3594,113270,1952,
+            64989,99.0,365385,1870,3547,115094,1953,
+            63761,100.0,363112,3578,3350,116219,1954,
+            66019,101.2,397469,2904,3048,117388,1955,
+            67857,104.6,419180,2822,2857,118734,1956,
+            68169,108.4,442769,2936,2798,120445,1957,
+            66513,110.8,444546,4681,2637,121950,1958,
+            68655,112.6,482704,3813,2552,123366,1959,
+            69564,114.2,502601,3931,2514,125368,1960,
+            69331,115.7,518173,4806,2572,127852,1961,
+            70551,116.9,554894,4007,2827,130081,1962
+        };
+
+    protected final double[] swissData = new double[] {
+            80.2,17.0,15,12,9.96,
+            83.1,45.1,6,9,84.84,
+            92.5,39.7,5,5,93.40,
+            85.8,36.5,12,7,33.77,
+            76.9,43.5,17,15,5.16,
+            76.1,35.3,9,7,90.57,
+            83.8,70.2,16,7,92.85,
+            92.4,67.8,14,8,97.16,
+            82.4,53.3,12,7,97.67,
+            82.9,45.2,16,13,91.38,
+            87.1,64.5,14,6,98.61,
+            64.1,62.0,21,12,8.52,
+            66.9,67.5,14,7,2.27,
+            68.9,60.7,19,12,4.43,
+            61.7,69.3,22,5,2.82,
+            68.3,72.6,18,2,24.20,
+            71.7,34.0,17,8,3.30,
+            55.7,19.4,26,28,12.11,
+            54.3,15.2,31,20,2.15,
+            65.1,73.0,19,9,2.84,
+            65.5,59.8,22,10,5.23,
+            65.0,55.1,14,3,4.52,
+            56.6,50.9,22,12,15.14,
+            57.4,54.1,20,6,4.20,
+            72.5,71.2,12,1,2.40,
+            74.2,58.1,14,8,5.23,
+            72.0,63.5,6,3,2.56,
+            60.5,60.8,16,10,7.72,
+            58.3,26.8,25,19,18.46,
+            65.4,49.5,15,8,6.10,
+            75.5,85.9,3,2,99.71,
+            69.3,84.9,7,6,99.68,
+            77.3,89.7,5,2,100.00,
+            70.5,78.2,12,6,98.96,
+            79.4,64.9,7,3,98.22,
+            65.0,75.9,9,9,99.06,
+            92.2,84.6,3,3,99.46,
+            79.3,63.1,13,13,96.83,
+            70.4,38.4,26,12,5.62,
+            65.7,7.7,29,11,13.79,
+            72.7,16.7,22,13,11.22,
+            64.4,17.6,35,32,16.92,
+            77.6,37.6,15,7,4.97,
+            67.6,18.7,25,7,8.65,
+            35.0,1.2,37,53,42.34,
+            44.7,46.6,16,29,50.43,
+            42.8,27.7,22,29,58.33
+        };
+
+
+    /**
+     * Test Longley dataset against R.
+     */
+    public void testLongly() throws Exception {
+        RealMatrix matrix = createRealMatrix(longleyData, 16, 7);
+        PearsonsCorrelation corrInstance = new PearsonsCorrelation(matrix);
+        RealMatrix correlationMatrix = corrInstance.getCorrelationMatrix();
+        double[] rData = new double[] {
+                1.000000000000000, 0.9708985250610560, 0.9835516111796693, 0.5024980838759942,
+                0.4573073999764817, 0.960390571594376, 0.9713294591921188,
+                0.970898525061056, 1.0000000000000000, 0.9915891780247822, 0.6206333925590966,
+                0.4647441876006747, 0.979163432977498, 0.9911491900672053,
+                0.983551611179669, 0.9915891780247822, 1.0000000000000000, 0.6042609398895580,
+                0.4464367918926265, 0.991090069458478, 0.9952734837647849,
+                0.502498083875994, 0.6206333925590966, 0.6042609398895580, 1.0000000000000000,
+                -0.1774206295018783, 0.686551516365312, 0.6682566045621746,
+                0.457307399976482, 0.4647441876006747, 0.4464367918926265, -0.1774206295018783,
+                1.0000000000000000, 0.364416267189032, 0.4172451498349454,
+                0.960390571594376, 0.9791634329774981, 0.9910900694584777, 0.6865515163653120,
+                0.3644162671890320, 1.000000000000000, 0.9939528462329257,
+                0.971329459192119, 0.9911491900672053, 0.9952734837647849, 0.6682566045621746,
+                0.4172451498349454, 0.993952846232926, 1.0000000000000000
+        };
+        TestUtils.assertEquals("correlation matrix", createRealMatrix(rData, 7, 7), correlationMatrix, 10E-15);
+
+        double[] rPvalues = new double[] {
+                4.38904690369668e-10,
+                8.36353208910623e-12, 7.8159700933611e-14,
+                0.0472894097790304, 0.01030636128354301, 0.01316878049026582,
+                0.0749178049642416, 0.06971758330341182, 0.0830166169296545, 0.510948586323452,
+                3.693245043123738e-09, 4.327782576751815e-11, 1.167954621905665e-13, 0.00331028281967516, 0.1652293725106684,
+                3.95834476307755e-10, 1.114663916723657e-13, 1.332267629550188e-15, 0.00466039138541463, 0.1078477071581498, 7.771561172376096e-15
+        };
+        RealMatrix rPMatrix = createLowerTriangularRealMatrix(rPvalues, 7);
+        fillUpper(rPMatrix, 0d);
+        TestUtils.assertEquals("correlation p values", rPMatrix, corrInstance.getCorrelationPValues(), 10E-15);
+    }
+
+    /**
+     * Test R Swiss fertility dataset against R.
+     */
+    public void testSwissFertility() throws Exception {
+         RealMatrix matrix = createRealMatrix(swissData, 47, 5);
+         PearsonsCorrelation corrInstance = new PearsonsCorrelation(matrix);
+         RealMatrix correlationMatrix = corrInstance.getCorrelationMatrix();
+         double[] rData = new double[] {
+               1.0000000000000000, 0.3530791836199747, -0.6458827064572875, -0.6637888570350691,  0.4636847006517939,
+                 0.3530791836199747, 1.0000000000000000,-0.6865422086171366, -0.6395225189483201, 0.4010950530487398,
+                -0.6458827064572875, -0.6865422086171366, 1.0000000000000000, 0.6984152962884830, -0.5727418060641666,
+                -0.6637888570350691, -0.6395225189483201, 0.6984152962884830, 1.0000000000000000, -0.1538589170909148,
+                 0.4636847006517939, 0.4010950530487398, -0.5727418060641666, -0.1538589170909148, 1.0000000000000000
+         };
+         TestUtils.assertEquals("correlation matrix", createRealMatrix(rData, 5, 5), correlationMatrix, 10E-15);
+
+         double[] rPvalues = new double[] {
+                 0.01491720061472623,
+                 9.45043734069043e-07, 9.95151527133974e-08,
+                 3.658616965962355e-07, 1.304590105694471e-06, 4.811397236181847e-08,
+                 0.001028523190118147, 0.005204433539191644, 2.588307925380906e-05, 0.301807756132683
+         };
+         RealMatrix rPMatrix = createLowerTriangularRealMatrix(rPvalues, 5);
+         fillUpper(rPMatrix, 0d);
+         TestUtils.assertEquals("correlation p values", rPMatrix, corrInstance.getCorrelationPValues(), 10E-15);
+    }
+    
+    /**
+     * Test p-value near 0. JIRA: MATH-371
+     */
+    public void testPValueNearZero() throws Exception {
+        /*
+         * Create a dataset that has r -> 1, p -> 0 as dimension increases.
+         * Prior to the fix for MATH-371, p vanished for dimension >= 14.
+         * Post fix, p-values diminish smoothly, vanishing at dimension = 127.
+         * Tested value is ~1E-303.
+         */
+        int dimension = 120; 
+        double[][] data = new double[dimension][2];
+        for (int i = 0; i < dimension; i++) {
+            data[i][0] = i;
+            data[i][1] = i + 1/((double)i + 1);
+        }
+        PearsonsCorrelation corrInstance = new PearsonsCorrelation(data);
+        assertTrue(corrInstance.getCorrelationPValues().getEntry(0, 1) > 0);
+    }
+    
+
+    /**
+     * Constant column
+     */
+    public void testConstant() {
+        double[] noVariance = new double[] {1, 1, 1, 1};
+        double[] values = new double[] {1, 2, 3, 4};
+        assertTrue(Double.isNaN(new PearsonsCorrelation().correlation(noVariance, values)));
+    }
+
+
+    /**
+     * Insufficient data
+     */
+
+    public void testInsufficientData() {
+        double[] one = new double[] {1};
+        double[] two = new double[] {2};
+        try {
+            new PearsonsCorrelation().correlation(one, two);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+        RealMatrix matrix = new BlockRealMatrix(new double[][] {{0},{1}});
+        try {
+            new PearsonsCorrelation(matrix);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+    }
+
+    /**
+     * Verify that direct t-tests using standard error estimates are consistent
+     * with reported p-values
+     */
+    public void testStdErrorConsistency() throws Exception {
+        TDistribution tDistribution = new TDistributionImpl(45);
+        RealMatrix matrix = createRealMatrix(swissData, 47, 5);
+        PearsonsCorrelation corrInstance = new PearsonsCorrelation(matrix);
+        RealMatrix rValues = corrInstance.getCorrelationMatrix();
+        RealMatrix pValues = corrInstance.getCorrelationPValues();
+        RealMatrix stdErrors = corrInstance.getCorrelationStandardErrors();
+        for (int i = 0; i < 5; i++) {
+            for (int j = 0; j < i; j++) {
+                double t = FastMath.abs(rValues.getEntry(i, j)) / stdErrors.getEntry(i, j);
+                double p = 2 * (1 - tDistribution.cumulativeProbability(t));
+                assertEquals(p, pValues.getEntry(i, j), 10E-15);
+            }
+        }
+    }
+
+    /**
+     * Verify that creating correlation from covariance gives same results as
+     * direct computation from the original matrix
+     */
+    public void testCovarianceConsistency() throws Exception {
+        RealMatrix matrix = createRealMatrix(longleyData, 16, 7);
+        PearsonsCorrelation corrInstance = new PearsonsCorrelation(matrix);
+        Covariance covInstance = new Covariance(matrix);
+        PearsonsCorrelation corrFromCovInstance = new PearsonsCorrelation(covInstance);
+        TestUtils.assertEquals("correlation values", corrInstance.getCorrelationMatrix(),
+                corrFromCovInstance.getCorrelationMatrix(), 10E-15);
+        TestUtils.assertEquals("p values", corrInstance.getCorrelationPValues(),
+                corrFromCovInstance.getCorrelationPValues(), 10E-15);
+        TestUtils.assertEquals("standard errors", corrInstance.getCorrelationStandardErrors(),
+                corrFromCovInstance.getCorrelationStandardErrors(), 10E-15);
+
+        PearsonsCorrelation corrFromCovInstance2 =
+            new PearsonsCorrelation(covInstance.getCovarianceMatrix(), 16);
+        TestUtils.assertEquals("correlation values", corrInstance.getCorrelationMatrix(),
+                corrFromCovInstance2.getCorrelationMatrix(), 10E-15);
+        TestUtils.assertEquals("p values", corrInstance.getCorrelationPValues(),
+                corrFromCovInstance2.getCorrelationPValues(), 10E-15);
+        TestUtils.assertEquals("standard errors", corrInstance.getCorrelationStandardErrors(),
+                corrFromCovInstance2.getCorrelationStandardErrors(), 10E-15);
+    }
+
+
+    public void testConsistency() {
+        RealMatrix matrix = createRealMatrix(longleyData, 16, 7);
+        PearsonsCorrelation corrInstance = new PearsonsCorrelation(matrix);
+        double[][] data = matrix.getData();
+        double[] x = matrix.getColumn(0);
+        double[] y = matrix.getColumn(1);
+        assertEquals(new PearsonsCorrelation().correlation(x, y),
+                corrInstance.getCorrelationMatrix().getEntry(0, 1), Double.MIN_VALUE);
+        TestUtils.assertEquals("Correlation matrix", corrInstance.getCorrelationMatrix(),
+                new PearsonsCorrelation().computeCorrelationMatrix(data), Double.MIN_VALUE);
+    }
+
+    protected RealMatrix createRealMatrix(double[] data, int nRows, int nCols) {
+        double[][] matrixData = new double[nRows][nCols];
+        int ptr = 0;
+        for (int i = 0; i < nRows; i++) {
+            System.arraycopy(data, ptr, matrixData[i], 0, nCols);
+            ptr += nCols;
+        }
+        return new BlockRealMatrix(matrixData);
+    }
+
+    protected RealMatrix createLowerTriangularRealMatrix(double[] data, int dimension) {
+        int ptr = 0;
+        RealMatrix result = new BlockRealMatrix(dimension, dimension);
+        for (int i = 1; i < dimension; i++) {
+            for (int j = 0; j < i; j++) {
+                result.setEntry(i, j, data[ptr]);
+                ptr++;
+            }
+        }
+        return result;
+    }
+
+    protected void fillUpper(RealMatrix matrix, double diagonalValue) {
+        int dimension = matrix.getColumnDimension();
+        for (int i = 0; i < dimension; i++) {
+            matrix.setEntry(i, i, diagonalValue);
+            for (int j = i+1; j < dimension; j++) {
+                matrix.setEntry(i, j, matrix.getEntry(j, i));
+            }
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/stat/correlation/SpearmansRankCorrelationTest.java b/src/test/java/org/apache/commons/math/stat/correlation/SpearmansRankCorrelationTest.java
new file mode 100644
index 0000000..99eb065
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/correlation/SpearmansRankCorrelationTest.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.stat.correlation;
+
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.linear.BlockRealMatrix;
+import org.apache.commons.math.linear.RealMatrix;
+
+/**
+ * Test cases for Spearman's rank correlation
+ *
+ * @since 2.0
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+public class SpearmansRankCorrelationTest extends PearsonsCorrelationTest {
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    /**
+     * Test Longley dataset against R.
+     */
+    @Override
+    public void testLongly() throws Exception {
+        RealMatrix matrix = createRealMatrix(longleyData, 16, 7);
+        SpearmansCorrelation corrInstance = new SpearmansCorrelation(matrix);
+        RealMatrix correlationMatrix = corrInstance.getCorrelationMatrix();
+        double[] rData = new double[] {
+                1, 0.982352941176471, 0.985294117647059, 0.564705882352941, 0.2264705882352941, 0.976470588235294,
+                0.976470588235294, 0.982352941176471, 1, 0.997058823529412, 0.664705882352941, 0.2205882352941176,
+                0.997058823529412, 0.997058823529412, 0.985294117647059, 0.997058823529412, 1, 0.638235294117647,
+                0.2235294117647059, 0.9941176470588236, 0.9941176470588236, 0.564705882352941, 0.664705882352941,
+                0.638235294117647, 1, -0.3411764705882353, 0.685294117647059, 0.685294117647059, 0.2264705882352941,
+                0.2205882352941176, 0.2235294117647059, -0.3411764705882353, 1, 0.2264705882352941, 0.2264705882352941,
+                0.976470588235294, 0.997058823529412, 0.9941176470588236, 0.685294117647059, 0.2264705882352941, 1, 1,
+                0.976470588235294, 0.997058823529412, 0.9941176470588236, 0.685294117647059, 0.2264705882352941, 1, 1
+        };
+        TestUtils.assertEquals("Spearman's correlation matrix", createRealMatrix(rData, 7, 7), correlationMatrix, 10E-15);
+    }
+
+    /**
+     * Test R swiss fertility dataset.
+     */
+    public void testSwiss() throws Exception {
+        RealMatrix matrix = createRealMatrix(swissData, 47, 5);
+        SpearmansCorrelation corrInstance = new SpearmansCorrelation(matrix);
+        RealMatrix correlationMatrix = corrInstance.getCorrelationMatrix();
+        double[] rData = new double[] {
+                1, 0.2426642769364176, -0.660902996352354, -0.443257690360988, 0.4136455623012432,
+                0.2426642769364176, 1, -0.598859938748963, -0.650463814145816, 0.2886878090882852,
+               -0.660902996352354, -0.598859938748963, 1, 0.674603831406147, -0.4750575257171745,
+               -0.443257690360988, -0.650463814145816, 0.674603831406147, 1, -0.1444163088302244,
+                0.4136455623012432, 0.2886878090882852, -0.4750575257171745, -0.1444163088302244, 1
+        };
+        TestUtils.assertEquals("Spearman's correlation matrix", createRealMatrix(rData, 5, 5), correlationMatrix, 10E-15);
+    }
+
+    /**
+     * Constant column
+     */
+    @Override
+    public void testConstant() {
+        double[] noVariance = new double[] {1, 1, 1, 1};
+        double[] values = new double[] {1, 2, 3, 4};
+        assertTrue(Double.isNaN(new SpearmansCorrelation().correlation(noVariance, values)));
+    }
+
+    /**
+     * Insufficient data
+     */
+    @Override
+    public void testInsufficientData() {
+        double[] one = new double[] {1};
+        double[] two = new double[] {2};
+        try {
+            new SpearmansCorrelation().correlation(one, two);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+        RealMatrix matrix = new BlockRealMatrix(new double[][] {{0},{1}});
+        try {
+            new SpearmansCorrelation(matrix);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+    }
+
+    @Override
+    public void testConsistency() {
+        RealMatrix matrix = createRealMatrix(longleyData, 16, 7);
+        SpearmansCorrelation corrInstance = new SpearmansCorrelation(matrix);
+        double[][] data = matrix.getData();
+        double[] x = matrix.getColumn(0);
+        double[] y = matrix.getColumn(1);
+        assertEquals(new SpearmansCorrelation().correlation(x, y),
+                corrInstance.getCorrelationMatrix().getEntry(0, 1), Double.MIN_VALUE);
+        TestUtils.assertEquals("Correlation matrix", corrInstance.getCorrelationMatrix(),
+                new SpearmansCorrelation().computeCorrelationMatrix(data), Double.MIN_VALUE);
+    }
+
+    // Not relevant here
+    @Override
+    public void testStdErrorConsistency() throws Exception {}
+    @Override
+    public void testCovarianceConsistency() throws Exception {}
+
+}
diff --git a/src/test/java/org/apache/commons/math/stat/data/CertifiedDataAbstractTest.java b/src/test/java/org/apache/commons/math/stat/data/CertifiedDataAbstractTest.java
new file mode 100644
index 0000000..319758b
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/data/CertifiedDataAbstractTest.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.stat.data;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.stat.descriptive.DescriptiveStatistics;
+import org.apache.commons.math.stat.descriptive.SummaryStatistics;
+
+/**
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+public abstract class CertifiedDataAbstractTest extends TestCase {
+
+    private DescriptiveStatistics descriptives;
+
+    private SummaryStatistics summaries;
+
+    private Map<String, Double> certifiedValues;
+
+    @Override
+    protected void setUp() throws Exception {
+        descriptives = new DescriptiveStatistics();
+        summaries = new SummaryStatistics();
+        certifiedValues = new HashMap<String, Double>();
+
+        loadData();
+    }
+
+    private void loadData() throws IOException {
+        BufferedReader in = null;
+
+        try {
+            URL resourceURL = getClass().getClassLoader().getResource(getResourceName());
+            in = new BufferedReader(new InputStreamReader(resourceURL.openStream()));
+
+            String line = in.readLine();
+            while (line != null) {
+
+                /* this call to StringUtils did little for the
+                 * following conditional structure
+                 */
+                line = line.trim();
+
+                // not empty line or comment
+                if (!("".equals(line) || line.startsWith("#"))) {
+                    int n = line.indexOf('=');
+                    if (n == -1) {
+                        // data value
+                        double value = Double.parseDouble(line);
+                        descriptives.addValue(value);
+                        summaries.addValue(value);
+                    } else {
+                        // certified value
+                        String name = line.substring(0, n).trim();
+                        String valueString = line.substring(n + 1).trim();
+                        Double value = Double.valueOf(valueString);
+                        certifiedValues.put(name, value);
+                    }
+                }
+                line = in.readLine();
+            }
+        } finally {
+            if (in != null) {
+                in.close();
+            }
+        }
+    }
+
+    protected abstract String getResourceName();
+
+    protected double getMaximumAbsoluteError() {
+        return 1.0e-5;
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        descriptives.clear();
+        descriptives = null;
+
+        summaries.clear();
+        summaries = null;
+
+        certifiedValues.clear();
+        certifiedValues = null;
+    }
+
+    public void testCertifiedValues() {
+        for (String name : certifiedValues.keySet()) {
+            Double expectedValue = certifiedValues.get(name);
+
+            Double summariesValue = getProperty(summaries, name);
+            if (summariesValue != null) {
+                TestUtils.assertEquals("summary value for " + name + " is incorrect.",
+                                       summariesValue.doubleValue(), expectedValue.doubleValue(),
+                                       getMaximumAbsoluteError());
+            }
+
+            Double descriptivesValue = getProperty(descriptives, name);
+            if (descriptivesValue != null) {
+                TestUtils.assertEquals("descriptive value for " + name + " is incorrect.",
+                                       descriptivesValue.doubleValue(), expectedValue.doubleValue(),
+                                       getMaximumAbsoluteError());
+            }
+        }
+    }
+
+
+    protected Double getProperty(Object bean, String name) {
+        try {
+            // Get the value of prop
+            String prop = "get" + name.substring(0,1).toUpperCase() + name.substring(1);
+            Method meth = bean.getClass().getMethod(prop, new Class[0]);
+            Object property = meth.invoke(bean, new Object[0]);
+            if (meth.getReturnType().equals(Double.TYPE)) {
+                return (Double) property;
+            } else if (meth.getReturnType().equals(Long.TYPE)) {
+                return Double.valueOf(((Long) property).doubleValue());
+            } else {
+                fail("wrong type: " + meth.getReturnType().getName());
+            }
+        } catch (NoSuchMethodException nsme) {
+            // ignored
+        } catch (InvocationTargetException ite) {
+            fail(ite.getMessage());
+        } catch (IllegalAccessException iae) {
+            fail(iae.getMessage());
+        }
+        return null;
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/stat/data/LewTest.java b/src/test/java/org/apache/commons/math/stat/data/LewTest.java
new file mode 100644
index 0000000..2f72a82
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/data/LewTest.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.stat.data;
+
+/**
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+public class LewTest extends CertifiedDataAbstractTest {
+
+    @Override
+    protected String getResourceName() {
+        return "org/apache/commons/math/stat/data/Lew.txt";
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/stat/data/LotteryTest.java b/src/test/java/org/apache/commons/math/stat/data/LotteryTest.java
new file mode 100644
index 0000000..ad70dc8
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/data/LotteryTest.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.stat.data;
+
+/**
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+public class LotteryTest extends CertifiedDataAbstractTest {
+
+    @Override
+    protected String getResourceName() {
+        return "org/apache/commons/math/stat/data/Lottery.txt";
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/stat/descriptive/AbstractUnivariateStatisticTest.java b/src/test/java/org/apache/commons/math/stat/descriptive/AbstractUnivariateStatisticTest.java
new file mode 100644
index 0000000..4d3159b
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/descriptive/AbstractUnivariateStatisticTest.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.stat.descriptive;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.math.stat.descriptive.moment.Mean;
+
+/**
+ * Tests for AbstractUnivariateStatistic
+ *
+ * @version $Revision: 902201 $ $Date: 2010-01-22 19:18:16 +0100 (ven. 22 janv. 2010) $
+ */
+public class AbstractUnivariateStatisticTest extends TestCase {
+
+    public AbstractUnivariateStatisticTest(String name) {
+        super(name);
+    }
+
+    protected double[] testArray = {0, 1, 2, 3, 4, 5};
+    protected double[] testWeightsArray = {0.3, 0.2, 1.3, 1.1, 1.0, 1.8};
+    protected double[] testNegativeWeightsArray = {-0.3, 0.2, -1.3, 1.1, 1.0, 1.8};
+    protected double[] nullArray = null;
+    protected double[] singletonArray = {0};
+    protected Mean testStatistic = new Mean();
+
+    public void testTestPositive() {
+        for (int j = 0; j < 6; j++) {
+            for (int i = 1; i < (7 - j); i++) {
+                assertTrue(testStatistic.test(testArray, 0, i));
+            }
+        }
+        assertTrue(testStatistic.test(singletonArray, 0, 1));
+    }
+
+    public void testTestNegative() {
+        assertFalse(testStatistic.test(singletonArray, 0, 0));
+        assertFalse(testStatistic.test(testArray, 0, 0));
+        try {
+            testStatistic.test(singletonArray, 2, 1);  // start past end
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            testStatistic.test(testArray, 0, 7);  // end past end
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            testStatistic.test(testArray, -1, 1);  // start negative
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            testStatistic.test(testArray, 0, -1);  // length negative
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            testStatistic.test(nullArray, 0, 1);  // null array
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            testStatistic.test(testArray, nullArray, 0, 1);  // null weights array
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            testStatistic.test(singletonArray, testWeightsArray, 0, 1);  // weights.length != value.length
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            testStatistic.test(testArray, testNegativeWeightsArray, 0, 6);  // can't have negative weights
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/stat/descriptive/AggregateSummaryStatisticsTest.java b/src/test/java/org/apache/commons/math/stat/descriptive/AggregateSummaryStatisticsTest.java
new file mode 100644
index 0000000..d4e7a11
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/descriptive/AggregateSummaryStatisticsTest.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.stat.descriptive;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.random.RandomData;
+import org.apache.commons.math.random.RandomDataImpl;
+
+
+/**
+ * Test cases for {@link AggregateSummaryStatistics}
+ *
+ */
+public class AggregateSummaryStatisticsTest extends TestCase {
+
+    /**
+     * Tests the standard aggregation behavior
+     */
+    public void testAggregation() {
+        AggregateSummaryStatistics aggregate = new AggregateSummaryStatistics();
+        SummaryStatistics setOneStats = aggregate.createContributingStatistics();
+        SummaryStatistics setTwoStats = aggregate.createContributingStatistics();
+
+        assertNotNull("The set one contributing stats are null", setOneStats);
+        assertNotNull("The set two contributing stats are null", setTwoStats);
+        assertNotSame("Contributing stats objects are the same", setOneStats, setTwoStats);
+
+        setOneStats.addValue(2);
+        setOneStats.addValue(3);
+        setOneStats.addValue(5);
+        setOneStats.addValue(7);
+        setOneStats.addValue(11);
+        assertEquals("Wrong number of set one values", 5, setOneStats.getN());
+        assertEquals("Wrong sum of set one values", 28.0, setOneStats.getSum());
+
+        setTwoStats.addValue(2);
+        setTwoStats.addValue(4);
+        setTwoStats.addValue(8);
+        assertEquals("Wrong number of set two values", 3, setTwoStats.getN());
+        assertEquals("Wrong sum of set two values", 14.0, setTwoStats.getSum());
+
+        assertEquals("Wrong number of aggregate values", 8, aggregate.getN());
+        assertEquals("Wrong aggregate sum", 42.0, aggregate.getSum());
+    }
+
+    /**
+     * Verify that aggregating over a partition gives the same results
+     * as direct computation.
+     *
+     *  1) Randomly generate a dataset of 10-100 values
+     *     from [-100, 100]
+     *  2) Divide the dataset it into 2-5 partitions
+     *  3) Create an AggregateSummaryStatistic and ContributingStatistics
+     *     for each partition
+     *  4) Compare results from the AggregateSummaryStatistic with values
+     *     returned by a single SummaryStatistics instance that is provided
+     *     the full dataset
+     */
+    public void testAggregationConsistency() throws Exception {
+
+        // Generate a random sample and random partition
+        double[] totalSample = generateSample();
+        double[][] subSamples = generatePartition(totalSample);
+        int nSamples = subSamples.length;
+
+        // Create aggregator and total stats for comparison
+        AggregateSummaryStatistics aggregate = new AggregateSummaryStatistics();
+        SummaryStatistics totalStats = new SummaryStatistics();
+
+        // Create array of component stats
+        SummaryStatistics componentStats[] = new SummaryStatistics[nSamples];
+
+        for (int i = 0; i < nSamples; i++) {
+
+            // Make componentStats[i] a contributing statistic to aggregate
+            componentStats[i] = aggregate.createContributingStatistics();
+
+            // Add values from subsample
+            for (int j = 0; j < subSamples[i].length; j++) {
+                componentStats[i].addValue(subSamples[i][j]);
+            }
+        }
+
+        // Compute totalStats directly
+        for (int i = 0; i < totalSample.length; i++) {
+            totalStats.addValue(totalSample[i]);
+        }
+
+        /*
+         * Compare statistics in totalStats with aggregate.
+         * Note that guaranteed success of this comparison depends on the
+         * fact that <aggregate> gets values in exactly the same order
+         * as <totalStats>.
+         *
+         */
+        assertEquals(totalStats.getSummary(), aggregate.getSummary());
+
+    }
+
+    /**
+     * Test aggregate function by randomly generating a dataset of 10-100 values
+     * from [-100, 100], dividing it into 2-5 partitions, computing stats for each
+     * partition and comparing the result of aggregate(...) applied to the collection
+     * of per-partition SummaryStatistics with a single SummaryStatistics computed
+     * over the full sample.
+     *
+     * @throws Exception
+     */
+    public void testAggregate() throws Exception {
+
+        // Generate a random sample and random partition
+        double[] totalSample = generateSample();
+        double[][] subSamples = generatePartition(totalSample);
+        int nSamples = subSamples.length;
+
+        // Compute combined stats directly
+        SummaryStatistics totalStats = new SummaryStatistics();
+        for (int i = 0; i < totalSample.length; i++) {
+            totalStats.addValue(totalSample[i]);
+        }
+
+        // Now compute subsample stats individually and aggregate
+        SummaryStatistics[] subSampleStats = new SummaryStatistics[nSamples];
+        for (int i = 0; i < nSamples; i++) {
+            subSampleStats[i] = new SummaryStatistics();
+        }
+        Collection<SummaryStatistics> aggregate = new ArrayList<SummaryStatistics>();
+        for (int i = 0; i < nSamples; i++) {
+            for (int j = 0; j < subSamples[i].length; j++) {
+                subSampleStats[i].addValue(subSamples[i][j]);
+            }
+            aggregate.add(subSampleStats[i]);
+        }
+
+        // Compare values
+        StatisticalSummary aggregatedStats = AggregateSummaryStatistics.aggregate(aggregate);
+        assertEquals(totalStats.getSummary(), aggregatedStats, 10E-12);
+    }
+
+
+    public void testAggregateDegenerate() throws Exception {
+        double[] totalSample = {1, 2, 3, 4, 5};
+        double[][] subSamples = {{1}, {2}, {3}, {4}, {5}};
+
+        // Compute combined stats directly
+        SummaryStatistics totalStats = new SummaryStatistics();
+        for (int i = 0; i < totalSample.length; i++) {
+            totalStats.addValue(totalSample[i]);
+        }
+
+        // Now compute subsample stats individually and aggregate
+        SummaryStatistics[] subSampleStats = new SummaryStatistics[5];
+        for (int i = 0; i < 5; i++) {
+            subSampleStats[i] = new SummaryStatistics();
+        }
+        Collection<SummaryStatistics> aggregate = new ArrayList<SummaryStatistics>();
+        for (int i = 0; i < 5; i++) {
+            for (int j = 0; j < subSamples[i].length; j++) {
+                subSampleStats[i].addValue(subSamples[i][j]);
+            }
+            aggregate.add(subSampleStats[i]);
+        }
+
+        // Compare values
+        StatisticalSummaryValues aggregatedStats = AggregateSummaryStatistics.aggregate(aggregate);
+        assertEquals(totalStats.getSummary(), aggregatedStats, 10E-12);
+    }
+
+    public void testAggregateSpecialValues() throws Exception {
+        double[] totalSample = {Double.POSITIVE_INFINITY, 2, 3, Double.NaN, 5};
+        double[][] subSamples = {{Double.POSITIVE_INFINITY, 2}, {3}, {Double.NaN}, {5}};
+
+        // Compute combined stats directly
+        SummaryStatistics totalStats = new SummaryStatistics();
+        for (int i = 0; i < totalSample.length; i++) {
+            totalStats.addValue(totalSample[i]);
+        }
+
+        // Now compute subsample stats individually and aggregate
+        SummaryStatistics[] subSampleStats = new SummaryStatistics[5];
+        for (int i = 0; i < 4; i++) {
+            subSampleStats[i] = new SummaryStatistics();
+        }
+        Collection<SummaryStatistics> aggregate = new ArrayList<SummaryStatistics>();
+        for (int i = 0; i < 4; i++) {
+            for (int j = 0; j < subSamples[i].length; j++) {
+                subSampleStats[i].addValue(subSamples[i][j]);
+            }
+            aggregate.add(subSampleStats[i]);
+        }
+
+        // Compare values
+        StatisticalSummaryValues aggregatedStats = AggregateSummaryStatistics.aggregate(aggregate);
+        assertEquals(totalStats.getSummary(), aggregatedStats, 10E-12);
+
+    }
+
+    /**
+     * Verifies that a StatisticalSummary and a StatisticalSummaryValues are equal up
+     * to delta, with NaNs, infinities returned in the same spots. For max, min, n, values
+     * have to agree exactly, delta is used only for sum, mean, variance, std dev.
+     */
+    protected static void assertEquals(StatisticalSummary expected, StatisticalSummary observed, double delta) {
+        TestUtils.assertEquals(expected.getMax(), observed.getMax(), 0);
+        TestUtils.assertEquals(expected.getMin(), observed.getMin(), 0);
+        assertEquals(expected.getN(), observed.getN());
+        TestUtils.assertEquals(expected.getSum(), observed.getSum(), delta);
+        TestUtils.assertEquals(expected.getMean(), observed.getMean(), delta);
+        TestUtils.assertEquals(expected.getStandardDeviation(), observed.getStandardDeviation(), delta);
+        TestUtils.assertEquals(expected.getVariance(), observed.getVariance(), delta);
+    }
+
+
+    /**
+     * Generates a random sample of double values.
+     * Sample size is random, between 10 and 100 and values are
+     * uniformly distributed over [-100, 100].
+     *
+     * @return array of random double values
+     */
+    private double[] generateSample() {
+        final RandomData randomData = new RandomDataImpl();
+        final int sampleSize = randomData.nextInt(10,100);
+        double[] out = new double[sampleSize];
+        for (int i = 0; i < out.length; i++) {
+            out[i] = randomData.nextUniform(-100, 100);
+        }
+        return out;
+    }
+
+    /**
+     * Generates a partition of <sample> into up to 5 sequentially selected
+     * subsamples with randomly selected partition points.
+     *
+     * @param sample array to partition
+     * @return rectangular array with rows = subsamples
+     */
+    private double[][] generatePartition(double[] sample) {
+        final int length = sample.length;
+        final double[][] out = new double[5][];
+        final RandomData randomData = new RandomDataImpl();
+        int cur = 0;
+        int offset = 0;
+        int sampleCount = 0;
+        for (int i = 0; i < 5; i++) {
+            if (cur == length || offset == length) {
+                break;
+            }
+            final int next = (i == 4 || cur == length - 1) ? length - 1 : randomData.nextInt(cur, length - 1);
+            final int subLength = next - cur + 1;
+            out[i] = new double[subLength];
+            System.arraycopy(sample, offset, out[i], 0, subLength);
+            cur = next + 1;
+            sampleCount++;
+            offset += subLength;
+        }
+        if (sampleCount < 5) {
+            double[][] out2 = new double[sampleCount][];
+            for (int j = 0; j < sampleCount; j++) {
+                final int curSize = out[j].length;
+                out2[j] = new double[curSize];
+                System.arraycopy(out[j], 0, out2[j], 0, curSize);
+            }
+            return out2;
+        } else {
+            return out;
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/stat/descriptive/DescriptiveStatisticsTest.java b/src/test/java/org/apache/commons/math/stat/descriptive/DescriptiveStatisticsTest.java
new file mode 100644
index 0000000..c86081f
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/descriptive/DescriptiveStatisticsTest.java
@@ -0,0 +1,296 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
+ * or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.apache.commons.math.stat.descriptive;
+
+import java.util.Locale;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.math.stat.descriptive.rank.Percentile;
+import org.apache.commons.math.util.MathUtils;
+
+/**
+ * Test cases for the DescriptiveStatistics class.
+ *
+ * @version $Revision: 1003907 $ $Date: 2007-08-16 15:36:33 -0500 (Thu, 16 Aug
+ *          2007) $
+ */
+public class DescriptiveStatisticsTest extends TestCase {
+
+    public DescriptiveStatisticsTest(String name) {
+        super(name);
+    }
+
+    protected DescriptiveStatistics createDescriptiveStatistics() {
+        return new DescriptiveStatistics();
+    }
+
+    public void testSetterInjection() {
+        DescriptiveStatistics stats = createDescriptiveStatistics();
+        stats.addValue(1);
+        stats.addValue(3);
+        assertEquals(2, stats.getMean(), 1E-10);
+        // Now lets try some new math
+        stats.setMeanImpl(new deepMean());
+        assertEquals(42, stats.getMean(), 1E-10);
+    }
+
+    public void testCopy() {
+        DescriptiveStatistics stats = createDescriptiveStatistics();
+        stats.addValue(1);
+        stats.addValue(3);
+        DescriptiveStatistics copy = new DescriptiveStatistics(stats);
+        assertEquals(2, copy.getMean(), 1E-10);
+        // Now lets try some new math
+        stats.setMeanImpl(new deepMean());
+        copy = stats.copy();
+        assertEquals(42, copy.getMean(), 1E-10);
+    }
+
+    public void testWindowSize() {
+        DescriptiveStatistics stats = createDescriptiveStatistics();
+        stats.setWindowSize(300);
+        for (int i = 0; i < 100; ++i) {
+            stats.addValue(i + 1);
+        }
+        int refSum = (100 * 101) / 2;
+        assertEquals(refSum / 100.0, stats.getMean(), 1E-10);
+        assertEquals(300, stats.getWindowSize());
+        try {
+            stats.setWindowSize(-3);
+            fail("an exception should have been thrown");
+        } catch (IllegalArgumentException iae) {
+            // expected
+        }
+        assertEquals(300, stats.getWindowSize());
+        stats.setWindowSize(50);
+        assertEquals(50, stats.getWindowSize());
+        int refSum2 = refSum - (50 * 51) / 2;
+        assertEquals(refSum2 / 50.0, stats.getMean(), 1E-10);
+    }
+
+    public void testGetValues() {
+        DescriptiveStatistics stats = createDescriptiveStatistics();
+        for (int i = 100; i > 0; --i) {
+            stats.addValue(i);
+        }
+        int refSum = (100 * 101) / 2;
+        assertEquals(refSum / 100.0, stats.getMean(), 1E-10);
+        double[] v = stats.getValues();
+        for (int i = 0; i < v.length; ++i) {
+            assertEquals(100.0 - i, v[i], 1.0e-10);
+        }
+        double[] s = stats.getSortedValues();
+        for (int i = 0; i < s.length; ++i) {
+            assertEquals(i + 1.0, s[i], 1.0e-10);
+        }
+        assertEquals(12.0, stats.getElement(88), 1.0e-10);
+    }
+
+    public void testToString() {
+        DescriptiveStatistics stats = createDescriptiveStatistics();
+        stats.addValue(1);
+        stats.addValue(2);
+        stats.addValue(3);
+        Locale d = Locale.getDefault();
+        Locale.setDefault(Locale.US);
+        assertEquals("DescriptiveStatistics:\n" +
+                     "n: 3\n" +
+                     "min: 1.0\n" +
+                     "max: 3.0\n" +
+                     "mean: 2.0\n" +
+                     "std dev: 1.0\n" +
+                     "median: 2.0\n" +
+                     "skewness: 0.0\n" +
+                     "kurtosis: NaN\n",  stats.toString());
+        Locale.setDefault(d);
+    }
+
+    public void testShuffledStatistics() {
+        // the purpose of this test is only to check the get/set methods
+        // we are aware shuffling statistics like this is really not
+        // something sensible to do in production ...
+        DescriptiveStatistics reference = createDescriptiveStatistics();
+        DescriptiveStatistics shuffled  = createDescriptiveStatistics();
+
+        UnivariateStatistic tmp = shuffled.getGeometricMeanImpl();
+        shuffled.setGeometricMeanImpl(shuffled.getMeanImpl());
+        shuffled.setMeanImpl(shuffled.getKurtosisImpl());
+        shuffled.setKurtosisImpl(shuffled.getSkewnessImpl());
+        shuffled.setSkewnessImpl(shuffled.getVarianceImpl());
+        shuffled.setVarianceImpl(shuffled.getMaxImpl());
+        shuffled.setMaxImpl(shuffled.getMinImpl());
+        shuffled.setMinImpl(shuffled.getSumImpl());
+        shuffled.setSumImpl(shuffled.getSumsqImpl());
+        shuffled.setSumsqImpl(tmp);
+
+        for (int i = 100; i > 0; --i) {
+            reference.addValue(i);
+            shuffled.addValue(i);
+        }
+
+        assertEquals(reference.getMean(),          shuffled.getGeometricMean(), 1.0e-10);
+        assertEquals(reference.getKurtosis(),      shuffled.getMean(),          1.0e-10);
+        assertEquals(reference.getSkewness(),      shuffled.getKurtosis(), 1.0e-10);
+        assertEquals(reference.getVariance(),      shuffled.getSkewness(), 1.0e-10);
+        assertEquals(reference.getMax(),           shuffled.getVariance(), 1.0e-10);
+        assertEquals(reference.getMin(),           shuffled.getMax(), 1.0e-10);
+        assertEquals(reference.getSum(),           shuffled.getMin(), 1.0e-10);
+        assertEquals(reference.getSumsq(),         shuffled.getSum(), 1.0e-10);
+        assertEquals(reference.getGeometricMean(), shuffled.getSumsq(), 1.0e-10);
+
+    }
+
+    public void testPercentileSetter() throws Exception {
+        DescriptiveStatistics stats = createDescriptiveStatistics();
+        stats.addValue(1);
+        stats.addValue(2);
+        stats.addValue(3);
+        assertEquals(2, stats.getPercentile(50.0), 1E-10);
+
+        // Inject wrapped Percentile impl
+        stats.setPercentileImpl(new goodPercentile());
+        assertEquals(2, stats.getPercentile(50.0), 1E-10);
+
+        // Try "new math" impl
+        stats.setPercentileImpl(new subPercentile());
+        assertEquals(10.0, stats.getPercentile(10.0), 1E-10);
+
+        // Try to set bad impl
+        try {
+            stats.setPercentileImpl(new badPercentile());
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+
+    public void test20090720() {
+        DescriptiveStatistics descriptiveStatistics = new DescriptiveStatistics(100);
+        for (int i = 0; i < 161; i++) {
+            descriptiveStatistics.addValue(1.2);
+        }
+        descriptiveStatistics.clear();
+        descriptiveStatistics.addValue(1.2);
+        assertEquals(1, descriptiveStatistics.getN());
+    }
+
+    public void testRemoval() {
+
+        final DescriptiveStatistics dstat = createDescriptiveStatistics();
+
+        checkremoval(dstat, 1, 6.0, 0.0, Double.NaN);
+        checkremoval(dstat, 3, 5.0, 3.0, 4.5);
+        checkremoval(dstat, 6, 3.5, 2.5, 3.0);
+        checkremoval(dstat, 9, 3.5, 2.5, 3.0);
+        checkremoval(dstat, DescriptiveStatistics.INFINITE_WINDOW, 3.5, 2.5, 3.0);
+
+    }
+
+    public void checkremoval(DescriptiveStatistics dstat, int wsize,
+                             double mean1, double mean2, double mean3) {
+
+        dstat.setWindowSize(wsize);
+        dstat.clear();
+
+        for (int i = 1 ; i <= 6 ; ++i) {
+            dstat.addValue(i);
+        }
+
+        assertTrue(MathUtils.equalsIncludingNaN(mean1, dstat.getMean()));
+        dstat.replaceMostRecentValue(0);
+        assertTrue(MathUtils.equalsIncludingNaN(mean2, dstat.getMean()));
+        dstat.removeMostRecentValue();
+        assertTrue(MathUtils.equalsIncludingNaN(mean3, dstat.getMean()));
+
+    }
+
+    // Test UnivariateStatistics impls for setter injection tests
+
+    /**
+     * A new way to compute the mean
+     */
+    static class deepMean implements UnivariateStatistic {
+
+        public double evaluate(double[] values, int begin, int length) {
+            return 42;
+        }
+
+        public double evaluate(double[] values) {
+            return 42;
+        }
+        public UnivariateStatistic copy() {
+            return new deepMean();
+        }
+    }
+
+    /**
+     * Test percentile implementation - wraps a Percentile
+     */
+    static class goodPercentile implements UnivariateStatistic {
+        private Percentile percentile = new Percentile();
+        public void setQuantile(double quantile) {
+            percentile.setQuantile(quantile);
+        }
+        public double evaluate(double[] values, int begin, int length) {
+            return percentile.evaluate(values, begin, length);
+        }
+        public double evaluate(double[] values) {
+            return percentile.evaluate(values);
+        }
+        public UnivariateStatistic copy() {
+            goodPercentile result = new goodPercentile();
+            result.setQuantile(percentile.getQuantile());
+            return result;
+        }
+    }
+
+    /**
+     * Test percentile subclass - another "new math" impl
+     * Always returns currently set quantile
+     */
+    static class subPercentile extends Percentile {
+        @Override
+        public double evaluate(double[] values, int begin, int length) {
+            return getQuantile();
+        }
+        @Override
+        public double evaluate(double[] values) {
+            return getQuantile();
+        }
+        private static final long serialVersionUID = 8040701391045914979L;
+        @Override
+        public Percentile copy() {
+            subPercentile result = new subPercentile();
+            return result;
+        }
+    }
+
+    /**
+     * "Bad" test percentile implementation - no setQuantile
+     */
+    static class badPercentile implements UnivariateStatistic {
+        private Percentile percentile = new Percentile();
+        public double evaluate(double[] values, int begin, int length) {
+            return percentile.evaluate(values, begin, length);
+        }
+        public double evaluate(double[] values) {
+            return percentile.evaluate(values);
+        }
+        public UnivariateStatistic copy() {
+            return new badPercentile();
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/stat/descriptive/InteractionTest.java b/src/test/java/org/apache/commons/math/stat/descriptive/InteractionTest.java
new file mode 100644
index 0000000..6d51997
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/descriptive/InteractionTest.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.stat.descriptive;
+
+import org.apache.commons.math.stat.descriptive.moment.FourthMoment;
+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 junit.framework.TestCase;
+
+/**
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+public class InteractionTest extends TestCase {
+
+    protected double mean = 12.40454545454550;
+    protected double var = 10.00235930735930;
+    protected double skew = 1.437423729196190;
+    protected double kurt = 2.377191264804700;
+
+    protected double tolerance = 10E-12;
+
+    protected double[] testArray =
+        {
+            12.5,
+            12,
+            11.8,
+            14.2,
+            14.9,
+            14.5,
+            21,
+            8.2,
+            10.3,
+            11.3,
+            14.1,
+            9.9,
+            12.2,
+            12,
+            12.1,
+            11,
+            19.8,
+            11,
+            10,
+            8.8,
+            9,
+            12.3 };
+
+    public InteractionTest(String name) {
+        super(name);
+    }
+
+
+    public void testInteraction() {
+
+        FourthMoment m4 = new FourthMoment();
+        Mean m = new Mean(m4);
+        Variance v = new Variance(m4);
+        Skewness s= new Skewness(m4);
+        Kurtosis k = new Kurtosis(m4);
+
+        for (int i = 0; i < testArray.length; i++){
+            m4.increment(testArray[i]);
+        }
+
+        assertEquals(mean,m.getResult(),tolerance);
+        assertEquals(var,v.getResult(),tolerance);
+        assertEquals(skew ,s.getResult(),tolerance);
+        assertEquals(kurt,k.getResult(),tolerance);
+
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/stat/descriptive/ListUnivariateImpl.java b/src/test/java/org/apache/commons/math/stat/descriptive/ListUnivariateImpl.java
new file mode 100644
index 0000000..a74de61
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/descriptive/ListUnivariateImpl.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;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.stat.descriptive.UnivariateStatistic;
+import org.apache.commons.math.stat.descriptive.DescriptiveStatistics;
+import org.apache.commons.math.util.DefaultTransformer;
+import org.apache.commons.math.util.FastMath;
+import org.apache.commons.math.util.NumberTransformer;
+
+/**
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ */
+public class ListUnivariateImpl extends DescriptiveStatistics implements Serializable {
+
+    /** Serializable version identifier */
+    private static final long serialVersionUID = -8837442489133392138L;
+
+    /**
+     * Holds a reference to a list - GENERICs are going to make
+     * our lives easier here as we could only accept List<Number>
+     */
+    protected List<Object> list;
+
+    /** Number Transformer maps Objects to Number for us. */
+    protected NumberTransformer transformer;
+
+    /**
+     * No argument Constructor
+     */
+    public ListUnivariateImpl(){
+        this(new ArrayList<Object>());
+    }
+
+    /**
+     * Construct a ListUnivariate with a specific List.
+     * @param list The list that will back this DescriptiveStatistics
+     */
+    public ListUnivariateImpl(List<Object> list) {
+        this(list, new DefaultTransformer());
+    }
+
+    /**
+     * Construct a ListUnivariate with a specific List.
+     * @param list The list that will back this DescriptiveStatistics
+     * @param transformer the number transformer used to convert the list items.
+     */
+    public ListUnivariateImpl(List<Object> list, NumberTransformer transformer) {
+        super();
+        this.list = list;
+        this.transformer = transformer;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double[] getValues() {
+
+        int length = list.size();
+
+        // If the window size is not INFINITE_WINDOW AND
+        // the current list is larger that the window size, we need to
+        // take into account only the last n elements of the list
+        // as definied by windowSize
+
+        if (windowSize != DescriptiveStatistics.INFINITE_WINDOW &&
+            windowSize < list.size())
+        {
+            length = list.size() - FastMath.max(0, list.size() - windowSize);
+        }
+
+        // Create an array to hold all values
+        double[] copiedArray = new double[length];
+
+        for (int i = 0; i < copiedArray.length; i++) {
+            copiedArray[i] = getElement(i);
+        }
+        return copiedArray;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public double getElement(int index) {
+
+        double value = Double.NaN;
+
+        int calcIndex = index;
+
+        if (windowSize != DescriptiveStatistics.INFINITE_WINDOW &&
+            windowSize < list.size())
+        {
+            calcIndex = (list.size() - windowSize) + index;
+        }
+
+
+        try {
+            value = transformer.transform(list.get(calcIndex));
+        } catch (MathException e) {
+            e.printStackTrace();
+        }
+
+        return value;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public long getN() {
+        int n = 0;
+
+        if (windowSize != DescriptiveStatistics.INFINITE_WINDOW) {
+            if (list.size() > windowSize) {
+                n = windowSize;
+            } else {
+                n = list.size();
+            }
+        } else {
+            n = list.size();
+        }
+        return n;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public void addValue(double v) {
+        list.add(Double.valueOf(v));
+    }
+
+    /**
+     * Adds an object to this list.
+     * @param o Object to add to the list
+     */
+    public void addObject(Object o) {
+        list.add(o);
+    }
+
+    /**
+     * Clears all statistics.
+     * <p>
+     * <strong>N.B.: </strong> This method has the side effect of clearing the underlying list.
+     */
+    @Override
+    public void clear() {
+        list.clear();
+    }
+
+    /**
+     * Apply the given statistic to this univariate collection.
+     * @param stat the statistic to apply
+     * @return the computed value of the statistic.
+     */
+    @Override
+    public double apply(UnivariateStatistic stat) {
+        double[] v = this.getValues();
+
+        if (v != null) {
+            return stat.evaluate(v, 0, v.length);
+        }
+        return Double.NaN;
+    }
+
+    /**
+     * Access the number transformer.
+     * @return the number transformer.
+     */
+    public NumberTransformer getTransformer() {
+        return transformer;
+    }
+
+    /**
+     * Modify the number transformer.
+     * @param transformer the new number transformer.
+     */
+    public void setTransformer(NumberTransformer transformer) {
+        this.transformer = transformer;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public synchronized void setWindowSize(int windowSize) {
+        this.windowSize = windowSize;
+        //Discard elements from the front of the list if the windowSize is less than
+        // the size of the list.
+        int extra = list.size() - windowSize;
+        for (int i = 0; i < extra; i++) {
+            list.remove(0);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public synchronized int getWindowSize() {
+        return windowSize;
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/stat/descriptive/ListUnivariateImplTest.java b/src/test/java/org/apache/commons/math/stat/descriptive/ListUnivariateImplTest.java
new file mode 100644
index 0000000..17f041b
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/descriptive/ListUnivariateImplTest.java
@@ -0,0 +1,157 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.stat.descriptive;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Test cases for the {@link ListUnivariateImpl} class.
+ *
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ */
+
+public final class ListUnivariateImplTest extends TestCase {
+
+    private double one = 1;
+    private float two = 2;
+    private int three = 3;
+
+    private double mean = 2;
+    private double sumSq = 18;
+    private double sum = 8;
+    private double var = 0.666666666666666666667;
+    private double std = FastMath.sqrt(var);
+    private double n = 4;
+    private double min = 1;
+    private double max = 3;
+    private double tolerance = 10E-15;
+
+    public ListUnivariateImplTest(String name) {
+        super(name);
+    }
+
+    /** test stats */
+    public void testStats() {
+        List<Object> externalList = new ArrayList<Object>();
+
+        DescriptiveStatistics u = new ListUnivariateImpl( externalList );
+
+        assertEquals("total count",0,u.getN(),tolerance);
+        u.addValue(one);
+        u.addValue(two);
+        u.addValue(two);
+        u.addValue(three);
+        assertEquals("N",n,u.getN(),tolerance);
+        assertEquals("sum",sum,u.getSum(),tolerance);
+        assertEquals("sumsq",sumSq,u.getSumsq(),tolerance);
+        assertEquals("var",var,u.getVariance(),tolerance);
+        assertEquals("std",std,u.getStandardDeviation(),tolerance);
+        assertEquals("mean",mean,u.getMean(),tolerance);
+        assertEquals("min",min,u.getMin(),tolerance);
+        assertEquals("max",max,u.getMax(),tolerance);
+        u.clear();
+        assertEquals("total count",0,u.getN(),tolerance);
+    }
+
+    public void testN0andN1Conditions() throws Exception {
+        List<Object> list = new ArrayList<Object>();
+
+        DescriptiveStatistics u = new ListUnivariateImpl( list );
+
+        assertTrue("Mean of n = 0 set should be NaN", Double.isNaN( u.getMean() ) );
+        assertTrue("Standard Deviation of n = 0 set should be NaN", Double.isNaN( u.getStandardDeviation() ) );
+        assertTrue("Variance of n = 0 set should be NaN", Double.isNaN(u.getVariance() ) );
+
+        list.add( Double.valueOf(one));
+
+        assertTrue( "Mean of n = 1 set should be value of single item n1", u.getMean() == one);
+        assertTrue( "StdDev of n = 1 set should be zero, instead it is: " + u.getStandardDeviation(), u.getStandardDeviation() == 0);
+        assertTrue( "Variance of n = 1 set should be zero", u.getVariance() == 0);
+    }
+
+    public void testSkewAndKurtosis() {
+        DescriptiveStatistics u = new DescriptiveStatistics();
+
+        double[] testArray = { 12.5, 12, 11.8, 14.2, 14.9, 14.5, 21, 8.2, 10.3, 11.3, 14.1,
+                                             9.9, 12.2, 12, 12.1, 11, 19.8, 11, 10, 8.8, 9, 12.3 };
+        for( int i = 0; i < testArray.length; i++) {
+            u.addValue( testArray[i]);
+        }
+
+        assertEquals("mean", 12.40455, u.getMean(), 0.0001);
+        assertEquals("variance", 10.00236, u.getVariance(), 0.0001);
+        assertEquals("skewness", 1.437424, u.getSkewness(), 0.0001);
+        assertEquals("kurtosis", 2.37719, u.getKurtosis(), 0.0001);
+    }
+
+    public void testProductAndGeometricMean() throws Exception {
+        ListUnivariateImpl u = new ListUnivariateImpl(new ArrayList<Object>());
+        u.setWindowSize(10);
+
+        u.addValue( 1.0 );
+        u.addValue( 2.0 );
+        u.addValue( 3.0 );
+        u.addValue( 4.0 );
+
+        assertEquals( "Geometric mean not expected", 2.213364, u.getGeometricMean(), 0.00001 );
+
+        // Now test rolling - StorelessDescriptiveStatistics should discount the contribution
+        // of a discarded element
+        for( int i = 0; i < 10; i++ ) {
+            u.addValue( i + 2 );
+        }
+        // Values should be (2,3,4,5,6,7,8,9,10,11)
+
+        assertEquals( "Geometric mean not expected", 5.755931, u.getGeometricMean(), 0.00001 );
+
+
+    }
+
+    /** test stats */
+    public void testSerialization() {
+
+        DescriptiveStatistics u = new ListUnivariateImpl();
+
+        assertEquals("total count",0,u.getN(),tolerance);
+        u.addValue(one);
+        u.addValue(two);
+
+        DescriptiveStatistics u2 = (DescriptiveStatistics)TestUtils.serializeAndRecover(u);
+
+        u2.addValue(two);
+        u2.addValue(three);
+
+        assertEquals("N",n,u2.getN(),tolerance);
+        assertEquals("sum",sum,u2.getSum(),tolerance);
+        assertEquals("sumsq",sumSq,u2.getSumsq(),tolerance);
+        assertEquals("var",var,u2.getVariance(),tolerance);
+        assertEquals("std",std,u2.getStandardDeviation(),tolerance);
+        assertEquals("mean",mean,u2.getMean(),tolerance);
+        assertEquals("min",min,u2.getMin(),tolerance);
+        assertEquals("max",max,u2.getMax(),tolerance);
+
+        u2.clear();
+        assertEquals("total count",0,u2.getN(),tolerance);
+    }
+}
+
diff --git a/src/test/java/org/apache/commons/math/stat/descriptive/MixedListUnivariateImplTest.java b/src/test/java/org/apache/commons/math/stat/descriptive/MixedListUnivariateImplTest.java
new file mode 100644
index 0000000..6d24945
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/descriptive/MixedListUnivariateImplTest.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.stat.descriptive;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.math.util.FastMath;
+import org.apache.commons.math.util.NumberTransformer;
+import org.apache.commons.math.util.TransformerMap;
+
+/**
+ * Test cases for the {@link ListUnivariateImpl} class.
+ *
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ */
+
+public final class MixedListUnivariateImplTest extends TestCase {
+    private double one = 1;
+    private float two = 2;
+    private int three = 3;
+
+    private double mean = 2;
+    private double sumSq = 18;
+    private double sum = 8;
+    private double var = 0.666666666666666666667;
+    private double std = FastMath.sqrt(var);
+    private double n = 4;
+    private double min = 1;
+    private double max = 3;
+    private double tolerance = 10E-15;
+
+    private TransformerMap transformers = new TransformerMap();
+
+    public MixedListUnivariateImplTest(String name) {
+        super(name);
+        transformers = new TransformerMap();
+
+        transformers.putTransformer(Foo.class, new FooTransformer());
+
+        transformers.putTransformer(Bar.class, new BarTransformer());
+
+    }
+
+    /** test stats */
+    public void testStats() {
+        List<Object> externalList = new ArrayList<Object>();
+
+        DescriptiveStatistics u = new ListUnivariateImpl(externalList,transformers);
+
+        assertEquals("total count", 0, u.getN(), tolerance);
+        u.addValue(one);
+        u.addValue(two);
+        u.addValue(two);
+        u.addValue(three);
+        assertEquals("N", n, u.getN(), tolerance);
+        assertEquals("sum", sum, u.getSum(), tolerance);
+        assertEquals("sumsq", sumSq, u.getSumsq(), tolerance);
+        assertEquals("var", var, u.getVariance(), tolerance);
+        assertEquals("std", std, u.getStandardDeviation(), tolerance);
+        assertEquals("mean", mean, u.getMean(), tolerance);
+        assertEquals("min", min, u.getMin(), tolerance);
+        assertEquals("max", max, u.getMax(), tolerance);
+        u.clear();
+        assertEquals("total count", 0, u.getN(), tolerance);
+    }
+
+    public void testN0andN1Conditions() throws Exception {
+        DescriptiveStatistics u = new ListUnivariateImpl(new ArrayList<Object>(),transformers);
+
+        assertTrue(
+            "Mean of n = 0 set should be NaN",
+            Double.isNaN(u.getMean()));
+        assertTrue(
+            "Standard Deviation of n = 0 set should be NaN",
+            Double.isNaN(u.getStandardDeviation()));
+        assertTrue(
+            "Variance of n = 0 set should be NaN",
+            Double.isNaN(u.getVariance()));
+
+        u.addValue(one);
+
+        assertTrue(
+            "Mean of n = 1 set should be value of single item n1, instead it is " + u.getMean() ,
+            u.getMean() == one);
+
+        assertTrue(
+            "StdDev of n = 1 set should be zero, instead it is: "
+                + u.getStandardDeviation(),
+            u.getStandardDeviation() == 0);
+        assertTrue(
+            "Variance of n = 1 set should be zero",
+            u.getVariance() == 0);
+    }
+
+    public void testSkewAndKurtosis() {
+        ListUnivariateImpl u =
+            new ListUnivariateImpl(new ArrayList<Object>(), transformers);
+
+        u.addObject("12.5");
+        u.addObject(Integer.valueOf(12));
+        u.addObject("11.8");
+        u.addObject("14.2");
+        u.addObject(new Foo());
+        u.addObject("14.5");
+        u.addObject(Long.valueOf(21));
+        u.addObject("8.2");
+        u.addObject("10.3");
+        u.addObject("11.3");
+        u.addObject(Float.valueOf(14.1f));
+        u.addObject("9.9");
+        u.addObject("12.2");
+        u.addObject(new Bar());
+        u.addObject("12.1");
+        u.addObject("11");
+        u.addObject(Double.valueOf(19.8));
+        u.addObject("11");
+        u.addObject("10");
+        u.addObject("8.8");
+        u.addObject("9");
+        u.addObject("12.3");
+
+
+        assertEquals("mean", 12.40455, u.getMean(), 0.0001);
+        assertEquals("variance", 10.00236, u.getVariance(), 0.0001);
+        assertEquals("skewness", 1.437424, u.getSkewness(), 0.0001);
+        assertEquals("kurtosis", 2.37719, u.getKurtosis(), 0.0001);
+    }
+
+    public void testProductAndGeometricMean() throws Exception {
+        ListUnivariateImpl u = new ListUnivariateImpl(new ArrayList<Object>(),transformers);
+        u.setWindowSize(10);
+
+        u.addValue(1.0);
+        u.addValue(2.0);
+        u.addValue(3.0);
+        u.addValue(4.0);
+
+        assertEquals(
+            "Geometric mean not expected",
+            2.213364,
+            u.getGeometricMean(),
+            0.00001);
+
+        // Now test rolling - StorelessDescriptiveStatistics should discount the contribution
+        // of a discarded element
+        for (int i = 0; i < 10; i++) {
+            u.addValue(i + 2);
+        }
+        // Values should be (2,3,4,5,6,7,8,9,10,11)
+        assertEquals(
+            "Geometric mean not expected",
+            5.755931,
+            u.getGeometricMean(),
+            0.00001);
+
+    }
+
+    public static final class Foo {
+        public String heresFoo() {
+            return "14.9";
+        }
+    }
+
+    public static final class FooTransformer implements NumberTransformer, Serializable {
+        private static final long serialVersionUID = -4252248129291326127L;
+        public double transform(Object o) {
+            return Double.parseDouble(((Foo) o).heresFoo());
+        }
+    }
+
+    public static final class Bar {
+        public String heresBar() {
+            return "12.0";
+        }
+    }
+
+    public static final class BarTransformer implements NumberTransformer, Serializable {
+        private static final long serialVersionUID = -1768345377764262043L;
+        public double transform(Object o) {
+            return Double.parseDouble(((Bar) o).heresBar());
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/stat/descriptive/MultivariateSummaryStatisticsTest.java b/src/test/java/org/apache/commons/math/stat/descriptive/MultivariateSummaryStatisticsTest.java
new file mode 100644
index 0000000..bac72c3
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/descriptive/MultivariateSummaryStatisticsTest.java
@@ -0,0 +1,309 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.util.Locale;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.math.DimensionMismatchException;
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.stat.descriptive.moment.Mean;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Test cases for the {@link MultivariateSummaryStatistics} class.
+ *
+ * @version $Revision: 1003907 $ $Date: 2010-10-03 00:23:34 +0200 (dim. 03 oct. 2010) $
+ */
+
+public class MultivariateSummaryStatisticsTest extends TestCase {
+
+    public MultivariateSummaryStatisticsTest(String name) {
+        super(name);
+    }
+
+    protected MultivariateSummaryStatistics createMultivariateSummaryStatistics(int k, boolean isCovarianceBiasCorrected) {
+        return new MultivariateSummaryStatistics(k, isCovarianceBiasCorrected);
+    }
+
+    public void testSetterInjection() throws Exception {
+        MultivariateSummaryStatistics u = createMultivariateSummaryStatistics(2, true);
+        u.setMeanImpl(new StorelessUnivariateStatistic[] {
+                        new sumMean(), new sumMean()
+                      });
+        u.addValue(new double[] { 1, 2 });
+        u.addValue(new double[] { 3, 4 });
+        assertEquals(4, u.getMean()[0], 1E-14);
+        assertEquals(6, u.getMean()[1], 1E-14);
+        u.clear();
+        u.addValue(new double[] { 1, 2 });
+        u.addValue(new double[] { 3, 4 });
+        assertEquals(4, u.getMean()[0], 1E-14);
+        assertEquals(6, u.getMean()[1], 1E-14);
+        u.clear();
+        u.setMeanImpl(new StorelessUnivariateStatistic[] {
+                        new Mean(), new Mean()
+                      }); // OK after clear
+        u.addValue(new double[] { 1, 2 });
+        u.addValue(new double[] { 3, 4 });
+        assertEquals(2, u.getMean()[0], 1E-14);
+        assertEquals(3, u.getMean()[1], 1E-14);
+        assertEquals(2, u.getDimension());
+    }
+
+    public void testSetterIllegalState() throws Exception {
+        MultivariateSummaryStatistics u = createMultivariateSummaryStatistics(2, true);
+        u.addValue(new double[] { 1, 2 });
+        u.addValue(new double[] { 3, 4 });
+        try {
+            u.setMeanImpl(new StorelessUnivariateStatistic[] {
+                            new sumMean(), new sumMean()
+                          });
+            fail("Expecting IllegalStateException");
+        } catch (IllegalStateException ex) {
+            // expected
+        }
+    }
+
+    public void testToString() throws DimensionMismatchException {
+        MultivariateSummaryStatistics stats = createMultivariateSummaryStatistics(2, true);
+        stats.addValue(new double[] {1, 3});
+        stats.addValue(new double[] {2, 2});
+        stats.addValue(new double[] {3, 1});
+        Locale d = Locale.getDefault();
+        Locale.setDefault(Locale.US);
+        final String suffix = System.getProperty("line.separator");
+        assertEquals("MultivariateSummaryStatistics:" + suffix+
+                     "n: 3" +suffix+
+                     "min: 1.0, 1.0" +suffix+
+                     "max: 3.0, 3.0" +suffix+
+                     "mean: 2.0, 2.0" +suffix+
+                     "geometric mean: 1.817..., 1.817..." +suffix+
+                     "sum of squares: 14.0, 14.0" +suffix+
+                     "sum of logarithms: 1.791..., 1.791..." +suffix+
+                     "standard deviation: 1.0, 1.0" +suffix+
+                     "covariance: Array2DRowRealMatrix{{1.0,-1.0},{-1.0,1.0}}" +suffix,
+                     stats.toString().replaceAll("([0-9]+\\.[0-9][0-9][0-9])[0-9]+", "$1..."));
+        Locale.setDefault(d);
+    }
+
+    public void testShuffledStatistics() throws DimensionMismatchException {
+        // the purpose of this test is only to check the get/set methods
+        // we are aware shuffling statistics like this is really not
+        // something sensible to do in production ...
+        MultivariateSummaryStatistics reference = createMultivariateSummaryStatistics(2, true);
+        MultivariateSummaryStatistics shuffled  = createMultivariateSummaryStatistics(2, true);
+
+        StorelessUnivariateStatistic[] tmp = shuffled.getGeoMeanImpl();
+        shuffled.setGeoMeanImpl(shuffled.getMeanImpl());
+        shuffled.setMeanImpl(shuffled.getMaxImpl());
+        shuffled.setMaxImpl(shuffled.getMinImpl());
+        shuffled.setMinImpl(shuffled.getSumImpl());
+        shuffled.setSumImpl(shuffled.getSumsqImpl());
+        shuffled.setSumsqImpl(shuffled.getSumLogImpl());
+        shuffled.setSumLogImpl(tmp);
+
+        for (int i = 100; i > 0; --i) {
+            reference.addValue(new double[] {i, i});
+            shuffled.addValue(new double[] {i, i});
+        }
+
+        TestUtils.assertEquals(reference.getMean(),          shuffled.getGeometricMean(), 1.0e-10);
+        TestUtils.assertEquals(reference.getMax(),           shuffled.getMean(),          1.0e-10);
+        TestUtils.assertEquals(reference.getMin(),           shuffled.getMax(),           1.0e-10);
+        TestUtils.assertEquals(reference.getSum(),           shuffled.getMin(),           1.0e-10);
+        TestUtils.assertEquals(reference.getSumSq(),         shuffled.getSum(),           1.0e-10);
+        TestUtils.assertEquals(reference.getSumLog(),        shuffled.getSumSq(),         1.0e-10);
+        TestUtils.assertEquals(reference.getGeometricMean(), shuffled.getSumLog(),        1.0e-10);
+
+    }
+
+    /**
+     * Bogus mean implementation to test setter injection.
+     * Returns the sum instead of the mean.
+     */
+    static class sumMean implements StorelessUnivariateStatistic {
+        private double sum = 0;
+        private long n = 0;
+        public double evaluate(double[] values, int begin, int length) {
+            return 0;
+        }
+        public double evaluate(double[] values) {
+            return 0;
+        }
+        public void clear() {
+          sum = 0;
+          n = 0;
+        }
+        public long getN() {
+            return n;
+        }
+        public double getResult() {
+            return sum;
+        }
+        public void increment(double d) {
+            sum += d;
+            n++;
+        }
+        public void incrementAll(double[] values, int start, int length) {
+        }
+        public void incrementAll(double[] values) {
+        }
+        public StorelessUnivariateStatistic copy() {
+            return new sumMean();
+        }
+    }
+
+    public void testDimension() {
+        try {
+            createMultivariateSummaryStatistics(2, true).addValue(new double[3]);
+            fail("Expecting DimensionMismatchException");
+        } catch (DimensionMismatchException dme) {
+            // expected behavior
+        }
+    }
+
+    /** test stats */
+    public void testStats() throws DimensionMismatchException {
+        MultivariateSummaryStatistics u = createMultivariateSummaryStatistics(2, true);
+        assertEquals(0, u.getN());
+        u.addValue(new double[] { 1, 2 });
+        u.addValue(new double[] { 2, 3 });
+        u.addValue(new double[] { 2, 3 });
+        u.addValue(new double[] { 3, 4 });
+        assertEquals( 4, u.getN());
+        assertEquals( 8, u.getSum()[0], 1.0e-10);
+        assertEquals(12, u.getSum()[1], 1.0e-10);
+        assertEquals(18, u.getSumSq()[0], 1.0e-10);
+        assertEquals(38, u.getSumSq()[1], 1.0e-10);
+        assertEquals( 1, u.getMin()[0], 1.0e-10);
+        assertEquals( 2, u.getMin()[1], 1.0e-10);
+        assertEquals( 3, u.getMax()[0], 1.0e-10);
+        assertEquals( 4, u.getMax()[1], 1.0e-10);
+        assertEquals(2.4849066497880003102, u.getSumLog()[0], 1.0e-10);
+        assertEquals( 4.276666119016055311, u.getSumLog()[1], 1.0e-10);
+        assertEquals( 1.8612097182041991979, u.getGeometricMean()[0], 1.0e-10);
+        assertEquals( 2.9129506302439405217, u.getGeometricMean()[1], 1.0e-10);
+        assertEquals( 2, u.getMean()[0], 1.0e-10);
+        assertEquals( 3, u.getMean()[1], 1.0e-10);
+        assertEquals(FastMath.sqrt(2.0 / 3.0), u.getStandardDeviation()[0], 1.0e-10);
+        assertEquals(FastMath.sqrt(2.0 / 3.0), u.getStandardDeviation()[1], 1.0e-10);
+        assertEquals(2.0 / 3.0, u.getCovariance().getEntry(0, 0), 1.0e-10);
+        assertEquals(2.0 / 3.0, u.getCovariance().getEntry(0, 1), 1.0e-10);
+        assertEquals(2.0 / 3.0, u.getCovariance().getEntry(1, 0), 1.0e-10);
+        assertEquals(2.0 / 3.0, u.getCovariance().getEntry(1, 1), 1.0e-10);
+        u.clear();
+        assertEquals(0, u.getN());
+    }
+
+    public void testN0andN1Conditions() throws Exception {
+        MultivariateSummaryStatistics u = createMultivariateSummaryStatistics(1, true);
+        assertTrue(Double.isNaN(u.getMean()[0]));
+        assertTrue(Double.isNaN(u.getStandardDeviation()[0]));
+
+        /* n=1 */
+        u.addValue(new double[] { 1 });
+        assertEquals(1.0, u.getMean()[0], 1.0e-10);
+        assertEquals(1.0, u.getGeometricMean()[0], 1.0e-10);
+        assertEquals(0.0, u.getStandardDeviation()[0], 1.0e-10);
+
+        /* n=2 */
+        u.addValue(new double[] { 2 });
+        assertTrue(u.getStandardDeviation()[0] > 0);
+
+    }
+
+    public void testNaNContracts() throws DimensionMismatchException {
+        MultivariateSummaryStatistics u = createMultivariateSummaryStatistics(1, true);
+        assertTrue(Double.isNaN(u.getMean()[0]));
+        assertTrue(Double.isNaN(u.getMin()[0]));
+        assertTrue(Double.isNaN(u.getStandardDeviation()[0]));
+        assertTrue(Double.isNaN(u.getGeometricMean()[0]));
+
+        u.addValue(new double[] { 1.0 });
+        assertFalse(Double.isNaN(u.getMean()[0]));
+        assertFalse(Double.isNaN(u.getMin()[0]));
+        assertFalse(Double.isNaN(u.getStandardDeviation()[0]));
+        assertFalse(Double.isNaN(u.getGeometricMean()[0]));
+
+    }
+
+    public void testSerialization() throws DimensionMismatchException {
+        MultivariateSummaryStatistics u = createMultivariateSummaryStatistics(2, true);
+        // Empty test
+        TestUtils.checkSerializedEquality(u);
+        MultivariateSummaryStatistics s = (MultivariateSummaryStatistics) TestUtils.serializeAndRecover(u);
+        assertEquals(u, s);
+
+        // Add some data
+        u.addValue(new double[] { 2d, 1d });
+        u.addValue(new double[] { 1d, 1d });
+        u.addValue(new double[] { 3d, 1d });
+        u.addValue(new double[] { 4d, 1d });
+        u.addValue(new double[] { 5d, 1d });
+
+        // Test again
+        TestUtils.checkSerializedEquality(u);
+        s = (MultivariateSummaryStatistics) TestUtils.serializeAndRecover(u);
+        assertEquals(u, s);
+
+    }
+
+    public void testEqualsAndHashCode() throws DimensionMismatchException {
+        MultivariateSummaryStatistics u = createMultivariateSummaryStatistics(2, true);
+        MultivariateSummaryStatistics t = null;
+        int emptyHash = u.hashCode();
+        assertTrue(u.equals(u));
+        assertFalse(u.equals(t));
+        assertFalse(u.equals(Double.valueOf(0)));
+        t = createMultivariateSummaryStatistics(2, true);
+        assertTrue(t.equals(u));
+        assertTrue(u.equals(t));
+        assertEquals(emptyHash, t.hashCode());
+
+        // Add some data to u
+        u.addValue(new double[] { 2d, 1d });
+        u.addValue(new double[] { 1d, 1d });
+        u.addValue(new double[] { 3d, 1d });
+        u.addValue(new double[] { 4d, 1d });
+        u.addValue(new double[] { 5d, 1d });
+        assertFalse(t.equals(u));
+        assertFalse(u.equals(t));
+        assertTrue(u.hashCode() != t.hashCode());
+
+        //Add data in same order to t
+        t.addValue(new double[] { 2d, 1d });
+        t.addValue(new double[] { 1d, 1d });
+        t.addValue(new double[] { 3d, 1d });
+        t.addValue(new double[] { 4d, 1d });
+        t.addValue(new double[] { 5d, 1d });
+        assertTrue(t.equals(u));
+        assertTrue(u.equals(t));
+        assertEquals(u.hashCode(), t.hashCode());
+
+        // Clear and make sure summaries are indistinguishable from empty summary
+        u.clear();
+        t.clear();
+        assertTrue(t.equals(u));
+        assertTrue(u.equals(t));
+        assertEquals(emptyHash, t.hashCode());
+        assertEquals(emptyHash, u.hashCode());
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/stat/descriptive/StatisticalSummaryValuesTest.java b/src/test/java/org/apache/commons/math/stat/descriptive/StatisticalSummaryValuesTest.java
new file mode 100644
index 0000000..33ab5ed
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/descriptive/StatisticalSummaryValuesTest.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.util.Locale;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.math.TestUtils;
+/**
+ * Test cases for the {@link StatisticalSummaryValues} class.
+ *
+ * @version $Revision: 1038873 $ $Date: 2010-11-25 00:35:13 +0100 (jeu. 25 nov. 2010) $
+ */
+
+public final class StatisticalSummaryValuesTest extends TestCase {
+
+
+    public StatisticalSummaryValuesTest(String name) {
+        super(name);
+    }
+
+    public void testSerialization() {
+        StatisticalSummaryValues u = new StatisticalSummaryValues(1, 2, 3, 4, 5, 6);
+        TestUtils.checkSerializedEquality(u);
+        StatisticalSummaryValues t = (StatisticalSummaryValues) TestUtils.serializeAndRecover(u);
+        verifyEquality(u, t);
+    }
+
+    public void testEqualsAndHashCode() {
+        StatisticalSummaryValues u  = new StatisticalSummaryValues(1, 2, 3, 4, 5, 6);
+        StatisticalSummaryValues t = null;
+        assertTrue("reflexive", u.equals(u));
+        assertFalse("non-null compared to null", u.equals(t));
+        assertFalse("wrong type", u.equals(Double.valueOf(0)));
+        t = new StatisticalSummaryValues(1, 2, 3, 4, 5, 6);
+        assertTrue("instances with same data should be equal", t.equals(u));
+        assertEquals("hash code", u.hashCode(), t.hashCode());
+
+        u = new StatisticalSummaryValues(Double.NaN, 2, 3, 4, 5, 6);
+        t = new StatisticalSummaryValues(1, Double.NaN, 3, 4, 5, 6);
+        assertFalse("instances based on different data should be different",
+                (u.equals(t) ||t.equals(u)));
+    }
+
+    private void verifyEquality(StatisticalSummaryValues s, StatisticalSummaryValues u) {
+        assertEquals("N",s.getN(),u.getN());
+        TestUtils.assertEquals("sum",s.getSum(),u.getSum(), 0);
+        TestUtils.assertEquals("var",s.getVariance(),u.getVariance(), 0);
+        TestUtils.assertEquals("std",s.getStandardDeviation(),u.getStandardDeviation(), 0);
+        TestUtils.assertEquals("mean",s.getMean(),u.getMean(), 0);
+        TestUtils.assertEquals("min",s.getMin(),u.getMin(), 0);
+        TestUtils.assertEquals("max",s.getMax(),u.getMax(), 0);
+    }
+    
+    public void testToString() {
+        StatisticalSummaryValues u  = new StatisticalSummaryValues(4.5, 16, 10, 5, 4, 45);
+        Locale d = Locale.getDefault();
+        Locale.setDefault(Locale.US);
+        assertEquals("StatisticalSummaryValues:\n" +
+                     "n: 10\n" +
+                     "min: 4.0\n" +
+                     "max: 5.0\n" +
+                     "mean: 4.5\n" +
+                     "std dev: 4.0\n" +
+                     "variance: 16.0\n" +
+                     "sum: 45.0\n",  u.toString());
+        Locale.setDefault(d);
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/stat/descriptive/StorelessUnivariateStatisticAbstractTest.java b/src/test/java/org/apache/commons/math/stat/descriptive/StorelessUnivariateStatisticAbstractTest.java
new file mode 100644
index 0000000..6e97b3f
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/descriptive/StorelessUnivariateStatisticAbstractTest.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.stat.descriptive;
+
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.stat.descriptive.moment.SecondMoment;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Test cases for {@link StorelessUnivariateStatistic} classes.
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ */
+public abstract class StorelessUnivariateStatisticAbstractTest
+    extends UnivariateStatisticAbstractTest {
+
+    public StorelessUnivariateStatisticAbstractTest(String name) {
+        super(name);
+    }
+
+    /** Small sample arrays */
+    protected double[][] smallSamples = {{}, {1}, {1,2}, {1,2,3}, {1,2,3,4}};
+
+    /** Return a new instance of the statistic */
+    @Override
+    public abstract UnivariateStatistic getUnivariateStatistic();
+
+    /**Expected value for  the testArray defined in UnivariateStatisticAbstractTest */
+    @Override
+    public abstract double expectedValue();
+
+    /**
+     *  Verifies that increment() and incrementAll work properly.
+     */
+    public void testIncrementation() throws Exception {
+
+        StorelessUnivariateStatistic statistic =
+            (StorelessUnivariateStatistic) getUnivariateStatistic();
+
+        // Add testArray one value at a time and check result
+        for (int i = 0; i < testArray.length; i++) {
+            statistic.increment(testArray[i]);
+        }
+
+        assertEquals(expectedValue(), statistic.getResult(), getTolerance());
+        assertEquals(testArray.length, statistic.getN());
+
+        statistic.clear();
+
+        // Add testArray all at once and check again
+        statistic.incrementAll(testArray);
+        assertEquals(expectedValue(), statistic.getResult(), getTolerance());
+        assertEquals(testArray.length, statistic.getN());
+
+        statistic.clear();
+
+        // Cleared
+        assertTrue(Double.isNaN(statistic.getResult()));
+        assertEquals(0, statistic.getN());
+
+    }
+
+    public void testSerialization() throws Exception {
+
+        StorelessUnivariateStatistic statistic =
+            (StorelessUnivariateStatistic) getUnivariateStatistic();
+
+        TestUtils.checkSerializedEquality(statistic);
+
+        statistic.clear();
+
+        for (int i = 0; i < testArray.length; i++) {
+            statistic.increment(testArray[i]);
+            if(i % 5 == 0)
+                statistic = (StorelessUnivariateStatistic)TestUtils.serializeAndRecover(statistic);
+        }
+
+        TestUtils.checkSerializedEquality(statistic);
+
+        assertEquals(expectedValue(), statistic.getResult(), getTolerance());
+
+        statistic.clear();
+
+        assertTrue(Double.isNaN(statistic.getResult()));
+
+    }
+
+    public void testEqualsAndHashCode() {
+        StorelessUnivariateStatistic statistic =
+            (StorelessUnivariateStatistic) getUnivariateStatistic();
+        StorelessUnivariateStatistic statistic2 = null;
+
+        assertTrue("non-null, compared to null", !statistic.equals(statistic2));
+        assertTrue("reflexive, non-null", statistic.equals(statistic));
+
+        int emptyHash = statistic.hashCode();
+        statistic2 = (StorelessUnivariateStatistic) getUnivariateStatistic();
+        assertTrue("empty stats should be equal", statistic.equals(statistic2));
+        assertEquals("empty stats should have the same hashcode",
+                emptyHash, statistic2.hashCode());
+
+        statistic.increment(1d);
+        assertTrue("reflexive, non-empty", statistic.equals(statistic));
+        assertTrue("non-empty, compared to empty", !statistic.equals(statistic2));
+        assertTrue("non-empty, compared to empty", !statistic2.equals(statistic));
+        assertTrue("non-empty stat should have different hashcode from empty stat",
+                statistic.hashCode() != emptyHash);
+
+        statistic2.increment(1d);
+        assertTrue("stats with same data should be equal", statistic.equals(statistic2));
+        assertEquals("stats with same data should have the same hashcode",
+                statistic.hashCode(), statistic2.hashCode());
+
+        statistic.increment(Double.POSITIVE_INFINITY);
+        assertTrue("stats with different n's should not be equal", !statistic2.equals(statistic));
+        assertTrue("stats with different n's should have different hashcodes",
+                statistic.hashCode() != statistic2.hashCode());
+
+        statistic2.increment(Double.POSITIVE_INFINITY);
+        assertTrue("stats with same data should be equal", statistic.equals(statistic2));
+        assertEquals("stats with same data should have the same hashcode",
+                statistic.hashCode(), statistic2.hashCode());
+
+        statistic.clear();
+        statistic2.clear();
+        assertTrue("cleared stats should be equal", statistic.equals(statistic2));
+        assertEquals("cleared stats should have thashcode of empty stat",
+                emptyHash, statistic2.hashCode());
+        assertEquals("cleared stats should have thashcode of empty stat",
+                emptyHash, statistic.hashCode());
+
+    }
+
+    public void testMomentSmallSamples() {
+        UnivariateStatistic stat = getUnivariateStatistic();
+        if (stat instanceof SecondMoment) {
+            SecondMoment moment = (SecondMoment) getUnivariateStatistic();
+            assertTrue(Double.isNaN(moment.getResult()));
+            moment.increment(1d);
+            assertEquals(0d, moment.getResult(), 0);
+        }
+    }
+
+    /**
+     * Make sure that evaluate(double[]) and inrementAll(double[]),
+     * getResult() give same results.
+     */
+    public void testConsistency() {
+        StorelessUnivariateStatistic stat = (StorelessUnivariateStatistic) getUnivariateStatistic();
+        stat.incrementAll(testArray);
+        assertEquals(stat.getResult(), stat.evaluate(testArray), getTolerance());
+        for (int i = 0; i < smallSamples.length; i++) {
+            stat.clear();
+            for (int j =0; j < smallSamples[i].length; j++) {
+                stat.increment(smallSamples[i][j]);
+            }
+            TestUtils.assertEquals(stat.getResult(), stat.evaluate(smallSamples[i]), getTolerance());
+        }
+    }
+
+    /**
+     * Verifies that copied statistics remain equal to originals when
+     * incremented the same way.
+     *
+     */
+    public void testCopyConsistency() {
+
+        StorelessUnivariateStatistic master =
+            (StorelessUnivariateStatistic) getUnivariateStatistic();
+
+        StorelessUnivariateStatistic replica = null;
+
+        // Randomly select a portion of testArray to load first
+        long index = FastMath.round((FastMath.random()) * testArray.length);
+
+        // Put first half in master and copy master to replica
+        master.incrementAll(testArray, 0, (int) index);
+        replica = master.copy();
+
+        // Check same
+        assertTrue(replica.equals(master));
+        assertTrue(master.equals(replica));
+
+        // Now add second part to both and check again
+        master.incrementAll(testArray,
+                (int) index, (int) (testArray.length - index));
+        replica.incrementAll(testArray,
+                (int) index, (int) (testArray.length - index));
+        assertTrue(replica.equals(master));
+        assertTrue(master.equals(replica));
+    }
+
+    public void testSerial() {
+        StorelessUnivariateStatistic s =
+            (StorelessUnivariateStatistic) getUnivariateStatistic();
+        assertEquals(s, TestUtils.serializeAndRecover(s));
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/stat/descriptive/SummaryStatisticsTest.java b/src/test/java/org/apache/commons/math/stat/descriptive/SummaryStatisticsTest.java
new file mode 100644
index 0000000..3dfac2f
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/descriptive/SummaryStatisticsTest.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 junit.framework.TestCase;
+
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.stat.descriptive.moment.Mean;
+import org.apache.commons.math.stat.descriptive.summary.Sum;
+import org.apache.commons.math.util.FastMath;
+/**
+ * Test cases for the {@link SummaryStatistics} class.
+ *
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ */
+
+public class SummaryStatisticsTest extends TestCase {
+
+    private double one = 1;
+    private float twoF = 2;
+    private long twoL = 2;
+    private int three = 3;
+    private double mean = 2;
+    private double sumSq = 18;
+    private double sum = 8;
+    private double var = 0.666666666666666666667;
+    private double std = FastMath.sqrt(var);
+    private double n = 4;
+    private double min = 1;
+    private double max = 3;
+    private double tolerance = 10E-15;
+
+    public SummaryStatisticsTest(String name) {
+        super(name);
+    }
+
+    protected SummaryStatistics createSummaryStatistics() {
+        return new SummaryStatistics();
+    }
+
+    /** test stats */
+    public void testStats() {
+        SummaryStatistics u = createSummaryStatistics();
+        assertEquals("total count",0,u.getN(),tolerance);
+        u.addValue(one);
+        u.addValue(twoF);
+        u.addValue(twoL);
+        u.addValue(three);
+        assertEquals("N",n,u.getN(),tolerance);
+        assertEquals("sum",sum,u.getSum(),tolerance);
+        assertEquals("sumsq",sumSq,u.getSumsq(),tolerance);
+        assertEquals("var",var,u.getVariance(),tolerance);
+        assertEquals("std",std,u.getStandardDeviation(),tolerance);
+        assertEquals("mean",mean,u.getMean(),tolerance);
+        assertEquals("min",min,u.getMin(),tolerance);
+        assertEquals("max",max,u.getMax(),tolerance);
+        u.clear();
+        assertEquals("total count",0,u.getN(),tolerance);
+    }
+
+    public void testN0andN1Conditions() throws Exception {
+        SummaryStatistics u = createSummaryStatistics();
+        assertTrue("Mean of n = 0 set should be NaN",
+                Double.isNaN( u.getMean() ) );
+        assertTrue("Standard Deviation of n = 0 set should be NaN",
+                Double.isNaN( u.getStandardDeviation() ) );
+        assertTrue("Variance of n = 0 set should be NaN",
+                Double.isNaN(u.getVariance() ) );
+
+        /* n=1 */
+        u.addValue(one);
+        assertTrue("mean should be one (n = 1)",
+                u.getMean() == one);
+        assertTrue("geometric should be one (n = 1) instead it is " + u.getGeometricMean(),
+                u.getGeometricMean() == one);
+        assertTrue("Std should be zero (n = 1)",
+                u.getStandardDeviation() == 0.0);
+        assertTrue("variance should be zero (n = 1)",
+                u.getVariance() == 0.0);
+
+        /* n=2 */
+        u.addValue(twoF);
+        assertTrue("Std should not be zero (n = 2)",
+                u.getStandardDeviation() != 0.0);
+        assertTrue("variance should not be zero (n = 2)",
+                u.getVariance() != 0.0);
+
+    }
+
+    public void testProductAndGeometricMean() throws Exception {
+        SummaryStatistics u = createSummaryStatistics();
+        u.addValue( 1.0 );
+        u.addValue( 2.0 );
+        u.addValue( 3.0 );
+        u.addValue( 4.0 );
+
+        assertEquals( "Geometric mean not expected", 2.213364,
+                u.getGeometricMean(), 0.00001 );
+    }
+
+    public void testNaNContracts() {
+        SummaryStatistics u = createSummaryStatistics();
+        assertTrue("mean not NaN",Double.isNaN(u.getMean()));
+        assertTrue("min not NaN",Double.isNaN(u.getMin()));
+        assertTrue("std dev not NaN",Double.isNaN(u.getStandardDeviation()));
+        assertTrue("var not NaN",Double.isNaN(u.getVariance()));
+        assertTrue("geom mean not NaN",Double.isNaN(u.getGeometricMean()));
+
+        u.addValue(1.0);
+
+        assertEquals( "mean not expected", 1.0,
+                u.getMean(), Double.MIN_VALUE);
+        assertEquals( "variance not expected", 0.0,
+                u.getVariance(), Double.MIN_VALUE);
+        assertEquals( "geometric mean not expected", 1.0,
+                u.getGeometricMean(), Double.MIN_VALUE);
+
+        u.addValue(-1.0);
+
+        assertTrue("geom mean not NaN",Double.isNaN(u.getGeometricMean()));
+
+        u.addValue(0.0);
+
+        assertTrue("geom mean not NaN",Double.isNaN(u.getGeometricMean()));
+
+        //FiXME: test all other NaN contract specs
+    }
+
+    public void testGetSummary() {
+        SummaryStatistics u = createSummaryStatistics();
+        StatisticalSummary summary = u.getSummary();
+        verifySummary(u, summary);
+        u.addValue(1d);
+        summary = u.getSummary();
+        verifySummary(u, summary);
+        u.addValue(2d);
+        summary = u.getSummary();
+        verifySummary(u, summary);
+        u.addValue(2d);
+        summary = u.getSummary();
+        verifySummary(u, summary);
+    }
+
+    public void testSerialization() {
+        SummaryStatistics u = createSummaryStatistics();
+        // Empty test
+        TestUtils.checkSerializedEquality(u);
+        SummaryStatistics s = (SummaryStatistics) TestUtils.serializeAndRecover(u);
+        StatisticalSummary summary = s.getSummary();
+        verifySummary(u, summary);
+
+        // Add some data
+        u.addValue(2d);
+        u.addValue(1d);
+        u.addValue(3d);
+        u.addValue(4d);
+        u.addValue(5d);
+
+        // Test again
+        TestUtils.checkSerializedEquality(u);
+        s = (SummaryStatistics) TestUtils.serializeAndRecover(u);
+        summary = s.getSummary();
+        verifySummary(u, summary);
+
+    }
+
+    public void testEqualsAndHashCode() {
+        SummaryStatistics u = createSummaryStatistics();
+        SummaryStatistics t = null;
+        int emptyHash = u.hashCode();
+        assertTrue("reflexive", u.equals(u));
+        assertFalse("non-null compared to null", u.equals(t));
+        assertFalse("wrong type", u.equals(Double.valueOf(0)));
+        t = createSummaryStatistics();
+        assertTrue("empty instances should be equal", t.equals(u));
+        assertTrue("empty instances should be equal", u.equals(t));
+        assertEquals("empty hash code", emptyHash, t.hashCode());
+
+        // Add some data to u
+        u.addValue(2d);
+        u.addValue(1d);
+        u.addValue(3d);
+        u.addValue(4d);
+        assertFalse("different n's should make instances not equal", t.equals(u));
+        assertFalse("different n's should make instances not equal", u.equals(t));
+        assertTrue("different n's should make hashcodes different",
+                u.hashCode() != t.hashCode());
+
+        //Add data in same order to t
+        t.addValue(2d);
+        t.addValue(1d);
+        t.addValue(3d);
+        t.addValue(4d);
+        assertTrue("summaries based on same data should be equal", t.equals(u));
+        assertTrue("summaries based on same data should be equal", u.equals(t));
+        assertEquals("summaries based on same data should have same hashcodes",
+                u.hashCode(), t.hashCode());
+
+        // Clear and make sure summaries are indistinguishable from empty summary
+        u.clear();
+        t.clear();
+        assertTrue("empty instances should be equal", t.equals(u));
+        assertTrue("empty instances should be equal", u.equals(t));
+        assertEquals("empty hash code", emptyHash, t.hashCode());
+        assertEquals("empty hash code", emptyHash, u.hashCode());
+    }
+
+    public void testCopy() throws Exception {
+        SummaryStatistics u = createSummaryStatistics();
+        u.addValue(2d);
+        u.addValue(1d);
+        u.addValue(3d);
+        u.addValue(4d);
+        SummaryStatistics v = new SummaryStatistics(u);
+        assertEquals(u, v);
+        assertEquals(v, u);
+        assertTrue(v.geoMean == v.getGeoMeanImpl());
+        assertTrue(v.mean == v.getMeanImpl());
+        assertTrue(v.min == v.getMinImpl());
+        assertTrue(v.max == v.getMaxImpl());
+        assertTrue(v.sum == v.getSumImpl());
+        assertTrue(v.sumsq == v.getSumsqImpl());
+        assertTrue(v.sumLog == v.getSumLogImpl());
+        assertTrue(v.variance == v.getVarianceImpl());
+
+        // Make sure both behave the same with additional values added
+        u.addValue(7d);
+        u.addValue(9d);
+        u.addValue(11d);
+        u.addValue(23d);
+        v.addValue(7d);
+        v.addValue(9d);
+        v.addValue(11d);
+        v.addValue(23d);
+        assertEquals(u, v);
+        assertEquals(v, u);
+
+        // Check implementation pointers are preserved
+        u.clear();
+        u.setSumImpl(new Sum());
+        SummaryStatistics.copy(u,v);
+        assertEquals(u.sum, v.sum);
+        assertEquals(u.getSumImpl(), v.getSumImpl());
+
+    }
+
+    private void verifySummary(SummaryStatistics u, StatisticalSummary s) {
+        assertEquals("N",s.getN(),u.getN());
+        TestUtils.assertEquals("sum",s.getSum(),u.getSum(),tolerance);
+        TestUtils.assertEquals("var",s.getVariance(),u.getVariance(),tolerance);
+        TestUtils.assertEquals("std",s.getStandardDeviation(),u.getStandardDeviation(),tolerance);
+        TestUtils.assertEquals("mean",s.getMean(),u.getMean(),tolerance);
+        TestUtils.assertEquals("min",s.getMin(),u.getMin(),tolerance);
+        TestUtils.assertEquals("max",s.getMax(),u.getMax(),tolerance);
+    }
+
+    public void testSetterInjection() throws Exception {
+        SummaryStatistics u = createSummaryStatistics();
+        u.setMeanImpl(new Sum());
+        u.setSumLogImpl(new Sum());
+        u.addValue(1);
+        u.addValue(3);
+        assertEquals(4, u.getMean(), 1E-14);
+        assertEquals(4, u.getSumOfLogs(), 1E-14);
+        assertEquals(FastMath.exp(2), u.getGeometricMean(), 1E-14);
+        u.clear();
+        u.addValue(1);
+        u.addValue(2);
+        assertEquals(3, u.getMean(), 1E-14);
+        u.clear();
+        u.setMeanImpl(new Mean()); // OK after clear
+    }
+
+    public void testSetterIllegalState() throws Exception {
+        SummaryStatistics u = createSummaryStatistics();
+        u.addValue(1);
+        u.addValue(3);
+        try {
+            u.setMeanImpl(new Sum());
+            fail("Expecting IllegalStateException");
+        } catch (IllegalStateException ex) {
+            // expected
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/stat/descriptive/SynchronizedDescriptiveStatisticsTest.java b/src/test/java/org/apache/commons/math/stat/descriptive/SynchronizedDescriptiveStatisticsTest.java
new file mode 100644
index 0000000..ac51256
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/descriptive/SynchronizedDescriptiveStatisticsTest.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.stat.descriptive;
+
+
+/**
+ * Test cases for the {@link SynchronizedDescriptiveStatisticsTest} class.
+ * @version $Revision: 902201 $ $Date: 2007-08-16 15:36:33 -0500 (Thu, 16 Aug
+ *          2007) $
+ */
+public final class SynchronizedDescriptiveStatisticsTest extends DescriptiveStatisticsTest {
+
+    public SynchronizedDescriptiveStatisticsTest(String name) {
+        super(name);
+    }
+
+    @Override
+    protected DescriptiveStatistics createDescriptiveStatistics() {
+        return new SynchronizedDescriptiveStatistics();
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/stat/descriptive/SynchronizedMultivariateSummaryStatisticsTest.java b/src/test/java/org/apache/commons/math/stat/descriptive/SynchronizedMultivariateSummaryStatisticsTest.java
new file mode 100644
index 0000000..1282f8d
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/descriptive/SynchronizedMultivariateSummaryStatisticsTest.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.stat.descriptive;
+
+
+/**
+ * Test cases for the {@link SynchronizedMultivariateSummaryStatisticsTest} class.
+ * @version $Revision: 902201 $ $Date: 2007-08-16 15:36:33 -0500 (Thu, 16 Aug
+ *          2007) $
+ */
+public final class SynchronizedMultivariateSummaryStatisticsTest extends MultivariateSummaryStatisticsTest {
+
+    public SynchronizedMultivariateSummaryStatisticsTest(String name) {
+        super(name);
+    }
+
+    @Override
+    protected MultivariateSummaryStatistics createMultivariateSummaryStatistics(int k, boolean isCovarianceBiasCorrected) {
+        return new SynchronizedMultivariateSummaryStatistics(k, isCovarianceBiasCorrected);
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/stat/descriptive/SynchronizedSummaryStatisticsTest.java b/src/test/java/org/apache/commons/math/stat/descriptive/SynchronizedSummaryStatisticsTest.java
new file mode 100644
index 0000000..2418a87
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/descriptive/SynchronizedSummaryStatisticsTest.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
+ * or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+package org.apache.commons.math.stat.descriptive;
+
+/**
+ * Test cases for the {@link SynchronizedSummaryStatisticsTest} class.
+ * @version $Revision: 902201 $ $Date: 2007-08-16 15:36:33 -0500 (Thu, 16 Aug
+ *          2007) $
+ */
+public final class SynchronizedSummaryStatisticsTest extends SummaryStatisticsTest {
+
+    public SynchronizedSummaryStatisticsTest(String name) {
+        super(name);
+    }
+
+    @Override
+    protected SummaryStatistics createSummaryStatistics() {
+        return new SynchronizedSummaryStatistics();
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/stat/descriptive/UnivariateStatisticAbstractTest.java b/src/test/java/org/apache/commons/math/stat/descriptive/UnivariateStatisticAbstractTest.java
new file mode 100644
index 0000000..a01503a
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/descriptive/UnivariateStatisticAbstractTest.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
+ *
+s * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.util.ArrayList;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.random.RandomData;
+import org.apache.commons.math.random.RandomDataImpl;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Test cases for the {@link UnivariateStatistic} class.
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ */
+public abstract class UnivariateStatisticAbstractTest extends TestCase {
+
+    protected double mean = 12.404545454545455d;
+    protected double geoMean = 12.070589161633011d;
+
+    protected double var = 10.00235930735931d;
+    protected double std = FastMath.sqrt(var);
+    protected double skew = 1.437423729196190d;
+    protected double kurt = 2.377191264804700d;
+
+    protected double min = 8.2d;
+    protected double max = 21d;
+    protected double median = 12d;
+    protected double percentile5 = 8.29d;
+    protected double percentile95 = 20.82d;
+
+    protected double product = 628096400563833396009676.9200400128d;
+    protected double sumLog = 54.7969806116451507d;
+    protected double sumSq = 3595.250d;
+    protected double sum = 272.90d;
+    protected double secondMoment = 210.04954545454547d;
+    protected double thirdMoment = 868.0906859504136;
+    protected double fourthMoment = 9244.080993773481;
+
+
+    protected double weightedMean = 12.366995073891626d;
+    protected double weightedVar =   9.974760968886391d;
+    protected double weightedStd = FastMath.sqrt(weightedVar);
+    protected double weightedProduct = 8517647448765288000000d;
+    protected double weightedSum = 251.05d;
+
+    protected double tolerance = 10E-12;
+
+    protected double[] testArray =
+        { 12.5, 12.0, 11.8, 14.2, 14.9, 14.5, 21.0,  8.2, 10.3, 11.3,
+          14.1,  9.9, 12.2, 12.0, 12.1, 11.0, 19.8, 11.0, 10.0,  8.8,
+           9.0, 12.3 };
+
+    protected double[] testWeightsArray =
+        {  1.5,  0.8,  1.2,  0.4,  0.8,  1.8,  1.2,  1.1,  1.0,  0.7,
+           1.3,  0.6,  0.7,  1.3,  0.7,  1.0,  0.4,  0.1,  1.4,  0.9,
+           1.1,  0.3 };
+
+    protected double[] identicalWeightsArray =
+        {  0.5,  0.5,  0.5,  0.5,  0.5,  0.5,  0.5,  0.5,  0.5,  0.5,
+           0.5,  0.5,  0.5,  0.5,  0.5,  0.5,  0.5,  0.5,  0.5,  0.5,
+           0.5,  0.5 };
+
+    protected double[] unitWeightsArray =
+        {  1.0,  1.0,  1.0,  1.0,  1.0,  1.0,  1.0,  1.0,  1.0,  1.0,
+           1.0,  1.0,  1.0,  1.0,  1.0,  1.0,  1.0,  1.0,  1.0,  1.0,
+           1.0,  1.0 };
+
+
+    public UnivariateStatisticAbstractTest(String name) {
+        super(name);
+    }
+
+    public abstract UnivariateStatistic getUnivariateStatistic();
+
+    public abstract double expectedValue();
+
+    public double getTolerance() {
+        return tolerance;
+    }
+
+    public void testEvaluation() throws Exception {
+        assertEquals(
+            expectedValue(),
+            getUnivariateStatistic().evaluate(testArray),
+            getTolerance());
+    }
+
+    public void testCopy() throws Exception {
+        UnivariateStatistic original = getUnivariateStatistic();
+        UnivariateStatistic copy = original.copy();
+        assertEquals(
+                expectedValue(),
+                copy.evaluate(testArray),
+                getTolerance());
+    }
+
+    /**
+     * Tests consistency of weighted statistic computation.
+     * For statistics that support weighted evaluation, this test case compares
+     * the result of direct computation on an array with repeated values with
+     * a weighted computation on the corresponding (shorter) array with each
+     * value appearing only once but with a weight value equal to its multiplicity
+     * in the repeating array.
+     */
+
+    public void testWeightedConsistency() throws Exception {
+
+        // See if this statistic computes weighted statistics
+        // If not, skip this test
+        UnivariateStatistic statistic = getUnivariateStatistic();
+        if (!(statistic instanceof WeightedEvaluation)) {
+            return;
+        }
+
+        // Create arrays of values and corresponding integral weights
+        // and longer array with values repeated according to the weights
+        final int len = 10;        // length of values array
+        final double mu = 0;       // mean of test data
+        final double sigma = 5;    // std dev of test data
+        double[] values = new double[len];
+        double[] weights = new double[len];
+        RandomData randomData = new RandomDataImpl();
+
+        // Fill weights array with random int values between 1 and 5
+        int[] intWeights = new int[len];
+        for (int i = 0; i < len; i++) {
+            intWeights[i] = randomData.nextInt(1, 5);
+            weights[i] = intWeights[i];
+        }
+
+        // Fill values array with random data from N(mu, sigma)
+        // and fill valuesList with values from values array with
+        // values[i] repeated weights[i] times, each i
+        List<Double> valuesList = new ArrayList<Double>();
+        for (int i = 0; i < len; i++) {
+            double value = randomData.nextGaussian(mu, sigma);
+            values[i] = value;
+            for (int j = 0; j < intWeights[i]; j++) {
+                valuesList.add(new Double(value));
+            }
+        }
+
+        // Dump valuesList into repeatedValues array
+        int sumWeights = valuesList.size();
+        double[] repeatedValues = new double[sumWeights];
+        for (int i = 0; i < sumWeights; i++) {
+            repeatedValues[i] = valuesList.get(i);
+        }
+
+        // Compare result of weighted statistic computation with direct computation
+        // on array of repeated values
+        WeightedEvaluation weightedStatistic = (WeightedEvaluation) statistic;
+        TestUtils.assertRelativelyEquals(statistic.evaluate(repeatedValues),
+                weightedStatistic.evaluate(values, weights, 0, values.length),
+                10E-14);
+
+        // Check consistency of weighted evaluation methods
+        assertEquals(weightedStatistic.evaluate(values, weights, 0, values.length),
+                weightedStatistic.evaluate(values, weights), Double.MIN_VALUE);
+
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/stat/descriptive/moment/FirstMomentTest.java b/src/test/java/org/apache/commons/math/stat/descriptive/moment/FirstMomentTest.java
new file mode 100644
index 0000000..fb13927
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/descriptive/moment/FirstMomentTest.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.stat.descriptive.moment;
+
+import org.apache.commons.math.stat.descriptive.StorelessUnivariateStatisticAbstractTest;
+import org.apache.commons.math.stat.descriptive.UnivariateStatistic;
+
+/**
+ * Test cases for the {@link FirstMoment} class.
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+public class FirstMomentTest extends StorelessUnivariateStatisticAbstractTest{
+
+    /** descriptive statistic. */
+    protected FirstMoment stat;
+
+    /**
+     * @param name
+     */
+    public FirstMomentTest(String name) {
+        super(name);
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.descriptive.UnivariateStatisticAbstractTest#getUnivariateStatistic()
+     */
+    @Override
+    public UnivariateStatistic getUnivariateStatistic() {
+        return new FirstMoment();
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.descriptive.UnivariateStatisticAbstractTest#expectedValue()
+     */
+    @Override
+    public double expectedValue() {
+        return this.mean;
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/stat/descriptive/moment/FourthMomentTest.java b/src/test/java/org/apache/commons/math/stat/descriptive/moment/FourthMomentTest.java
new file mode 100644
index 0000000..5219d10
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/descriptive/moment/FourthMomentTest.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.stat.descriptive.moment;
+
+import org.apache.commons.math.stat.descriptive.StorelessUnivariateStatisticAbstractTest;
+import org.apache.commons.math.stat.descriptive.UnivariateStatistic;
+
+/**
+ * Test cases for the {@link FourthMoment} class.
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+public class FourthMomentTest extends StorelessUnivariateStatisticAbstractTest{
+
+    /** descriptive statistic. */
+    protected FourthMoment stat;
+
+    /**
+     * @param name
+     */
+    public FourthMomentTest(String name) {
+        super(name);
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.descriptive.UnivariateStatisticAbstractTest#getUnivariateStatistic()
+     */
+    @Override
+    public UnivariateStatistic getUnivariateStatistic() {
+        return new FourthMoment();
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.descriptive.UnivariateStatisticAbstractTest#expectedValue()
+     */
+    @Override
+    public double expectedValue() {
+       return this.fourthMoment;
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/stat/descriptive/moment/GeometricMeanTest.java b/src/test/java/org/apache/commons/math/stat/descriptive/moment/GeometricMeanTest.java
new file mode 100644
index 0000000..e021a42
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/descriptive/moment/GeometricMeanTest.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.stat.descriptive.moment;
+
+import org.apache.commons.math.stat.descriptive.StorelessUnivariateStatisticAbstractTest;
+import org.apache.commons.math.stat.descriptive.UnivariateStatistic;
+
+/**
+ * Test cases for the {@link UnivariateStatistic} class.
+ * @version $Revision: 902201 $ $Date: 2010-01-22 19:18:16 +0100 (ven. 22 janv. 2010) $
+ */
+public class GeometricMeanTest extends StorelessUnivariateStatisticAbstractTest{
+
+    protected GeometricMean stat;
+
+    /**
+     * @param name
+     */
+    public GeometricMeanTest(String name) {
+        super(name);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public UnivariateStatistic getUnivariateStatistic() {
+        return new GeometricMean();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public double expectedValue() {
+        return this.geoMean;
+    }
+
+    public void testSpecialValues() {
+        GeometricMean mean = new GeometricMean();
+        // empty
+        assertTrue(Double.isNaN(mean.getResult()));
+
+        // finite data
+        mean.increment(1d);
+        assertFalse(Double.isNaN(mean.getResult()));
+
+        // add 0 -- makes log sum blow to minus infinity, should make 0
+        mean.increment(0d);
+        assertEquals(0d, mean.getResult(), 0);
+
+        // add positive infinity - note the minus infinity above
+        mean.increment(Double.POSITIVE_INFINITY);
+        assertTrue(Double.isNaN(mean.getResult()));
+
+        // clear
+        mean.clear();
+        assertTrue(Double.isNaN(mean.getResult()));
+
+        // positive infinity by itself
+        mean.increment(Double.POSITIVE_INFINITY);
+        assertEquals(Double.POSITIVE_INFINITY, mean.getResult(), 0);
+
+        // negative value -- should make NaN
+        mean.increment(-2d);
+        assertTrue(Double.isNaN(mean.getResult()));
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/stat/descriptive/moment/KurtosisTest.java b/src/test/java/org/apache/commons/math/stat/descriptive/moment/KurtosisTest.java
new file mode 100644
index 0000000..62878cd
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/descriptive/moment/KurtosisTest.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.descriptive.moment;
+
+import org.apache.commons.math.stat.descriptive.StorelessUnivariateStatisticAbstractTest;
+import org.apache.commons.math.stat.descriptive.UnivariateStatistic;
+
+/**
+ * Test cases for the {@link UnivariateStatistic} class.
+ * @version $Revision: 902201 $ $Date: 2010-01-22 19:18:16 +0100 (ven. 22 janv. 2010) $
+ */
+public class KurtosisTest extends StorelessUnivariateStatisticAbstractTest{
+
+    protected Kurtosis stat;
+
+    /**
+     * @param name
+     */
+    public KurtosisTest(String name) {
+        super(name);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public UnivariateStatistic getUnivariateStatistic() {
+        return new Kurtosis();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public double expectedValue() {
+        return this.kurt;
+    }
+
+    /**
+     * Make sure Double.NaN is returned iff n < 4
+     *
+     */
+    public void testNaN() {
+        Kurtosis kurt = new Kurtosis();
+        assertTrue(Double.isNaN(kurt.getResult()));
+        kurt.increment(1d);
+        assertTrue(Double.isNaN(kurt.getResult()));
+        kurt.increment(1d);
+        assertTrue(Double.isNaN(kurt.getResult()));
+        kurt.increment(1d);
+        assertTrue(Double.isNaN(kurt.getResult()));
+        kurt.increment(1d);
+        assertFalse(Double.isNaN(kurt.getResult()));
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/stat/descriptive/moment/MeanTest.java b/src/test/java/org/apache/commons/math/stat/descriptive/moment/MeanTest.java
new file mode 100644
index 0000000..102803c
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/descriptive/moment/MeanTest.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.stat.descriptive.moment;
+
+import org.apache.commons.math.stat.descriptive.StorelessUnivariateStatisticAbstractTest;
+import org.apache.commons.math.stat.descriptive.UnivariateStatistic;
+
+/**
+ * Test cases for the {@link UnivariateStatistic} class.
+ * @version $Revision: 902201 $ $Date: 2010-01-22 19:18:16 +0100 (ven. 22 janv. 2010) $
+ */
+public class MeanTest extends StorelessUnivariateStatisticAbstractTest{
+
+    protected Mean stat;
+
+    /**
+     * @param name
+     */
+    public MeanTest(String name) {
+        super(name);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public UnivariateStatistic getUnivariateStatistic() {
+        return new Mean();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public double expectedValue() {
+        return this.mean;
+    }
+
+    /**Expected value for  the testArray defined in UnivariateStatisticAbstractTest */
+    public double expectedWeightedValue() {
+        return this.weightedMean;
+    }
+
+    public void testSmallSamples() {
+        Mean mean = new Mean();
+        assertTrue(Double.isNaN(mean.getResult()));
+        mean.increment(1d);
+        assertEquals(1d, mean.getResult(), 0);
+    }
+
+    public void testWeightedMean() {
+        Mean mean = new Mean();
+        assertEquals(expectedWeightedValue(), mean.evaluate(testArray, testWeightsArray, 0, testArray.length), getTolerance());
+        assertEquals(expectedValue(), mean.evaluate(testArray, identicalWeightsArray, 0, testArray.length), getTolerance());
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/stat/descriptive/moment/SecondMomentTest.java b/src/test/java/org/apache/commons/math/stat/descriptive/moment/SecondMomentTest.java
new file mode 100644
index 0000000..7af7774
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/descriptive/moment/SecondMomentTest.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.stat.descriptive.moment;
+
+import org.apache.commons.math.stat.descriptive.StorelessUnivariateStatisticAbstractTest;
+import org.apache.commons.math.stat.descriptive.UnivariateStatistic;
+
+/**
+ * Test cases for the {@link SecondMoment} class.
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+public class SecondMomentTest extends StorelessUnivariateStatisticAbstractTest {
+
+    /** descriptive statistic. */
+    protected SecondMoment stat;
+
+    /**
+     * @param name
+     */
+    public SecondMomentTest(String name) {
+        super(name);
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.descriptive.UnivariateStatisticAbstractTest#getUnivariateStatistic()
+     */
+    @Override
+    public UnivariateStatistic getUnivariateStatistic() {
+        return new SecondMoment();
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.descriptive.UnivariateStatisticAbstractTest#expectedValue()
+     */
+    @Override
+    public double expectedValue() {
+        return this.secondMoment;
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/stat/descriptive/moment/SemiVarianceTest.java b/src/test/java/org/apache/commons/math/stat/descriptive/moment/SemiVarianceTest.java
new file mode 100644
index 0000000..24b1512
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/descriptive/moment/SemiVarianceTest.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 org.apache.commons.math.TestUtils;
+import org.apache.commons.math.stat.StatUtils;
+
+import junit.framework.TestCase;
+
+public class SemiVarianceTest extends TestCase {
+
+    public void testInsufficientData() {
+        double[] nothing = null;
+        SemiVariance sv = new SemiVariance();
+        try {
+            sv.evaluate(nothing);
+            fail("null is not a valid data array.");
+        } catch (IllegalArgumentException iae) {
+        }
+
+        try {
+            sv.setVarianceDirection(SemiVariance.UPSIDE_VARIANCE);
+            sv.evaluate(nothing);
+            fail("null is not a valid data array.");
+        } catch (IllegalArgumentException iae) {
+        }
+        nothing = new double[] {};
+        assertTrue(Double.isNaN(sv.evaluate(nothing)));
+    }
+
+    public void testSingleDown() {
+        SemiVariance sv = new SemiVariance();
+        double[] values = { 50.0d };
+        double singletest = sv.evaluate(values);
+        assertEquals(0.0d, singletest, 0);
+    }
+
+    public void testSingleUp() {
+        SemiVariance sv = new SemiVariance(SemiVariance.UPSIDE_VARIANCE);
+        double[] values = { 50.0d };
+        double singletest = sv.evaluate(values);
+        assertEquals(0.0d, singletest, 0);
+    }
+
+    public void testSample() {
+        final double[] values = { -2.0d, 2.0d, 4.0d, -2.0d, 22.0d, 11.0d, 3.0d, 14.0d, 5.0d };
+        final int length = values.length;
+        final double mean = StatUtils.mean(values); // 6.333...
+        final SemiVariance sv = new SemiVariance();  // Default bias correction is true
+        final double downsideSemiVariance = sv.evaluate(values); // Downside is the default
+        assertEquals(TestUtils.sumSquareDev(new double[] {-2d, 2d, 4d, -2d, 3d, 5d}, mean) / (length - 1),
+                downsideSemiVariance, 1E-14);
+
+        sv.setVarianceDirection(SemiVariance.UPSIDE_VARIANCE);
+        final double upsideSemiVariance = sv.evaluate(values);
+        assertEquals(TestUtils.sumSquareDev(new double[] {22d, 11d, 14d}, mean) / (length - 1),
+                upsideSemiVariance, 1E-14);
+
+        // Verify that upper + lower semivariance against the mean sum to variance
+        assertEquals(StatUtils.variance(values), downsideSemiVariance + upsideSemiVariance, 10e-12);
+    }
+
+    public void testPopulation() {
+        double[] values = { -2.0d, 2.0d, 4.0d, -2.0d, 22.0d, 11.0d, 3.0d, 14.0d, 5.0d };
+        SemiVariance sv = new SemiVariance(false);
+
+        double singletest = sv.evaluate(values);
+        assertEquals(19.556d, singletest, 0.01d);
+
+        sv.setVarianceDirection(SemiVariance.UPSIDE_VARIANCE);
+        singletest = sv.evaluate(values);
+        assertEquals(36.222d, singletest, 0.01d);
+    }
+
+    public void testNonMeanCutoffs() {
+        double[] values = { -2.0d, 2.0d, 4.0d, -2.0d, 22.0d, 11.0d, 3.0d, 14.0d, 5.0d };
+        SemiVariance sv = new SemiVariance(false); // Turn off bias correction - use df = length
+
+        double singletest = sv.evaluate(values, 1.0d, SemiVariance.DOWNSIDE_VARIANCE, false, 0, values.length);
+        assertEquals(TestUtils.sumSquareDev(new double[] { -2d, -2d }, 1.0d) / values.length,
+                singletest, 0.01d);
+
+        singletest = sv.evaluate(values, 3.0d, SemiVariance.UPSIDE_VARIANCE, false, 0, values.length);
+        assertEquals(TestUtils.sumSquareDev(new double[] { 4d, 22d, 11d, 14d, 5d }, 3.0d) / values.length, singletest,
+                0.01d);
+    }
+
+    /**
+     * Check that the lower + upper semivariance against the mean sum to the
+     * variance.
+     */
+    public void testVarianceDecompMeanCutoff() {
+        double[] values = { -2.0d, 2.0d, 4.0d, -2.0d, 22.0d, 11.0d, 3.0d, 14.0d, 5.0d };
+        double variance = StatUtils.variance(values);
+        SemiVariance sv = new SemiVariance(true); // Bias corrected
+        sv.setVarianceDirection(SemiVariance.DOWNSIDE_VARIANCE);
+        final double lower = sv.evaluate(values);
+        sv.setVarianceDirection(SemiVariance.UPSIDE_VARIANCE);
+        final double upper = sv.evaluate(values);
+        assertEquals(variance, lower + upper, 10e-12);
+    }
+
+    /**
+     * Check that upper and lower semivariances against a cutoff sum to the sum
+     * of squared deviations of the full set of values against the cutoff
+     * divided by df = length - 1 (assuming bias-corrected).
+     */
+    public void testVarianceDecompNonMeanCutoff() {
+        double[] values = { -2.0d, 2.0d, 4.0d, -2.0d, 22.0d, 11.0d, 3.0d, 14.0d, 5.0d };
+        double target = 0;
+        double totalSumOfSquares = TestUtils.sumSquareDev(values, target);
+        SemiVariance sv = new SemiVariance(true); // Bias corrected
+        sv.setVarianceDirection(SemiVariance.DOWNSIDE_VARIANCE);
+        double lower = sv.evaluate(values, target);
+        sv.setVarianceDirection(SemiVariance.UPSIDE_VARIANCE);
+        double upper = sv.evaluate(values, target);
+        assertEquals(totalSumOfSquares / (values.length - 1), lower + upper, 10e-12);
+    }
+
+    public void testNoVariance() {
+        final double[] values = {100d, 100d, 100d, 100d};
+        SemiVariance sv = new SemiVariance();
+        assertEquals(0, sv.evaluate(values), 10E-12);
+        assertEquals(0, sv.evaluate(values, 100d), 10E-12);
+        assertEquals(0, sv.evaluate(values, 100d, SemiVariance.UPSIDE_VARIANCE, false, 0, values.length), 10E-12);
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/stat/descriptive/moment/SkewnessTest.java b/src/test/java/org/apache/commons/math/stat/descriptive/moment/SkewnessTest.java
new file mode 100644
index 0000000..e0f4bd4
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/descriptive/moment/SkewnessTest.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.stat.descriptive.moment;
+
+import org.apache.commons.math.stat.descriptive.StorelessUnivariateStatisticAbstractTest;
+import org.apache.commons.math.stat.descriptive.UnivariateStatistic;
+
+/**
+ * Test cases for the {@link UnivariateStatistic} class.
+ *
+ * @version $Revision: 902201 $ $Date: 2010-01-22 19:18:16 +0100 (ven. 22 janv. 2010) $
+ */
+public class SkewnessTest extends StorelessUnivariateStatisticAbstractTest{
+
+    protected Skewness stat;
+
+    /**
+     * @param name
+     */
+    public SkewnessTest(String name) {
+        super(name);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public UnivariateStatistic getUnivariateStatistic() {
+        return new Skewness();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public double expectedValue() {
+        return this.skew;
+    }
+
+    /**
+     * Make sure Double.NaN is returned iff n < 3
+     *
+     */
+    public void testNaN() {
+        Skewness skew = new Skewness();
+        assertTrue(Double.isNaN(skew.getResult()));
+        skew.increment(1d);
+        assertTrue(Double.isNaN(skew.getResult()));
+        skew.increment(1d);
+        assertTrue(Double.isNaN(skew.getResult()));
+        skew.increment(1d);
+        assertFalse(Double.isNaN(skew.getResult()));
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/stat/descriptive/moment/StandardDeviationTest.java b/src/test/java/org/apache/commons/math/stat/descriptive/moment/StandardDeviationTest.java
new file mode 100644
index 0000000..9429fcd
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/descriptive/moment/StandardDeviationTest.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.stat.descriptive.moment;
+
+import org.apache.commons.math.stat.descriptive.StorelessUnivariateStatisticAbstractTest;
+import org.apache.commons.math.stat.descriptive.UnivariateStatistic;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Test cases for the {@link UnivariateStatistic} class.
+ *
+ * @version $Revision: 990655 $ $Date: 2010-08-29 23:49:40 +0200 (dim. 29 août 2010) $
+ */
+public class StandardDeviationTest extends StorelessUnivariateStatisticAbstractTest{
+
+    protected StandardDeviation stat;
+
+    /**
+     * @param name
+     */
+    public StandardDeviationTest(String name) {
+        super(name);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public UnivariateStatistic getUnivariateStatistic() {
+        return new StandardDeviation();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public double expectedValue() {
+        return this.std;
+    }
+
+    /**
+     * Make sure Double.NaN is returned iff n = 0
+     *
+     */
+    public void testNaN() {
+        StandardDeviation std = new StandardDeviation();
+        assertTrue(Double.isNaN(std.getResult()));
+        std.increment(1d);
+        assertEquals(0d, std.getResult(), 0);
+    }
+
+    /**
+     * Test population version of variance
+     */
+    public void testPopulation() {
+        double[] values = {-1.0d, 3.1d, 4.0d, -2.1d, 22d, 11.7d, 3d, 14d};
+        double sigma = populationStandardDeviation(values);
+        SecondMoment m = new SecondMoment();
+        m.evaluate(values);  // side effect is to add values
+        StandardDeviation s1 = new StandardDeviation();
+        s1.setBiasCorrected(false);
+        assertEquals(sigma, s1.evaluate(values), 1E-14);
+        s1.incrementAll(values);
+        assertEquals(sigma, s1.getResult(), 1E-14);
+        s1 = new StandardDeviation(false, m);
+        assertEquals(sigma, s1.getResult(), 1E-14);
+        s1 = new StandardDeviation(false);
+        assertEquals(sigma, s1.evaluate(values), 1E-14);
+        s1.incrementAll(values);
+        assertEquals(sigma, s1.getResult(), 1E-14);
+    }
+
+    /**
+     * Definitional formula for population standard deviation
+     */
+    protected double populationStandardDeviation(double[] v) {
+        double mean = new Mean().evaluate(v);
+        double sum = 0;
+        for (int i = 0; i < v.length; i++) {
+            sum += (v[i] - mean) * (v[i] - mean);
+        }
+        return FastMath.sqrt(sum / v.length);
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/stat/descriptive/moment/ThirdMomentTest.java b/src/test/java/org/apache/commons/math/stat/descriptive/moment/ThirdMomentTest.java
new file mode 100644
index 0000000..f376c53
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/descriptive/moment/ThirdMomentTest.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.stat.descriptive.moment;
+
+import org.apache.commons.math.stat.descriptive.StorelessUnivariateStatisticAbstractTest;
+import org.apache.commons.math.stat.descriptive.UnivariateStatistic;
+
+/**
+ * Test cases for the {@link ThirdMoment} class.
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+public class ThirdMomentTest extends StorelessUnivariateStatisticAbstractTest{
+
+    /** descriptive statistic. */
+    protected ThirdMoment stat;
+
+    /**
+     * @param name
+     */
+    public ThirdMomentTest(String name) {
+        super(name);
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.descriptive.UnivariateStatisticAbstractTest#getUnivariateStatistic()
+     */
+    @Override
+    public UnivariateStatistic getUnivariateStatistic() {
+        return new ThirdMoment();
+    }
+
+    /**
+     * @see org.apache.commons.math.stat.descriptive.UnivariateStatisticAbstractTest#expectedValue()
+     */
+    @Override
+    public double expectedValue() {
+      return this.thirdMoment;
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/stat/descriptive/moment/VarianceTest.java b/src/test/java/org/apache/commons/math/stat/descriptive/moment/VarianceTest.java
new file mode 100644
index 0000000..7da38ed
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/descriptive/moment/VarianceTest.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.moment;
+
+import org.apache.commons.math.stat.descriptive.StorelessUnivariateStatisticAbstractTest;
+import org.apache.commons.math.stat.descriptive.UnivariateStatistic;
+import org.apache.commons.math.util.MathUtils;
+
+/**
+ * Test cases for the {@link UnivariateStatistic} class.
+ *
+ * @version $Revision: 902201 $ $Date: 2010-01-22 19:18:16 +0100 (ven. 22 janv. 2010) $
+ */
+public class VarianceTest extends StorelessUnivariateStatisticAbstractTest{
+
+    protected Variance stat;
+
+    /**
+     * @param name
+     */
+    public VarianceTest(String name) {
+        super(name);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public UnivariateStatistic getUnivariateStatistic() {
+        return new Variance();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public double expectedValue() {
+        return this.var;
+    }
+
+    /**Expected value for  the testArray defined in UnivariateStatisticAbstractTest */
+    public double expectedWeightedValue() {
+        return this.weightedVar;
+    }
+
+    /**
+     * Make sure Double.NaN is returned iff n = 0
+     *
+     */
+    public void testNaN() {
+        StandardDeviation std = new StandardDeviation();
+        assertTrue(Double.isNaN(std.getResult()));
+        std.increment(1d);
+        assertEquals(0d, std.getResult(), 0);
+    }
+
+    /**
+     * Test population version of variance
+     */
+    public void testPopulation() {
+        double[] values = {-1.0d, 3.1d, 4.0d, -2.1d, 22d, 11.7d, 3d, 14d};
+        SecondMoment m = new SecondMoment();
+        m.evaluate(values);  // side effect is to add values
+        Variance v1 = new Variance();
+        v1.setBiasCorrected(false);
+        assertEquals(populationVariance(values), v1.evaluate(values), 1E-14);
+        v1.incrementAll(values);
+        assertEquals(populationVariance(values), v1.getResult(), 1E-14);
+        v1 = new Variance(false, m);
+        assertEquals(populationVariance(values), v1.getResult(), 1E-14);
+        v1 = new Variance(false);
+        assertEquals(populationVariance(values), v1.evaluate(values), 1E-14);
+        v1.incrementAll(values);
+        assertEquals(populationVariance(values), v1.getResult(), 1E-14);
+    }
+
+    /**
+     * Definitional formula for population variance
+     */
+    protected double populationVariance(double[] v) {
+        double mean = new Mean().evaluate(v);
+        double sum = 0;
+        for (int i = 0; i < v.length; i++) {
+           sum += (v[i] - mean) * (v[i] - mean);
+        }
+        return sum / v.length;
+    }
+
+    public void testWeightedVariance() {
+        Variance variance = new Variance();
+        assertEquals(expectedWeightedValue(),
+                variance.evaluate(testArray, testWeightsArray, 0, testArray.length), getTolerance());
+
+        // All weights = 1 -> weighted variance = unweighted variance
+        assertEquals(expectedValue(),
+                variance.evaluate(testArray, unitWeightsArray, 0, testArray.length), getTolerance());
+
+        // All weights the same -> when weights are normalized to sum to the length of the values array,
+        // weighted variance = unweighted value
+        assertEquals(expectedValue(),
+                variance.evaluate(testArray, MathUtils.normalizeArray(identicalWeightsArray, testArray.length),
+                        0, testArray.length), getTolerance());
+
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/stat/descriptive/moment/VectorialCovarianceTest.java b/src/test/java/org/apache/commons/math/stat/descriptive/moment/VectorialCovarianceTest.java
new file mode 100644
index 0000000..74c60bd
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/descriptive/moment/VectorialCovarianceTest.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.stat.descriptive.moment;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.math.DimensionMismatchException;
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.linear.RealMatrix;
+
+public class VectorialCovarianceTest
+extends TestCase {
+
+    public VectorialCovarianceTest(String name) {
+        super(name);
+        points = null;
+    }
+
+    public void testMismatch() {
+        try {
+            new VectorialCovariance(8, true).increment(new double[5]);
+            fail("an exception should have been thrown");
+        } catch (DimensionMismatchException dme) {
+            assertEquals(5, dme.getDimension1());
+            assertEquals(8, dme.getDimension2());
+        }
+    }
+
+    public void testSimplistic() throws DimensionMismatchException {
+        VectorialCovariance stat = new VectorialCovariance(2, true);
+        stat.increment(new double[] {-1.0,  1.0});
+        stat.increment(new double[] { 1.0, -1.0});
+        RealMatrix c = stat.getResult();
+        assertEquals( 2.0, c.getEntry(0, 0), 1.0e-12);
+        assertEquals(-2.0, c.getEntry(1, 0), 1.0e-12);
+        assertEquals( 2.0, c.getEntry(1, 1), 1.0e-12);
+    }
+
+    public void testBasicStats() throws DimensionMismatchException {
+
+        VectorialCovariance stat = new VectorialCovariance(points[0].length, true);
+        for (int i = 0; i < points.length; ++i) {
+            stat.increment(points[i]);
+        }
+
+        assertEquals(points.length, stat.getN());
+
+        RealMatrix c = stat.getResult();
+        double[][] refC    = new double[][] {
+                { 8.0470, -1.9195, -3.4445},
+                {-1.9195,  1.0470,  3.2795},
+                {-3.4445,  3.2795, 12.2070}
+        };
+
+        for (int i = 0; i < c.getRowDimension(); ++i) {
+            for (int j = 0; j <= i; ++j) {
+                assertEquals(refC[i][j], c.getEntry(i, j), 1.0e-12);
+            }
+        }
+
+    }
+
+    public void testSerial(){
+        VectorialCovariance stat = new VectorialCovariance(points[0].length, true);
+        assertEquals(stat, TestUtils.serializeAndRecover(stat));
+    }
+
+    @Override
+    public void setUp() {
+        points = new double[][] {
+                { 1.2, 2.3,  4.5},
+                {-0.7, 2.3,  5.0},
+                { 3.1, 0.0, -3.1},
+                { 6.0, 1.2,  4.2},
+                {-0.7, 2.3,  5.0}
+        };
+    }
+
+    @Override
+    public void tearDown() {
+        points = null;
+    }
+
+    private double [][] points;
+
+}
diff --git a/src/test/java/org/apache/commons/math/stat/descriptive/moment/VectorialMeanTest.java b/src/test/java/org/apache/commons/math/stat/descriptive/moment/VectorialMeanTest.java
new file mode 100644
index 0000000..a8b9ae8
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/descriptive/moment/VectorialMeanTest.java
@@ -0,0 +1,95 @@
+//Licensed to the Apache Software Foundation (ASF) under one
+//or more contributor license agreements.  See the NOTICE file
+//distributed with this work for additional information
+//regarding copyright ownership.  The ASF licenses this file
+//to you under the Apache License, Version 2.0 (the
+//"License"); you may not use this file except in compliance
+//with the License.  You may obtain a copy of the License at
+
+//http://www.apache.org/licenses/LICENSE-2.0
+
+//Unless required by applicable law or agreed to in writing,
+//software distributed under the License is distributed on an
+//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+//KIND, either express or implied.  See the License for the
+//specific language governing permissions and limitations
+//under the License.
+
+package org.apache.commons.math.stat.descriptive.moment;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.math.DimensionMismatchException;
+import org.apache.commons.math.TestUtils;
+
+public class VectorialMeanTest
+extends TestCase {
+
+    public VectorialMeanTest(String name) {
+        super(name);
+        points = null;
+    }
+
+    public void testMismatch() {
+        try {
+            new VectorialMean(8).increment(new double[5]);
+            fail("an exception should have been thrown");
+        } catch (DimensionMismatchException dme) {
+            assertEquals(5, dme.getDimension1());
+            assertEquals(8, dme.getDimension2());
+        }
+    }
+
+    public void testSimplistic() throws DimensionMismatchException {
+        VectorialMean stat = new VectorialMean(2);
+        stat.increment(new double[] {-1.0,  1.0});
+        stat.increment(new double[] { 1.0, -1.0});
+        double[] mean = stat.getResult();
+        assertEquals(0.0, mean[0], 1.0e-12);
+        assertEquals(0.0, mean[1], 1.0e-12);
+    }
+
+    public void testBasicStats() throws DimensionMismatchException {
+
+        VectorialMean stat = new VectorialMean(points[0].length);
+        for (int i = 0; i < points.length; ++i) {
+            stat.increment(points[i]);
+        }
+
+        assertEquals(points.length, stat.getN());
+
+        double[] mean = stat.getResult();
+        double[]   refMean = new double[] { 1.78, 1.62,  3.12};
+
+        for (int i = 0; i < mean.length; ++i) {
+            assertEquals(refMean[i], mean[i], 1.0e-12);
+        }
+
+    }
+
+    public void testSerial() throws DimensionMismatchException {
+        VectorialMean stat = new VectorialMean(points[0].length);
+        for (int i = 0; i < points.length; ++i) {
+            stat.increment(points[i]);
+        }
+        assertEquals(stat, TestUtils.serializeAndRecover(stat));
+    }
+    @Override
+    public void setUp() {
+        points = new double[][] {
+                { 1.2, 2.3,  4.5},
+                {-0.7, 2.3,  5.0},
+                { 3.1, 0.0, -3.1},
+                { 6.0, 1.2,  4.2},
+                {-0.7, 2.3,  5.0}
+        };
+    }
+
+    @Override
+    public void tearDown() {
+        points = null;
+    }
+
+    private double [][] points;
+
+}
diff --git a/src/test/java/org/apache/commons/math/stat/descriptive/rank/MaxTest.java b/src/test/java/org/apache/commons/math/stat/descriptive/rank/MaxTest.java
new file mode 100644
index 0000000..7482e61
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/descriptive/rank/MaxTest.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.stat.descriptive.rank;
+
+import org.apache.commons.math.stat.descriptive.StorelessUnivariateStatisticAbstractTest;
+import org.apache.commons.math.stat.descriptive.UnivariateStatistic;
+
+/**
+ * Test cases for the {@link UnivariateStatistic} class.
+ * @version $Revision: 902201 $ $Date: 2010-01-22 19:18:16 +0100 (ven. 22 janv. 2010) $
+ */
+public class MaxTest extends StorelessUnivariateStatisticAbstractTest{
+
+    protected Max stat;
+
+    /**
+     * @param name
+     */
+    public MaxTest(String name) {
+        super(name);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public UnivariateStatistic getUnivariateStatistic() {
+        return new Max();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public double expectedValue() {
+        return this.max;
+    }
+
+    public void testSpecialValues() {
+        double[] testArray = {0d, Double.NaN, Double.NEGATIVE_INFINITY,
+                Double.POSITIVE_INFINITY};
+        Max max = new Max();
+        assertTrue(Double.isNaN(max.getResult()));
+        max.increment(testArray[0]);
+        assertEquals(0d, max.getResult(), 0);
+        max.increment(testArray[1]);
+        assertEquals(0d, max.getResult(), 0);
+        max.increment(testArray[2]);
+        assertEquals(0d, max.getResult(), 0);
+        max.increment(testArray[3]);
+        assertEquals(Double.POSITIVE_INFINITY, max.getResult(), 0);
+        assertEquals(Double.POSITIVE_INFINITY, max.evaluate(testArray), 0);
+    }
+
+    public void testNaNs() {
+        Max max = new Max();
+        double nan = Double.NaN;
+        assertEquals(3d, max.evaluate(new double[]{nan, 2d, 3d}), 0);
+        assertEquals(3d, max.evaluate(new double[]{1d, nan, 3d}), 0);
+        assertEquals(2d, max.evaluate(new double[]{1d, 2d, nan}), 0);
+        assertTrue(Double.isNaN(max.evaluate(new double[]{nan, nan, nan})));
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/stat/descriptive/rank/MedianTest.java b/src/test/java/org/apache/commons/math/stat/descriptive/rank/MedianTest.java
new file mode 100644
index 0000000..8ae2a6e
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/descriptive/rank/MedianTest.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.rank;
+
+import org.apache.commons.math.stat.descriptive.UnivariateStatistic;
+import org.apache.commons.math.stat.descriptive.UnivariateStatisticAbstractTest;
+
+/**
+ * Test cases for the {@link UnivariateStatistic} class.
+ * @version $Revision: 902201 $ $Date: 2010-01-22 19:18:16 +0100 (ven. 22 janv. 2010) $
+ */
+public class MedianTest extends UnivariateStatisticAbstractTest{
+
+    protected Median stat;
+
+    /**
+     * @param name
+     */
+    public MedianTest(String name) {
+        super(name);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public UnivariateStatistic getUnivariateStatistic() {
+        return new Median();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public double expectedValue() {
+        return this.median;
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/stat/descriptive/rank/MinTest.java b/src/test/java/org/apache/commons/math/stat/descriptive/rank/MinTest.java
new file mode 100644
index 0000000..6b5f489
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/descriptive/rank/MinTest.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.stat.descriptive.rank;
+
+import org.apache.commons.math.stat.descriptive.StorelessUnivariateStatisticAbstractTest;
+import org.apache.commons.math.stat.descriptive.UnivariateStatistic;
+
+/**
+ * Test cases for the {@link UnivariateStatistic} class.
+ * @version $Revision: 902201 $ $Date: 2010-01-22 19:18:16 +0100 (ven. 22 janv. 2010) $
+ */
+public class MinTest extends StorelessUnivariateStatisticAbstractTest{
+
+    protected Min stat;
+
+    /**
+     * @param name
+     */
+    public MinTest(String name) {
+        super(name);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public UnivariateStatistic getUnivariateStatistic() {
+        return new Min();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public double expectedValue() {
+        return this.min;
+    }
+
+    public void testSpecialValues() {
+        double[] testArray = {0d, Double.NaN, Double.POSITIVE_INFINITY,
+                Double.NEGATIVE_INFINITY};
+        Min min = new Min();
+        assertTrue(Double.isNaN(min.getResult()));
+        min.increment(testArray[0]);
+        assertEquals(0d, min.getResult(), 0);
+        min.increment(testArray[1]);
+        assertEquals(0d, min.getResult(), 0);
+        min.increment(testArray[2]);
+        assertEquals(0d, min.getResult(), 0);
+        min.increment(testArray[3]);
+        assertEquals(Double.NEGATIVE_INFINITY, min.getResult(), 0);
+        assertEquals(Double.NEGATIVE_INFINITY, min.evaluate(testArray), 0);
+    }
+
+    public void testNaNs() {
+        Min min = new Min();
+        double nan = Double.NaN;
+        assertEquals(2d, min.evaluate(new double[]{nan, 2d, 3d}), 0);
+        assertEquals(1d, min.evaluate(new double[]{1d, nan, 3d}), 0);
+        assertEquals(1d, min.evaluate(new double[]{1d, 2d, nan}), 0);
+        assertTrue(Double.isNaN(min.evaluate(new double[]{nan, nan, nan})));
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/stat/descriptive/rank/PercentileTest.java b/src/test/java/org/apache/commons/math/stat/descriptive/rank/PercentileTest.java
new file mode 100644
index 0000000..0f8624a
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/descriptive/rank/PercentileTest.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.stat.descriptive.rank;
+
+import org.apache.commons.math.stat.descriptive.UnivariateStatistic;
+import org.apache.commons.math.stat.descriptive.UnivariateStatisticAbstractTest;
+
+/**
+ * Test cases for the {@link UnivariateStatistic} class.
+ * @version $Revision: 902201 $ $Date: 2010-01-22 19:18:16 +0100 (ven. 22 janv. 2010) $
+ */
+public class PercentileTest extends UnivariateStatisticAbstractTest{
+
+    protected Percentile stat;
+
+    /**
+     * @param name
+     */
+    public PercentileTest(String name) {
+        super(name);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public UnivariateStatistic getUnivariateStatistic() {
+        return new Percentile(95.0);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public double expectedValue() {
+        return this.percentile95;
+    }
+
+    public void testHighPercentile(){
+        double[] d = new double[]{1, 2, 3};
+        Percentile p = new Percentile(75);
+        assertEquals(3.0, p.evaluate(d), 1.0e-5);
+    }
+
+    public void testPercentile() {
+        double[] d = new double[] {1, 3, 2, 4};
+        Percentile p = new Percentile(30);
+        assertEquals(1.5, p.evaluate(d), 1.0e-5);
+        p.setQuantile(25);
+        assertEquals(1.25, p.evaluate(d), 1.0e-5);
+        p.setQuantile(75);
+        assertEquals(3.75, p.evaluate(d), 1.0e-5);
+        p.setQuantile(50);
+        assertEquals(2.5, p.evaluate(d), 1.0e-5);
+
+        // invalid percentiles
+        try {
+            p.evaluate(d, 0, d.length, -1.0);
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // success
+        }
+        try {
+            p.evaluate(d, 0, d.length, 101.0);
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // success
+        }
+    }
+
+    public void testNISTExample() {
+        double[] d = new double[] {95.1772, 95.1567, 95.1937, 95.1959,
+                95.1442, 95.0610,  95.1591, 95.1195, 95.1772, 95.0925, 95.1990, 95.1682
+        };
+        Percentile p = new Percentile(90);
+        assertEquals(95.1981, p.evaluate(d), 1.0e-4);
+        assertEquals(95.1990, p.evaluate(d,0,d.length, 100d), 0);
+    }
+
+    public void test5() {
+        Percentile percentile = new Percentile(5);
+        assertEquals(this.percentile5, percentile.evaluate(testArray), getTolerance());
+    }
+
+    public void testNullEmpty() {
+        Percentile percentile = new Percentile(50);
+        double[] nullArray = null;
+        double[] emptyArray = new double[] {};
+        try {
+            percentile.evaluate(nullArray);
+            fail("Expecting IllegalArgumentException for null array");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        assertTrue(Double.isNaN(percentile.evaluate(emptyArray)));
+    }
+
+    public void testSingleton() {
+        Percentile percentile = new Percentile(50);
+        double[] singletonArray = new double[] {1d};
+        assertEquals(1d, percentile.evaluate(singletonArray), 0);
+        assertEquals(1d, percentile.evaluate(singletonArray, 0, 1), 0);
+        assertEquals(1d, percentile.evaluate(singletonArray, 0, 1, 5), 0);
+        assertEquals(1d, percentile.evaluate(singletonArray, 0, 1, 100), 0);
+        assertTrue(Double.isNaN(percentile.evaluate(singletonArray, 0, 0)));
+    }
+
+    public void testSpecialValues() {
+        Percentile percentile = new Percentile(50);
+        double[] specialValues = new double[] {0d, 1d, 2d, 3d, 4d,  Double.NaN};
+        assertEquals(2.5d, percentile.evaluate(specialValues), 0);
+        specialValues =  new double[] {Double.NEGATIVE_INFINITY, 1d, 2d, 3d,
+                Double.NaN, Double.POSITIVE_INFINITY};
+        assertEquals(2.5d, percentile.evaluate(specialValues), 0);
+        specialValues = new double[] {1d, 1d, Double.POSITIVE_INFINITY,
+                Double.POSITIVE_INFINITY};
+        assertTrue(Double.isInfinite(percentile.evaluate(specialValues)));
+        specialValues = new double[] {1d, 1d, Double.NaN,
+                Double.NaN};
+        assertTrue(Double.isNaN(percentile.evaluate(specialValues)));
+        specialValues = new double[] {1d, 1d, Double.NEGATIVE_INFINITY,
+                Double.NEGATIVE_INFINITY};
+        // Interpolation results in NEGATIVE_INFINITY + POSITIVE_INFINITY
+        assertTrue(Double.isNaN(percentile.evaluate(specialValues)));
+    }
+
+    public void testSetQuantile() {
+        Percentile percentile = new Percentile(10);
+        percentile.setQuantile(100); // OK
+        assertEquals(100, percentile.getQuantile(), 0);
+        try {
+            percentile.setQuantile(0);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            new Percentile(0);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/stat/descriptive/summary/ProductTest.java b/src/test/java/org/apache/commons/math/stat/descriptive/summary/ProductTest.java
new file mode 100644
index 0000000..4da006c
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/descriptive/summary/ProductTest.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.stat.descriptive.summary;
+
+import org.apache.commons.math.stat.descriptive.StorelessUnivariateStatisticAbstractTest;
+import org.apache.commons.math.stat.descriptive.UnivariateStatistic;
+
+/**
+ * Test cases for the {@link UnivariateStatistic} class.
+ * @version $Revision: 902201 $ $Date: 2010-01-22 19:18:16 +0100 (ven. 22 janv. 2010) $
+ */
+public class ProductTest extends StorelessUnivariateStatisticAbstractTest{
+
+    protected Product stat;
+
+    /**
+     * @param name
+     */
+    public ProductTest(String name) {
+        super(name);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public UnivariateStatistic getUnivariateStatistic() {
+        return new Product();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public double getTolerance() {
+        return 10E8;    //sic -- big absolute error due to only 15 digits of accuracy in double
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public double expectedValue() {
+        return this.product;
+    }
+
+    /**Expected value for  the testArray defined in UnivariateStatisticAbstractTest */
+    public double expectedWeightedValue() {
+        return this.weightedProduct;
+    }
+
+    public void testSpecialValues() {
+        Product product = new Product();
+        assertTrue(Double.isNaN(product.getResult()));
+        product.increment(1);
+        assertEquals(1, product.getResult(), 0);
+        product.increment(Double.POSITIVE_INFINITY);
+        assertEquals(Double.POSITIVE_INFINITY, product.getResult(), 0);
+        product.increment(Double.NEGATIVE_INFINITY);
+        assertEquals(Double.NEGATIVE_INFINITY, product.getResult(), 0);
+        product.increment(Double.NaN);
+        assertTrue(Double.isNaN(product.getResult()));
+        product.increment(1);
+        assertTrue(Double.isNaN(product.getResult()));
+    }
+
+    public void testWeightedProduct() {
+        Product product = new Product();
+        assertEquals(expectedWeightedValue(), product.evaluate(testArray, testWeightsArray, 0, testArray.length),getTolerance());
+        assertEquals(expectedValue(), product.evaluate(testArray, unitWeightsArray, 0, testArray.length), getTolerance());
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/stat/descriptive/summary/SumLogTest.java b/src/test/java/org/apache/commons/math/stat/descriptive/summary/SumLogTest.java
new file mode 100644
index 0000000..567388d
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/descriptive/summary/SumLogTest.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.stat.descriptive.summary;
+
+import org.apache.commons.math.stat.descriptive.StorelessUnivariateStatisticAbstractTest;
+import org.apache.commons.math.stat.descriptive.UnivariateStatistic;
+
+/**
+ * Test cases for the {@link UnivariateStatistic} class.
+ * @version $Revision: 902201 $ $Date: 2010-01-22 19:18:16 +0100 (ven. 22 janv. 2010) $
+ */
+public class SumLogTest extends StorelessUnivariateStatisticAbstractTest{
+
+    protected SumOfLogs stat;
+
+    /**
+     * @param name
+     */
+    public SumLogTest(String name) {
+        super(name);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public UnivariateStatistic getUnivariateStatistic() {
+        return new SumOfLogs();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public double expectedValue() {
+        return this.sumLog;
+    }
+
+    public void testSpecialValues() {
+        SumOfLogs sum = new SumOfLogs();
+        // empty
+        assertTrue(Double.isNaN(sum.getResult()));
+
+        // finite data
+        sum.increment(1d);
+        assertFalse(Double.isNaN(sum.getResult()));
+
+        // add negative infinity
+        sum.increment(0d);
+        assertEquals(Double.NEGATIVE_INFINITY, sum.getResult(), 0);
+
+        // add positive infinity -- should make NaN
+        sum.increment(Double.POSITIVE_INFINITY);
+        assertTrue(Double.isNaN(sum.getResult()));
+
+        // clear
+        sum.clear();
+        assertTrue(Double.isNaN(sum.getResult()));
+
+        // positive infinity by itself
+        sum.increment(Double.POSITIVE_INFINITY);
+        assertEquals(Double.POSITIVE_INFINITY, sum.getResult(), 0);
+
+        // negative value -- should make NaN
+        sum.increment(-2d);
+        assertTrue(Double.isNaN(sum.getResult()));
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/stat/descriptive/summary/SumSqTest.java b/src/test/java/org/apache/commons/math/stat/descriptive/summary/SumSqTest.java
new file mode 100644
index 0000000..9b14e47
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/descriptive/summary/SumSqTest.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.stat.descriptive.summary;
+
+import org.apache.commons.math.stat.descriptive.StorelessUnivariateStatisticAbstractTest;
+import org.apache.commons.math.stat.descriptive.UnivariateStatistic;
+
+/**
+ * Test cases for the {@link SumOfSquares} class.
+ *
+ * @version $Revision: 902201 $ $Date: 2010-01-22 19:18:16 +0100 (ven. 22 janv. 2010) $
+ */
+public class SumSqTest extends StorelessUnivariateStatisticAbstractTest{
+
+    protected SumOfSquares stat;
+
+    /**
+     * @param name
+     */
+    public SumSqTest(String name) {
+        super(name);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public UnivariateStatistic getUnivariateStatistic() {
+        return new SumOfSquares();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public double expectedValue() {
+        return this.sumSq;
+    }
+
+    public void testSpecialValues() {
+        SumOfSquares sumSq = new SumOfSquares();
+        assertTrue(Double.isNaN(sumSq.getResult()));
+        sumSq.increment(2d);
+        assertEquals(4d, sumSq.getResult(), 0);
+        sumSq.increment(Double.POSITIVE_INFINITY);
+        assertEquals(Double.POSITIVE_INFINITY, sumSq.getResult(), 0);
+        sumSq.increment(Double.NEGATIVE_INFINITY);
+        assertEquals(Double.POSITIVE_INFINITY, sumSq.getResult(), 0);
+        sumSq.increment(Double.NaN);
+        assertTrue(Double.isNaN(sumSq.getResult()));
+        sumSq.increment(1);
+        assertTrue(Double.isNaN(sumSq.getResult()));
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/stat/descriptive/summary/SumTest.java b/src/test/java/org/apache/commons/math/stat/descriptive/summary/SumTest.java
new file mode 100644
index 0000000..07327bb
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/descriptive/summary/SumTest.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.stat.descriptive.summary;
+
+import org.apache.commons.math.stat.descriptive.StorelessUnivariateStatisticAbstractTest;
+import org.apache.commons.math.stat.descriptive.UnivariateStatistic;
+
+/**
+ * Test cases for the {@link Sum} class.
+ * @version $Revision: 902201 $ $Date: 2010-01-22 19:18:16 +0100 (ven. 22 janv. 2010) $
+ */
+public class SumTest extends StorelessUnivariateStatisticAbstractTest{
+
+    protected Sum stat;
+
+    /**
+     * @param name
+     */
+    public SumTest(String name) {
+        super(name);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public UnivariateStatistic getUnivariateStatistic() {
+        return new Sum();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public double expectedValue() {
+        return this.sum;
+    }
+
+    /**Expected value for  the testArray defined in UnivariateStatisticAbstractTest */
+    public double expectedWeightedValue() {
+        return this.weightedSum;
+    }
+
+    public void testSpecialValues() {
+        Sum sum = new Sum();
+        assertTrue(Double.isNaN(sum.getResult()));
+        sum.increment(1);
+        assertEquals(1, sum.getResult(), 0);
+        sum.increment(Double.POSITIVE_INFINITY);
+        assertEquals(Double.POSITIVE_INFINITY, sum.getResult(), 0);
+        sum.increment(Double.NEGATIVE_INFINITY);
+        assertTrue(Double.isNaN(sum.getResult()));
+        sum.increment(1);
+        assertTrue(Double.isNaN(sum.getResult()));
+    }
+
+    public void testWeightedSum() {
+        Sum sum = new Sum();
+        assertEquals(expectedWeightedValue(), sum.evaluate(testArray, testWeightsArray, 0, testArray.length), getTolerance());
+        assertEquals(expectedValue(), sum.evaluate(testArray, unitWeightsArray, 0, testArray.length), getTolerance());
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/stat/inference/ChiSquareFactoryTest.java b/src/test/java/org/apache/commons/math/stat/inference/ChiSquareFactoryTest.java
new file mode 100644
index 0000000..54bc18d
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/inference/ChiSquareFactoryTest.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.stat.inference;
+
+/**
+ * Test cases for the ChiSquareTestFactory.
+ *
+ * @version $Revision: 902201 $ $Date: 2010-01-22 19:18:16 +0100 (ven. 22 janv. 2010) $
+ */
+
+public class ChiSquareFactoryTest extends ChiSquareTestTest {
+
+    public ChiSquareFactoryTest(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        testStatistic = TestUtils.getUnknownDistributionChiSquareTest();
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/stat/inference/ChiSquareTestTest.java b/src/test/java/org/apache/commons/math/stat/inference/ChiSquareTestTest.java
new file mode 100644
index 0000000..776248d
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/inference/ChiSquareTestTest.java
@@ -0,0 +1,251 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.stat.inference;
+
+import junit.framework.TestCase;
+
+/**
+ * Test cases for the ChiSquareTestImpl class.
+ *
+ * @version $Revision: 902201 $ $Date: 2010-01-22 19:18:16 +0100 (ven. 22 janv. 2010) $
+ */
+
+public class ChiSquareTestTest extends TestCase {
+
+    protected UnknownDistributionChiSquareTest testStatistic = new ChiSquareTestImpl();
+
+    public ChiSquareTestTest(String name) {
+        super(name);
+    }
+
+    public void testChiSquare() throws Exception {
+
+        // Target values computed using R version 1.8.1
+        // Some assembly required ;-)
+        //      Use sum((obs - exp)^2/exp) for the chi-square statistic and
+        //      1 - pchisq(sum((obs - exp)^2/exp), length(obs) - 1) for the p-value
+
+        long[] observed = {10, 9, 11};
+        double[] expected = {10, 10, 10};
+        assertEquals("chi-square statistic", 0.2,  testStatistic.chiSquare(expected, observed), 10E-12);
+        assertEquals("chi-square p-value", 0.904837418036, testStatistic.chiSquareTest(expected, observed), 1E-10);
+
+        long[] observed1 = { 500, 623, 72, 70, 31 };
+        double[] expected1 = { 485, 541, 82, 61, 37 };
+        assertEquals( "chi-square test statistic", 9.023307936427388, testStatistic.chiSquare(expected1, observed1), 1E-10);
+        assertEquals("chi-square p-value", 0.06051952647453607, testStatistic.chiSquareTest(expected1, observed1), 1E-9);
+        assertTrue("chi-square test reject", testStatistic.chiSquareTest(expected1, observed1, 0.08));
+        assertTrue("chi-square test accept", !testStatistic.chiSquareTest(expected1, observed1, 0.05));
+
+        try {
+            testStatistic.chiSquareTest(expected1, observed1, 95);
+            fail("alpha out of range, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        long[] tooShortObs = { 0 };
+        double[] tooShortEx = { 1 };
+        try {
+            testStatistic.chiSquare(tooShortEx, tooShortObs);
+            fail("arguments too short, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        // unmatched arrays
+        long[] unMatchedObs = { 0, 1, 2, 3 };
+        double[] unMatchedEx = { 1, 1, 2 };
+        try {
+            testStatistic.chiSquare(unMatchedEx, unMatchedObs);
+            fail("arrays have different lengths, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        // 0 expected count
+        expected[0] = 0;
+        try {
+            testStatistic.chiSquareTest(expected, observed, .01);
+            fail("bad expected count, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        // negative observed count
+        expected[0] = 1;
+        observed[0] = -1;
+        try {
+            testStatistic.chiSquareTest(expected, observed, .01);
+            fail("bad expected count, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+    }
+
+    public void testChiSquareIndependence() throws Exception {
+
+        // Target values computed using R version 1.8.1
+
+        long[][] counts = { {40, 22, 43}, {91, 21, 28}, {60, 10, 22}};
+        assertEquals( "chi-square test statistic", 22.709027688, testStatistic.chiSquare(counts), 1E-9);
+        assertEquals("chi-square p-value", 0.000144751460134, testStatistic.chiSquareTest(counts), 1E-9);
+        assertTrue("chi-square test reject", testStatistic.chiSquareTest(counts, 0.0002));
+        assertTrue("chi-square test accept", !testStatistic.chiSquareTest(counts, 0.0001));
+
+        long[][] counts2 = {{10, 15}, {30, 40}, {60, 90} };
+        assertEquals( "chi-square test statistic", 0.168965517241, testStatistic.chiSquare(counts2), 1E-9);
+        assertEquals("chi-square p-value",0.918987499852, testStatistic.chiSquareTest(counts2), 1E-9);
+        assertTrue("chi-square test accept", !testStatistic.chiSquareTest(counts2, 0.1));
+
+        // ragged input array
+        long[][] counts3 = { {40, 22, 43}, {91, 21, 28}, {60, 10}};
+        try {
+            testStatistic.chiSquare(counts3);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        // insufficient data
+        long[][] counts4 = {{40, 22, 43}};
+        try {
+            testStatistic.chiSquare(counts4);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        long[][] counts5 = {{40}, {40}, {30}, {10}};
+        try {
+            testStatistic.chiSquare(counts5);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        // negative counts
+        long[][] counts6 = {{10, -2}, {30, 40}, {60, 90} };
+        try {
+            testStatistic.chiSquare(counts6);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        // bad alpha
+        try {
+            testStatistic.chiSquareTest(counts, 0);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+
+    public void testChiSquareLargeTestStatistic() throws Exception {
+        double[] exp = new double[] {
+            3389119.5, 649136.6, 285745.4, 25357364.76, 11291189.78, 543628.0,
+            232921.0, 437665.75
+        };
+
+        long[] obs = new long[] {
+            2372383, 584222, 257170, 17750155, 7903832, 489265, 209628, 393899
+        };
+        org.apache.commons.math.stat.inference.ChiSquareTestImpl csti =
+            new org.apache.commons.math.stat.inference.ChiSquareTestImpl();
+        double cst = csti.chiSquareTest(exp, obs);
+        assertEquals("chi-square p-value", 0.0, cst, 1E-3);
+        assertEquals( "chi-square test statistic",
+                114875.90421929007, testStatistic.chiSquare(exp, obs), 1E-9);
+    }
+
+    /** Contingency table containing zeros - PR # 32531 */
+    public void testChiSquareZeroCount() throws Exception {
+        // Target values computed using R version 1.8.1
+        long[][] counts = { {40, 0, 4}, {91, 1, 2}, {60, 2, 0}};
+        assertEquals( "chi-square test statistic", 9.67444662263,
+                testStatistic.chiSquare(counts), 1E-9);
+        assertEquals("chi-square p-value", 0.0462835770603,
+                testStatistic.chiSquareTest(counts), 1E-9);
+    }
+
+    /** Target values verified using DATAPLOT version 2006.3 */
+    public void testChiSquareDataSetsComparisonEqualCounts()
+    throws Exception {
+        long[] observed1 = {10, 12, 12, 10};
+        long[] observed2 = {5, 15, 14, 10};
+        assertEquals("chi-square p value", 0.541096,
+                testStatistic.chiSquareTestDataSetsComparison(
+                observed1, observed2), 1E-6);
+        assertEquals("chi-square test statistic", 2.153846,
+                testStatistic.chiSquareDataSetsComparison(
+                observed1, observed2), 1E-6);
+        assertFalse("chi-square test result",
+                testStatistic.chiSquareTestDataSetsComparison(
+                observed1, observed2, 0.4));
+    }
+
+    /** Target values verified using DATAPLOT version 2006.3 */
+    public void testChiSquareDataSetsComparisonUnEqualCounts()
+    throws Exception {
+        long[] observed1 = {10, 12, 12, 10, 15};
+        long[] observed2 = {15, 10, 10, 15, 5};
+        assertEquals("chi-square p value", 0.124115,
+                testStatistic.chiSquareTestDataSetsComparison(
+                observed1, observed2), 1E-6);
+        assertEquals("chi-square test statistic", 7.232189,
+                testStatistic.chiSquareDataSetsComparison(
+                observed1, observed2), 1E-6);
+        assertTrue("chi-square test result",
+                testStatistic.chiSquareTestDataSetsComparison(
+                observed1, observed2, 0.13));
+        assertFalse("chi-square test result",
+                testStatistic.chiSquareTestDataSetsComparison(
+                observed1, observed2, 0.12));
+    }
+
+    public void testChiSquareDataSetsComparisonBadCounts()
+    throws Exception {
+        long[] observed1 = {10, -1, 12, 10, 15};
+        long[] observed2 = {15, 10, 10, 15, 5};
+        try {
+            testStatistic.chiSquareTestDataSetsComparison(
+                    observed1, observed2);
+            fail("Expecting IllegalArgumentException - negative count");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        long[] observed3 = {10, 0, 12, 10, 15};
+        long[] observed4 = {15, 0, 10, 15, 5};
+        try {
+            testStatistic.chiSquareTestDataSetsComparison(
+                    observed3, observed4);
+            fail("Expecting IllegalArgumentException - double 0's");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        long[] observed5 = {10, 10, 12, 10, 15};
+        long[] observed6 = {0, 0, 0, 0, 0};
+        try {
+            testStatistic.chiSquareTestDataSetsComparison(
+                    observed5, observed6);
+            fail("Expecting IllegalArgumentException - vanishing counts");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/stat/inference/OneWayAnovaTest.java b/src/test/java/org/apache/commons/math/stat/inference/OneWayAnovaTest.java
new file mode 100644
index 0000000..5daaff0
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/inference/OneWayAnovaTest.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.inference;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+/**
+ * Test cases for the OneWayAnovaImpl class.
+ *
+ * @version $Revision: 902201 $ $Date: 2010-01-22 19:18:16 +0100 (ven. 22 janv. 2010) $
+ */
+
+public class OneWayAnovaTest extends TestCase {
+
+    protected OneWayAnova testStatistic = new OneWayAnovaImpl();
+
+    private double[] emptyArray = {};
+
+    private double[] classA =
+            {93.0, 103.0, 95.0, 101.0, 91.0, 105.0, 96.0, 94.0, 101.0 };
+    private double[] classB =
+            {99.0, 92.0, 102.0, 100.0, 102.0, 89.0 };
+    private double[] classC =
+            {110.0, 115.0, 111.0, 117.0, 128.0, 117.0 };
+
+    public OneWayAnovaTest(String name) {
+        super(name);
+    }
+
+    public void testAnovaFValue() throws Exception {
+        // Target comparison values computed using R version 2.6.0 (Linux version)
+        List<double[]> threeClasses = new ArrayList<double[]>();
+        threeClasses.add(classA);
+        threeClasses.add(classB);
+        threeClasses.add(classC);
+
+        assertEquals("ANOVA F-value",  24.67361709460624,
+                 testStatistic.anovaFValue(threeClasses), 1E-12);
+
+        List<double[]> twoClasses = new ArrayList<double[]>();
+        twoClasses.add(classA);
+        twoClasses.add(classB);
+
+        assertEquals("ANOVA F-value",  0.0150579150579,
+                 testStatistic.anovaFValue(twoClasses), 1E-12);
+
+        List<double[]> emptyContents = new ArrayList<double[]>();
+        emptyContents.add(emptyArray);
+        emptyContents.add(classC);
+        try {
+            testStatistic.anovaFValue(emptyContents);
+            fail("empty array for key classX, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        List<double[]> tooFew = new ArrayList<double[]>();
+        tooFew.add(classA);
+        try {
+            testStatistic.anovaFValue(tooFew);
+            fail("less than two classes, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+
+
+    public void testAnovaPValue() throws Exception {
+        // Target comparison values computed using R version 2.6.0 (Linux version)
+        List<double[]> threeClasses = new ArrayList<double[]>();
+        threeClasses.add(classA);
+        threeClasses.add(classB);
+        threeClasses.add(classC);
+
+        assertEquals("ANOVA P-value", 6.959446E-06,
+                 testStatistic.anovaPValue(threeClasses), 1E-12);
+
+        List<double[]> twoClasses = new ArrayList<double[]>();
+        twoClasses.add(classA);
+        twoClasses.add(classB);
+
+        assertEquals("ANOVA P-value",  0.904212960464,
+                 testStatistic.anovaPValue(twoClasses), 1E-12);
+
+    }
+
+    public void testAnovaTest() throws Exception {
+        // Target comparison values computed using R version 2.3.1 (Linux version)
+        List<double[]> threeClasses = new ArrayList<double[]>();
+        threeClasses.add(classA);
+        threeClasses.add(classB);
+        threeClasses.add(classC);
+
+        assertTrue("ANOVA Test P<0.01", testStatistic.anovaTest(threeClasses, 0.01));
+
+        List<double[]> twoClasses = new ArrayList<double[]>();
+        twoClasses.add(classA);
+        twoClasses.add(classB);
+
+        assertFalse("ANOVA Test P>0.01", testStatistic.anovaTest(twoClasses, 0.01));
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/stat/inference/TTestFactoryTest.java b/src/test/java/org/apache/commons/math/stat/inference/TTestFactoryTest.java
new file mode 100644
index 0000000..a1fdeea
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/inference/TTestFactoryTest.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.stat.inference;
+
+/**
+ * Test cases for the TTestTestFactory.
+ *
+ * @version $Revision: 902201 $ $Date: 2010-01-22 19:18:16 +0100 (ven. 22 janv. 2010) $
+ */
+
+public class TTestFactoryTest extends TTestTest {
+
+    public TTestFactoryTest(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setUp() {
+        super.setUp();
+        testStatistic = TestUtils.getTTest();
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/stat/inference/TTestTest.java b/src/test/java/org/apache/commons/math/stat/inference/TTestTest.java
new file mode 100644
index 0000000..1bf9c79
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/inference/TTestTest.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.stat.inference;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.math.stat.descriptive.SummaryStatistics;
+/**
+ * Test cases for the TTestImpl class.
+ *
+ * @version $Revision: 902201 $ $Date: 2010-01-22 19:18:16 +0100 (ven. 22 janv. 2010) $
+ */
+
+public class TTestTest extends TestCase {
+
+    protected TTest testStatistic = new TTestImpl();
+
+    private double[] tooShortObs = { 1.0 };
+    private double[] emptyObs = {};
+    private SummaryStatistics emptyStats = new SummaryStatistics();
+   SummaryStatistics tooShortStats = null;
+
+    public TTestTest(String name) {
+        super(name);
+    }
+
+    @Override
+    public void setUp() {
+        tooShortStats = new SummaryStatistics();
+        tooShortStats.addValue(0d);
+    }
+
+    public void testOneSampleT() throws Exception {
+        double[] observed =
+            {93.0, 103.0, 95.0, 101.0, 91.0, 105.0, 96.0, 94.0, 101.0,  88.0, 98.0, 94.0, 101.0, 92.0, 95.0 };
+        double mu = 100.0;
+        SummaryStatistics sampleStats = null;
+        sampleStats = new SummaryStatistics();
+        for (int i = 0; i < observed.length; i++) {
+            sampleStats.addValue(observed[i]);
+        }
+
+        // Target comparison values computed using R version 1.8.1 (Linux version)
+        assertEquals("t statistic",  -2.81976445346,
+                testStatistic.t(mu, observed), 10E-10);
+        assertEquals("t statistic",  -2.81976445346,
+                testStatistic.t(mu, sampleStats), 10E-10);
+        assertEquals("p value", 0.0136390585873,
+                testStatistic.tTest(mu, observed), 10E-10);
+        assertEquals("p value", 0.0136390585873,
+                testStatistic.tTest(mu, sampleStats), 10E-10);
+
+        try {
+            testStatistic.t(mu, (double[]) null);
+            fail("arguments too short, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        try {
+            testStatistic.t(mu, (SummaryStatistics) null);
+            fail("arguments too short, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        try {
+            testStatistic.t(mu, emptyObs);
+            fail("arguments too short, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        try {
+            testStatistic.t(mu, emptyStats);
+            fail("arguments too short, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        try {
+            testStatistic.t(mu, tooShortObs);
+            fail("insufficient data to compute t statistic, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            testStatistic.tTest(mu, tooShortObs);
+            fail("insufficient data to perform t test, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+           // expected
+        }
+
+        try {
+            testStatistic.t(mu, tooShortStats);
+            fail("insufficient data to compute t statistic, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            testStatistic.tTest(mu, tooShortStats);
+            fail("insufficient data to perform t test, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+
+    public void testOneSampleTTest() throws Exception {
+        double[] oneSidedP =
+            {2d, 0d, 6d, 6d, 3d, 3d, 2d, 3d, -6d, 6d, 6d, 6d, 3d, 0d, 1d, 1d, 0d, 2d, 3d, 3d };
+        SummaryStatistics oneSidedPStats = new SummaryStatistics();
+        for (int i = 0; i < oneSidedP.length; i++) {
+            oneSidedPStats.addValue(oneSidedP[i]);
+        }
+        // Target comparison values computed using R version 1.8.1 (Linux version)
+        assertEquals("one sample t stat", 3.86485535541,
+                testStatistic.t(0d, oneSidedP), 10E-10);
+        assertEquals("one sample t stat", 3.86485535541,
+                testStatistic.t(0d, oneSidedPStats),1E-10);
+        assertEquals("one sample p value", 0.000521637019637,
+                testStatistic.tTest(0d, oneSidedP) / 2d, 10E-10);
+        assertEquals("one sample p value", 0.000521637019637,
+                testStatistic.tTest(0d, oneSidedPStats) / 2d, 10E-5);
+        assertTrue("one sample t-test reject", testStatistic.tTest(0d, oneSidedP, 0.01));
+        assertTrue("one sample t-test reject", testStatistic.tTest(0d, oneSidedPStats, 0.01));
+        assertTrue("one sample t-test accept", !testStatistic.tTest(0d, oneSidedP, 0.0001));
+        assertTrue("one sample t-test accept", !testStatistic.tTest(0d, oneSidedPStats, 0.0001));
+
+        try {
+            testStatistic.tTest(0d, oneSidedP, 95);
+            fail("alpha out of range, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        try {
+            testStatistic.tTest(0d, oneSidedPStats, 95);
+            fail("alpha out of range, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+    }
+
+    public void testTwoSampleTHeterscedastic() throws Exception {
+        double[] sample1 = { 7d, -4d, 18d, 17d, -3d, -5d, 1d, 10d, 11d, -2d };
+        double[] sample2 = { -1d, 12d, -1d, -3d, 3d, -5d, 5d, 2d, -11d, -1d, -3d };
+        SummaryStatistics sampleStats1 = new SummaryStatistics();
+        for (int i = 0; i < sample1.length; i++) {
+            sampleStats1.addValue(sample1[i]);
+        }
+        SummaryStatistics sampleStats2 = new SummaryStatistics();
+        for (int i = 0; i < sample2.length; i++) {
+            sampleStats2.addValue(sample2[i]);
+        }
+
+        // Target comparison values computed using R version 1.8.1 (Linux version)
+        assertEquals("two sample heteroscedastic t stat", 1.60371728768,
+                testStatistic.t(sample1, sample2), 1E-10);
+        assertEquals("two sample heteroscedastic t stat", 1.60371728768,
+                testStatistic.t(sampleStats1, sampleStats2), 1E-10);
+        assertEquals("two sample heteroscedastic p value", 0.128839369622,
+                testStatistic.tTest(sample1, sample2), 1E-10);
+        assertEquals("two sample heteroscedastic p value", 0.128839369622,
+                testStatistic.tTest(sampleStats1, sampleStats2), 1E-10);
+        assertTrue("two sample heteroscedastic t-test reject",
+                testStatistic.tTest(sample1, sample2, 0.2));
+        assertTrue("two sample heteroscedastic t-test reject",
+                testStatistic.tTest(sampleStats1, sampleStats2, 0.2));
+        assertTrue("two sample heteroscedastic t-test accept",
+                !testStatistic.tTest(sample1, sample2, 0.1));
+        assertTrue("two sample heteroscedastic t-test accept",
+                !testStatistic.tTest(sampleStats1, sampleStats2, 0.1));
+
+        try {
+            testStatistic.tTest(sample1, sample2, .95);
+            fail("alpha out of range, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        try {
+            testStatistic.tTest(sampleStats1, sampleStats2, .95);
+            fail("alpha out of range, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        try {
+            testStatistic.tTest(sample1, tooShortObs, .01);
+            fail("insufficient data, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        try {
+            testStatistic.tTest(sampleStats1, tooShortStats, .01);
+            fail("insufficient data, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        try {
+            testStatistic.tTest(sample1, tooShortObs);
+            fail("insufficient data, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+           // expected
+        }
+
+        try {
+            testStatistic.tTest(sampleStats1, tooShortStats);
+            fail("insufficient data, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        try {
+            testStatistic.t(sample1, tooShortObs);
+            fail("insufficient data, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        try {
+            testStatistic.t(sampleStats1, tooShortStats);
+            fail("insufficient data, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+           // expected
+        }
+    }
+    public void testTwoSampleTHomoscedastic() throws Exception {
+        double[] sample1 ={2, 4, 6, 8, 10, 97};
+        double[] sample2 = {4, 6, 8, 10, 16};
+        SummaryStatistics sampleStats1 = new SummaryStatistics();
+        for (int i = 0; i < sample1.length; i++) {
+            sampleStats1.addValue(sample1[i]);
+        }
+        SummaryStatistics sampleStats2 = new SummaryStatistics();
+        for (int i = 0; i < sample2.length; i++) {
+            sampleStats2.addValue(sample2[i]);
+        }
+
+        // Target comparison values computed using R version 1.8.1 (Linux version)
+        assertEquals("two sample homoscedastic t stat", 0.73096310086,
+              testStatistic.homoscedasticT(sample1, sample2), 10E-11);
+        assertEquals("two sample homoscedastic p value", 0.4833963785,
+                testStatistic.homoscedasticTTest(sampleStats1, sampleStats2), 1E-10);
+        assertTrue("two sample homoscedastic t-test reject",
+                testStatistic.homoscedasticTTest(sample1, sample2, 0.49));
+        assertTrue("two sample homoscedastic t-test accept",
+                !testStatistic.homoscedasticTTest(sample1, sample2, 0.48));
+    }
+
+    public void testSmallSamples() throws Exception {
+        double[] sample1 = {1d, 3d};
+        double[] sample2 = {4d, 5d};
+
+        // Target values computed using R, version 1.8.1 (linux version)
+        assertEquals(-2.2360679775, testStatistic.t(sample1, sample2),
+                1E-10);
+        assertEquals(0.198727388935, testStatistic.tTest(sample1, sample2),
+                1E-10);
+    }
+
+    public void testPaired() throws Exception {
+        double[] sample1 = {1d, 3d, 5d, 7d};
+        double[] sample2 = {0d, 6d, 11d, 2d};
+        double[] sample3 = {5d, 7d, 8d, 10d};
+
+        // Target values computed using R, version 1.8.1 (linux version)
+        assertEquals(-0.3133, testStatistic.pairedT(sample1, sample2), 1E-4);
+        assertEquals(0.774544295819, testStatistic.pairedTTest(sample1, sample2), 1E-10);
+        assertEquals(0.001208, testStatistic.pairedTTest(sample1, sample3), 1E-6);
+        assertFalse(testStatistic.pairedTTest(sample1, sample3, .001));
+        assertTrue(testStatistic.pairedTTest(sample1, sample3, .002));
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/stat/inference/TestUtilsTest.java b/src/test/java/org/apache/commons/math/stat/inference/TestUtilsTest.java
new file mode 100644
index 0000000..03c351d
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/inference/TestUtilsTest.java
@@ -0,0 +1,458 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.ArrayList;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.math.stat.descriptive.SummaryStatistics;
+/**
+ * Test cases for the TestUtils class.
+ *
+ * @version $Revision: 902201 $ $Date: 2010-01-22 19:18:16 +0100 (ven. 22 janv. 2010) $
+ */
+
+public class TestUtilsTest extends TestCase {
+
+    public TestUtilsTest(String name) {
+        super(name);
+    }
+
+    public void testChiSquare() throws Exception {
+
+        // Target values computed using R version 1.8.1
+        // Some assembly required ;-)
+        //      Use sum((obs - exp)^2/exp) for the chi-square statistic and
+        //      1 - pchisq(sum((obs - exp)^2/exp), length(obs) - 1) for the p-value
+
+        long[] observed = {10, 9, 11};
+        double[] expected = {10, 10, 10};
+        assertEquals("chi-square statistic", 0.2,  TestUtils.chiSquare(expected, observed), 10E-12);
+        assertEquals("chi-square p-value", 0.904837418036, TestUtils.chiSquareTest(expected, observed), 1E-10);
+
+        long[] observed1 = { 500, 623, 72, 70, 31 };
+        double[] expected1 = { 485, 541, 82, 61, 37 };
+        assertEquals( "chi-square test statistic", 9.023307936427388, TestUtils.chiSquare(expected1, observed1), 1E-10);
+        assertEquals("chi-square p-value", 0.06051952647453607, TestUtils.chiSquareTest(expected1, observed1), 1E-9);
+        assertTrue("chi-square test reject", TestUtils.chiSquareTest(expected1, observed1, 0.07));
+        assertTrue("chi-square test accept", !TestUtils.chiSquareTest(expected1, observed1, 0.05));
+
+        try {
+            TestUtils.chiSquareTest(expected1, observed1, 95);
+            fail("alpha out of range, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        long[] tooShortObs = { 0 };
+        double[] tooShortEx = { 1 };
+        try {
+            TestUtils.chiSquare(tooShortEx, tooShortObs);
+            fail("arguments too short, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        // unmatched arrays
+        long[] unMatchedObs = { 0, 1, 2, 3 };
+        double[] unMatchedEx = { 1, 1, 2 };
+        try {
+            TestUtils.chiSquare(unMatchedEx, unMatchedObs);
+            fail("arrays have different lengths, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        // 0 expected count
+        expected[0] = 0;
+        try {
+            TestUtils.chiSquareTest(expected, observed, .01);
+            fail("bad expected count, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        // negative observed count
+        expected[0] = 1;
+        observed[0] = -1;
+        try {
+            TestUtils.chiSquareTest(expected, observed, .01);
+            fail("bad expected count, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+    }
+
+    public void testChiSquareIndependence() throws Exception {
+
+        // Target values computed using R version 1.8.1
+
+        long[][] counts = { {40, 22, 43}, {91, 21, 28}, {60, 10, 22}};
+        assertEquals( "chi-square test statistic", 22.709027688, TestUtils.chiSquare(counts), 1E-9);
+        assertEquals("chi-square p-value", 0.000144751460134, TestUtils.chiSquareTest(counts), 1E-9);
+        assertTrue("chi-square test reject", TestUtils.chiSquareTest(counts, 0.0002));
+        assertTrue("chi-square test accept", !TestUtils.chiSquareTest(counts, 0.0001));
+
+        long[][] counts2 = {{10, 15}, {30, 40}, {60, 90} };
+        assertEquals( "chi-square test statistic", 0.168965517241, TestUtils.chiSquare(counts2), 1E-9);
+        assertEquals("chi-square p-value",0.918987499852, TestUtils.chiSquareTest(counts2), 1E-9);
+        assertTrue("chi-square test accept", !TestUtils.chiSquareTest(counts2, 0.1));
+
+        // ragged input array
+        long[][] counts3 = { {40, 22, 43}, {91, 21, 28}, {60, 10}};
+        try {
+            TestUtils.chiSquare(counts3);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        // insufficient data
+        long[][] counts4 = {{40, 22, 43}};
+        try {
+            TestUtils.chiSquare(counts4);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        long[][] counts5 = {{40}, {40}, {30}, {10}};
+        try {
+            TestUtils.chiSquare(counts5);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        // negative counts
+        long[][] counts6 = {{10, -2}, {30, 40}, {60, 90} };
+        try {
+            TestUtils.chiSquare(counts6);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        // bad alpha
+        try {
+            TestUtils.chiSquareTest(counts, 0);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+
+    public void testChiSquareLargeTestStatistic() throws Exception {
+        double[] exp = new double[] {
+                3389119.5, 649136.6, 285745.4, 25357364.76, 11291189.78, 543628.0,
+                232921.0, 437665.75
+        };
+
+        long[] obs = new long[] {
+                2372383, 584222, 257170, 17750155, 7903832, 489265, 209628, 393899
+        };
+        org.apache.commons.math.stat.inference.ChiSquareTestImpl csti =
+            new org.apache.commons.math.stat.inference.ChiSquareTestImpl();
+        double cst = csti.chiSquareTest(exp, obs);
+        assertEquals("chi-square p-value", 0.0, cst, 1E-3);
+        assertEquals( "chi-square test statistic",
+                114875.90421929007, TestUtils.chiSquare(exp, obs), 1E-9);
+    }
+
+    /** Contingency table containing zeros - PR # 32531 */
+    public void testChiSquareZeroCount() throws Exception {
+        // Target values computed using R version 1.8.1
+        long[][] counts = { {40, 0, 4}, {91, 1, 2}, {60, 2, 0}};
+        assertEquals( "chi-square test statistic", 9.67444662263,
+                TestUtils.chiSquare(counts), 1E-9);
+        assertEquals("chi-square p-value", 0.0462835770603,
+                TestUtils.chiSquareTest(counts), 1E-9);
+    }
+
+    private double[] tooShortObs = { 1.0 };
+    private double[] emptyObs = {};
+    private SummaryStatistics emptyStats = new SummaryStatistics();
+
+    public void testOneSampleT() throws Exception {
+        double[] observed =
+            {93.0, 103.0, 95.0, 101.0, 91.0, 105.0, 96.0, 94.0, 101.0,  88.0, 98.0, 94.0, 101.0, 92.0, 95.0 };
+        double mu = 100.0;
+        SummaryStatistics sampleStats = null;
+        sampleStats = new SummaryStatistics();
+        for (int i = 0; i < observed.length; i++) {
+            sampleStats.addValue(observed[i]);
+        }
+
+        // Target comparison values computed using R version 1.8.1 (Linux version)
+        assertEquals("t statistic",  -2.81976445346,
+                TestUtils.t(mu, observed), 10E-10);
+        assertEquals("t statistic",  -2.81976445346,
+                TestUtils.t(mu, sampleStats), 10E-10);
+        assertEquals("p value", 0.0136390585873,
+                TestUtils.tTest(mu, observed), 10E-10);
+        assertEquals("p value", 0.0136390585873,
+                TestUtils.tTest(mu, sampleStats), 10E-10);
+
+        try {
+            TestUtils.t(mu, (double[]) null);
+            fail("arguments too short, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        try {
+            TestUtils.t(mu, (SummaryStatistics) null);
+            fail("arguments too short, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        try {
+            TestUtils.t(mu, emptyObs);
+            fail("arguments too short, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        try {
+            TestUtils.t(mu, emptyStats);
+            fail("arguments too short, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        try {
+            TestUtils.t(mu, tooShortObs);
+            fail("insufficient data to compute t statistic, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            TestUtils.tTest(mu, tooShortObs);
+            fail("insufficient data to perform t test, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        try {
+            TestUtils.t(mu, (SummaryStatistics) null);
+            fail("insufficient data to compute t statistic, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            TestUtils.tTest(mu, (SummaryStatistics) null);
+            fail("insufficient data to perform t test, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+
+    public void testOneSampleTTest() throws Exception {
+        double[] oneSidedP =
+            {2d, 0d, 6d, 6d, 3d, 3d, 2d, 3d, -6d, 6d, 6d, 6d, 3d, 0d, 1d, 1d, 0d, 2d, 3d, 3d };
+        SummaryStatistics oneSidedPStats = new SummaryStatistics();
+        for (int i = 0; i < oneSidedP.length; i++) {
+            oneSidedPStats.addValue(oneSidedP[i]);
+        }
+        // Target comparison values computed using R version 1.8.1 (Linux version)
+        assertEquals("one sample t stat", 3.86485535541,
+                TestUtils.t(0d, oneSidedP), 10E-10);
+        assertEquals("one sample t stat", 3.86485535541,
+                TestUtils.t(0d, oneSidedPStats),1E-10);
+        assertEquals("one sample p value", 0.000521637019637,
+                TestUtils.tTest(0d, oneSidedP) / 2d, 10E-10);
+        assertEquals("one sample p value", 0.000521637019637,
+                TestUtils.tTest(0d, oneSidedPStats) / 2d, 10E-5);
+        assertTrue("one sample t-test reject", TestUtils.tTest(0d, oneSidedP, 0.01));
+        assertTrue("one sample t-test reject", TestUtils.tTest(0d, oneSidedPStats, 0.01));
+        assertTrue("one sample t-test accept", !TestUtils.tTest(0d, oneSidedP, 0.0001));
+        assertTrue("one sample t-test accept", !TestUtils.tTest(0d, oneSidedPStats, 0.0001));
+
+        try {
+            TestUtils.tTest(0d, oneSidedP, 95);
+            fail("alpha out of range, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        try {
+            TestUtils.tTest(0d, oneSidedPStats, 95);
+            fail("alpha out of range, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+    }
+
+    public void testTwoSampleTHeterscedastic() throws Exception {
+        double[] sample1 = { 7d, -4d, 18d, 17d, -3d, -5d, 1d, 10d, 11d, -2d };
+        double[] sample2 = { -1d, 12d, -1d, -3d, 3d, -5d, 5d, 2d, -11d, -1d, -3d };
+        SummaryStatistics sampleStats1 = new SummaryStatistics();
+        for (int i = 0; i < sample1.length; i++) {
+            sampleStats1.addValue(sample1[i]);
+        }
+        SummaryStatistics sampleStats2 = new SummaryStatistics();
+        for (int i = 0; i < sample2.length; i++) {
+            sampleStats2.addValue(sample2[i]);
+        }
+
+        // Target comparison values computed using R version 1.8.1 (Linux version)
+        assertEquals("two sample heteroscedastic t stat", 1.60371728768,
+                TestUtils.t(sample1, sample2), 1E-10);
+        assertEquals("two sample heteroscedastic t stat", 1.60371728768,
+                TestUtils.t(sampleStats1, sampleStats2), 1E-10);
+        assertEquals("two sample heteroscedastic p value", 0.128839369622,
+                TestUtils.tTest(sample1, sample2), 1E-10);
+        assertEquals("two sample heteroscedastic p value", 0.128839369622,
+                TestUtils.tTest(sampleStats1, sampleStats2), 1E-10);
+        assertTrue("two sample heteroscedastic t-test reject",
+                TestUtils.tTest(sample1, sample2, 0.2));
+        assertTrue("two sample heteroscedastic t-test reject",
+                TestUtils.tTest(sampleStats1, sampleStats2, 0.2));
+        assertTrue("two sample heteroscedastic t-test accept",
+                !TestUtils.tTest(sample1, sample2, 0.1));
+        assertTrue("two sample heteroscedastic t-test accept",
+                !TestUtils.tTest(sampleStats1, sampleStats2, 0.1));
+
+        try {
+            TestUtils.tTest(sample1, sample2, .95);
+            fail("alpha out of range, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        try {
+            TestUtils.tTest(sampleStats1, sampleStats2, .95);
+            fail("alpha out of range, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        try {
+            TestUtils.tTest(sample1, tooShortObs, .01);
+            fail("insufficient data, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        try {
+            TestUtils.tTest(sampleStats1, (SummaryStatistics) null, .01);
+            fail("insufficient data, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        try {
+            TestUtils.tTest(sample1, tooShortObs);
+            fail("insufficient data, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        try {
+            TestUtils.tTest(sampleStats1, (SummaryStatistics) null);
+            fail("insufficient data, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        try {
+            TestUtils.t(sample1, tooShortObs);
+            fail("insufficient data, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        try {
+            TestUtils.t(sampleStats1, (SummaryStatistics) null);
+            fail("insufficient data, IllegalArgumentException expected");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+    public void testTwoSampleTHomoscedastic() throws Exception {
+        double[] sample1 ={2, 4, 6, 8, 10, 97};
+        double[] sample2 = {4, 6, 8, 10, 16};
+        SummaryStatistics sampleStats1 = new SummaryStatistics();
+        for (int i = 0; i < sample1.length; i++) {
+            sampleStats1.addValue(sample1[i]);
+        }
+        SummaryStatistics sampleStats2 = new SummaryStatistics();
+        for (int i = 0; i < sample2.length; i++) {
+            sampleStats2.addValue(sample2[i]);
+        }
+
+        // Target comparison values computed using R version 1.8.1 (Linux version)
+        assertEquals("two sample homoscedastic t stat", 0.73096310086,
+                TestUtils.homoscedasticT(sample1, sample2), 10E-11);
+        assertEquals("two sample homoscedastic p value", 0.4833963785,
+                TestUtils.homoscedasticTTest(sampleStats1, sampleStats2), 1E-10);
+        assertTrue("two sample homoscedastic t-test reject",
+                TestUtils.homoscedasticTTest(sample1, sample2, 0.49));
+        assertTrue("two sample homoscedastic t-test accept",
+                !TestUtils.homoscedasticTTest(sample1, sample2, 0.48));
+    }
+
+    public void testSmallSamples() throws Exception {
+        double[] sample1 = {1d, 3d};
+        double[] sample2 = {4d, 5d};
+
+        // Target values computed using R, version 1.8.1 (linux version)
+        assertEquals(-2.2360679775, TestUtils.t(sample1, sample2),
+                1E-10);
+        assertEquals(0.198727388935, TestUtils.tTest(sample1, sample2),
+                1E-10);
+    }
+
+    public void testPaired() throws Exception {
+        double[] sample1 = {1d, 3d, 5d, 7d};
+        double[] sample2 = {0d, 6d, 11d, 2d};
+        double[] sample3 = {5d, 7d, 8d, 10d};
+
+        // Target values computed using R, version 1.8.1 (linux version)
+        assertEquals(-0.3133, TestUtils.pairedT(sample1, sample2), 1E-4);
+        assertEquals(0.774544295819, TestUtils.pairedTTest(sample1, sample2), 1E-10);
+        assertEquals(0.001208, TestUtils.pairedTTest(sample1, sample3), 1E-6);
+        assertFalse(TestUtils.pairedTTest(sample1, sample3, .001));
+        assertTrue(TestUtils.pairedTTest(sample1, sample3, .002));
+    }
+
+    private double[] classA =
+      {93.0, 103.0, 95.0, 101.0};
+    private double[] classB =
+      {99.0, 92.0, 102.0, 100.0, 102.0};
+    private double[] classC =
+      {110.0, 115.0, 111.0, 117.0, 128.0};
+
+    private List<double[]> classes = new ArrayList<double[]>();
+    private OneWayAnova oneWayAnova = new OneWayAnovaImpl();
+
+    public void testOneWayAnovaUtils() throws Exception {
+        classes.add(classA);
+        classes.add(classB);
+        classes.add(classC);
+        assertEquals(oneWayAnova.anovaFValue(classes),
+                TestUtils.oneWayAnovaFValue(classes), 10E-12);
+        assertEquals(oneWayAnova.anovaPValue(classes),
+                TestUtils.oneWayAnovaPValue(classes), 10E-12);
+        assertEquals(oneWayAnova.anovaTest(classes, 0.01),
+                TestUtils.oneWayAnovaTest(classes, 0.01));
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/stat/ranking/NaturalRankingTest.java b/src/test/java/org/apache/commons/math/stat/ranking/NaturalRankingTest.java
new file mode 100644
index 0000000..3d12bc5
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/ranking/NaturalRankingTest.java
@@ -0,0 +1,204 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.stat.ranking;
+
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.random.JDKRandomGenerator;
+import org.apache.commons.math.random.RandomGenerator;
+
+import junit.framework.TestCase;
+
+/**
+ * Test cases for NaturalRanking class
+ *
+ * @since 2.0
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+public class NaturalRankingTest extends TestCase {
+
+    private final double[] exampleData = { 20, 17, 30, 42.3, 17, 50,
+            Double.NaN, Double.NEGATIVE_INFINITY, 17 };
+    private final double[] tiesFirst = { 0, 0, 2, 1, 4 };
+    private final double[] tiesLast = { 4, 4, 1, 0 };
+    private final double[] multipleNaNs = { 0, 1, Double.NaN, Double.NaN };
+    private final double[] multipleTies = { 3, 2, 5, 5, 6, 6, 1 };
+    private final double[] allSame = { 0, 0, 0, 0 };
+
+    public NaturalRankingTest(String arg0) {
+        super(arg0);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    public void testDefault() { // Ties averaged, NaNs maximal
+        NaturalRanking ranking = new NaturalRanking();
+        double[] ranks = ranking.rank(exampleData);
+        double[] correctRanks = { 5, 3, 6, 7, 3, 8, 9, 1, 3 };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+        ranks = ranking.rank(tiesFirst);
+        correctRanks = new double[] { 1.5, 1.5, 4, 3, 5 };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+        ranks = ranking.rank(tiesLast);
+        correctRanks = new double[] { 3.5, 3.5, 2, 1 };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+        ranks = ranking.rank(multipleNaNs);
+        correctRanks = new double[] { 1, 2, 3.5, 3.5 };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+        ranks = ranking.rank(multipleTies);
+        correctRanks = new double[] { 3, 2, 4.5, 4.5, 6.5, 6.5, 1 };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+        ranks = ranking.rank(allSame);
+        correctRanks = new double[] { 2.5, 2.5, 2.5, 2.5 };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+    }
+
+    public void testNaNsMaximalTiesMinimum() {
+        NaturalRanking ranking = new NaturalRanking(TiesStrategy.MINIMUM);
+        double[] ranks = ranking.rank(exampleData);
+        double[] correctRanks = { 5, 2, 6, 7, 2, 8, 9, 1, 2 };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+        ranks = ranking.rank(tiesFirst);
+        correctRanks = new double[] { 1, 1, 4, 3, 5 };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+        ranks = ranking.rank(tiesLast);
+        correctRanks = new double[] { 3, 3, 2, 1 };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+        ranks = ranking.rank(multipleNaNs);
+        correctRanks = new double[] { 1, 2, 3, 3 };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+        ranks = ranking.rank(multipleTies);
+        correctRanks = new double[] { 3, 2, 4, 4, 6, 6, 1 };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+        ranks = ranking.rank(allSame);
+        correctRanks = new double[] { 1, 1, 1, 1 };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+    }
+
+    public void testNaNsRemovedTiesSequential() {
+        NaturalRanking ranking = new NaturalRanking(NaNStrategy.REMOVED,
+                TiesStrategy.SEQUENTIAL);
+        double[] ranks = ranking.rank(exampleData);
+        double[] correctRanks = { 5, 2, 6, 7, 3, 8, 1, 4 };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+        ranks = ranking.rank(tiesFirst);
+        correctRanks = new double[] { 1, 2, 4, 3, 5 };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+        ranks = ranking.rank(tiesLast);
+        correctRanks = new double[] { 3, 4, 2, 1 };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+        ranks = ranking.rank(multipleNaNs);
+        correctRanks = new double[] { 1, 2 };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+        ranks = ranking.rank(multipleTies);
+        correctRanks = new double[] { 3, 2, 4, 5, 6, 7, 1 };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+        ranks = ranking.rank(allSame);
+        correctRanks = new double[] { 1, 2, 3, 4 };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+    }
+
+    public void testNaNsMinimalTiesMaximum() {
+        NaturalRanking ranking = new NaturalRanking(NaNStrategy.MINIMAL,
+                TiesStrategy.MAXIMUM);
+        double[] ranks = ranking.rank(exampleData);
+        double[] correctRanks = { 6, 5, 7, 8, 5, 9, 2, 2, 5 };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+        ranks = ranking.rank(tiesFirst);
+        correctRanks = new double[] { 2, 2, 4, 3, 5 };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+        ranks = ranking.rank(tiesLast);
+        correctRanks = new double[] { 4, 4, 2, 1 };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+        ranks = ranking.rank(multipleNaNs);
+        correctRanks = new double[] { 3, 4, 2, 2 };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+        ranks = ranking.rank(multipleTies);
+        correctRanks = new double[] { 3, 2, 5, 5, 7, 7, 1 };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+        ranks = ranking.rank(allSame);
+        correctRanks = new double[] { 4, 4, 4, 4 };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+    }
+
+    public void testNaNsMinimalTiesAverage() {
+        NaturalRanking ranking = new NaturalRanking(NaNStrategy.MINIMAL);
+        double[] ranks = ranking.rank(exampleData);
+        double[] correctRanks = { 6, 4, 7, 8, 4, 9, 1.5, 1.5, 4 };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+        ranks = ranking.rank(tiesFirst);
+        correctRanks = new double[] { 1.5, 1.5, 4, 3, 5 };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+        ranks = ranking.rank(tiesLast);
+        correctRanks = new double[] { 3.5, 3.5, 2, 1 };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+        ranks = ranking.rank(multipleNaNs);
+        correctRanks = new double[] { 3, 4, 1.5, 1.5 };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+        ranks = ranking.rank(multipleTies);
+        correctRanks = new double[] { 3, 2, 4.5, 4.5, 6.5, 6.5, 1 };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+        ranks = ranking.rank(allSame);
+        correctRanks = new double[] { 2.5, 2.5, 2.5, 2.5 };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+    }
+
+    public void testNaNsFixedTiesRandom() {
+        RandomGenerator randomGenerator = new JDKRandomGenerator();
+        randomGenerator.setSeed(1000);
+        NaturalRanking ranking = new NaturalRanking(NaNStrategy.FIXED,
+                randomGenerator);
+        double[] ranks = ranking.rank(exampleData);
+        double[] correctRanks = { 5, 4, 6, 7, 3, 8, Double.NaN, 1, 4 };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+        ranks = ranking.rank(tiesFirst);
+        correctRanks = new double[] { 1, 1, 4, 3, 5 };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+        ranks = ranking.rank(tiesLast);
+        correctRanks = new double[] { 3, 4, 2, 1 };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+        ranks = ranking.rank(multipleNaNs);
+        correctRanks = new double[] { 1, 2, Double.NaN, Double.NaN };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+        ranks = ranking.rank(multipleTies);
+        correctRanks = new double[] { 3, 2, 5, 5, 7, 6, 1 };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+        ranks = ranking.rank(allSame);
+        correctRanks = new double[] { 1, 3, 4, 4 };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+    }
+
+    public void testNaNsAndInfs() {
+        double[] data = { 0, Double.POSITIVE_INFINITY, Double.NaN,
+                Double.NEGATIVE_INFINITY };
+        NaturalRanking ranking = new NaturalRanking(NaNStrategy.MAXIMAL);
+        double[] ranks = ranking.rank(data);
+        double[] correctRanks = new double[] { 2, 3.5, 3.5, 1 };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+        ranking = new NaturalRanking(NaNStrategy.MINIMAL);
+        ranks = ranking.rank(data);
+        correctRanks = new double[] { 3, 4, 1.5, 1.5 };
+        TestUtils.assertEquals(correctRanks, ranks, 0d);
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/stat/regression/GLSMultipleLinearRegressionTest.java b/src/test/java/org/apache/commons/math/stat/regression/GLSMultipleLinearRegressionTest.java
new file mode 100644
index 0000000..7b8f9fe
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/regression/GLSMultipleLinearRegressionTest.java
@@ -0,0 +1,297 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.stat.regression;
+
+import static org.junit.Assert.assertEquals;
+import org.junit.Before;
+import org.junit.Test;
+import org.apache.commons.math.TestUtils;
+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.random.CorrelatedRandomVectorGenerator;
+import org.apache.commons.math.random.JDKRandomGenerator;
+import org.apache.commons.math.random.GaussianRandomGenerator;
+import org.apache.commons.math.random.RandomGenerator;
+import org.apache.commons.math.stat.correlation.Covariance;
+import org.apache.commons.math.stat.descriptive.DescriptiveStatistics;
+
+public class GLSMultipleLinearRegressionTest extends MultipleLinearRegressionAbstractTest {
+
+    private double[] y;
+    private double[][] x;
+    private double[][] omega;
+    private double[] longley = new double[] {
+            60323,83.0,234289,2356,1590,107608,1947,
+            61122,88.5,259426,2325,1456,108632,1948,
+            60171,88.2,258054,3682,1616,109773,1949,
+            61187,89.5,284599,3351,1650,110929,1950,
+            63221,96.2,328975,2099,3099,112075,1951,
+            63639,98.1,346999,1932,3594,113270,1952,
+            64989,99.0,365385,1870,3547,115094,1953,
+            63761,100.0,363112,3578,3350,116219,1954,
+            66019,101.2,397469,2904,3048,117388,1955,
+            67857,104.6,419180,2822,2857,118734,1956,
+            68169,108.4,442769,2936,2798,120445,1957,
+            66513,110.8,444546,4681,2637,121950,1958,
+            68655,112.6,482704,3813,2552,123366,1959,
+            69564,114.2,502601,3931,2514,125368,1960,
+            69331,115.7,518173,4806,2572,127852,1961,
+            70551,116.9,554894,4007,2827,130081,1962
+        };
+
+    @Before
+    @Override
+    public void setUp(){
+        y = new double[]{11.0, 12.0, 13.0, 14.0, 15.0, 16.0};
+        x = new double[6][];
+        x[0] = new double[]{0, 0, 0, 0, 0};
+        x[1] = new double[]{2.0, 0, 0, 0, 0};
+        x[2] = new double[]{0, 3.0, 0, 0, 0};
+        x[3] = new double[]{0, 0, 4.0, 0, 0};
+        x[4] = new double[]{0, 0, 0, 5.0, 0};
+        x[5] = new double[]{0, 0, 0, 0, 6.0};
+        omega = new double[6][];
+        omega[0] = new double[]{1.0, 0, 0, 0, 0, 0};
+        omega[1] = new double[]{0, 2.0, 0, 0, 0, 0};
+        omega[2] = new double[]{0, 0, 3.0, 0, 0, 0};
+        omega[3] = new double[]{0, 0, 0, 4.0, 0, 0};
+        omega[4] = new double[]{0, 0, 0, 0, 5.0, 0};
+        omega[5] = new double[]{0, 0, 0, 0, 0, 6.0};
+        super.setUp();
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void cannotAddXSampleData() {
+        createRegression().newSampleData(new double[]{}, null, null);
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void cannotAddNullYSampleData() {
+        createRegression().newSampleData(null, new double[][]{}, null);
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void cannotAddSampleDataWithSizeMismatch() {
+        double[] y = new double[]{1.0, 2.0};
+        double[][] x = new double[1][];
+        x[0] = new double[]{1.0, 0};
+        createRegression().newSampleData(y, x, null);
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void cannotAddNullCovarianceData() {
+        createRegression().newSampleData(new double[]{}, new double[][]{}, null);
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void notEnoughData() {
+        double[]   reducedY = new double[y.length - 1];
+        double[][] reducedX = new double[x.length - 1][];
+        double[][] reducedO = new double[omega.length - 1][];
+        System.arraycopy(y,     0, reducedY, 0, reducedY.length);
+        System.arraycopy(x,     0, reducedX, 0, reducedX.length);
+        System.arraycopy(omega, 0, reducedO, 0, reducedO.length);
+        createRegression().newSampleData(reducedY, reducedX, reducedO);
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void cannotAddCovarianceDataWithSampleSizeMismatch() {
+        double[] y = new double[]{1.0, 2.0};
+        double[][] x = new double[2][];
+        x[0] = new double[]{1.0, 0};
+        x[1] = new double[]{0, 1.0};
+        double[][] omega = new double[1][];
+        omega[0] = new double[]{1.0, 0};
+        createRegression().newSampleData(y, x, omega);
+    }
+
+    @Test(expected=IllegalArgumentException.class)
+    public void cannotAddCovarianceDataThatIsNotSquare() {
+        double[] y = new double[]{1.0, 2.0};
+        double[][] x = new double[2][];
+        x[0] = new double[]{1.0, 0};
+        x[1] = new double[]{0, 1.0};
+        double[][] omega = new double[3][];
+        omega[0] = new double[]{1.0, 0};
+        omega[1] = new double[]{0, 1.0};
+        omega[2] = new double[]{0, 2.0};
+        createRegression().newSampleData(y, x, omega);
+    }
+
+    @Override
+    protected GLSMultipleLinearRegression createRegression() {
+        GLSMultipleLinearRegression regression = new GLSMultipleLinearRegression();
+        regression.newSampleData(y, x, omega);
+        return regression;
+    }
+
+    @Override
+    protected int getNumberOfRegressors() {
+        return x[0].length + 1;
+    }
+
+    @Override
+    protected int getSampleSize() {
+        return y.length;
+    }
+
+    /**
+     * test calculateYVariance
+     */
+    @Test
+    public void testYVariance() {
+
+        // assumes: y = new double[]{11.0, 12.0, 13.0, 14.0, 15.0, 16.0};
+
+        GLSMultipleLinearRegression model = new GLSMultipleLinearRegression();
+        model.newSampleData(y, x, omega);
+        TestUtils.assertEquals(model.calculateYVariance(), 3.5, 0);
+    }
+    
+    /**
+     * Verifies that setting X, Y and covariance separately has the same effect as newSample(X,Y,cov).
+     */
+    @Test
+    public void testNewSample2() throws Exception {
+        double[] y = new double[] {1, 2, 3, 4}; 
+        double[][] x = new double[][] {
+          {19, 22, 33},
+          {20, 30, 40},
+          {25, 35, 45},
+          {27, 37, 47}   
+        };
+        double[][] covariance = MatrixUtils.createRealIdentityMatrix(4).scalarMultiply(2).getData();
+        GLSMultipleLinearRegression regression = new GLSMultipleLinearRegression();
+        regression.newSampleData(y, x, covariance);
+        RealMatrix combinedX = regression.X.copy();
+        RealVector combinedY = regression.Y.copy();
+        RealMatrix combinedCovInv = regression.getOmegaInverse();
+        regression.newXSampleData(x);
+        regression.newYSampleData(y);
+        assertEquals(combinedX, regression.X);
+        assertEquals(combinedY, regression.Y);
+        assertEquals(combinedCovInv, regression.getOmegaInverse());
+    }
+    
+    /**
+     * Verifies that GLS with identity covariance matrix gives the same results
+     * as OLS.
+     */
+    @Test
+    public void testGLSOLSConsistency() throws Exception {      
+        RealMatrix identityCov = MatrixUtils.createRealIdentityMatrix(16);
+        GLSMultipleLinearRegression glsModel = new GLSMultipleLinearRegression();
+        OLSMultipleLinearRegression olsModel = new OLSMultipleLinearRegression();
+        glsModel.newSampleData(longley, 16, 6);
+        olsModel.newSampleData(longley, 16, 6);
+        glsModel.newCovarianceData(identityCov.getData());
+        double[] olsBeta = olsModel.calculateBeta().getData();
+        double[] glsBeta = glsModel.calculateBeta().getData();
+        // TODO:  Should have assertRelativelyEquals(double[], double[], eps) in TestUtils
+        //        Should also add RealVector and RealMatrix versions
+        for (int i = 0; i < olsBeta.length; i++) {
+            TestUtils.assertRelativelyEquals(olsBeta[i], glsBeta[i], 10E-7);
+        }
+    }
+    
+    /**
+     * Generate an error covariance matrix and sample data representing models
+     * with this error structure. Then verify that GLS estimated coefficients,
+     * on average, perform better than OLS.
+     */
+    @Test
+    public void testGLSEfficiency() throws Exception {
+        RandomGenerator rg = new JDKRandomGenerator();
+        rg.setSeed(200);  // Seed has been selected to generate non-trivial covariance
+        
+        // Assume model has 16 observations (will use Longley data).  Start by generating
+        // non-constant variances for the 16 error terms.
+        final int nObs = 16;
+        double[] sigma = new double[nObs];
+        for (int i = 0; i < nObs; i++) {
+            sigma[i] = 10 * rg.nextDouble();
+        }
+        
+        // Now generate 1000 error vectors to use to estimate the covariance matrix
+        // Columns are draws on N(0, sigma[col])
+        final int numSeeds = 1000;
+        RealMatrix errorSeeds = MatrixUtils.createRealMatrix(numSeeds, nObs);
+        for (int i = 0; i < numSeeds; i++) {
+            for (int j = 0; j < nObs; j++) {
+                errorSeeds.setEntry(i, j, rg.nextGaussian() * sigma[j]);
+            }
+        }
+        
+        // Get covariance matrix for columns
+        RealMatrix cov = (new Covariance(errorSeeds)).getCovarianceMatrix();
+          
+        // Create a CorrelatedRandomVectorGenerator to use to generate correlated errors
+        GaussianRandomGenerator rawGenerator = new GaussianRandomGenerator(rg);
+        double[] errorMeans = new double[nObs];  // Counting on init to 0 here
+        CorrelatedRandomVectorGenerator gen = new CorrelatedRandomVectorGenerator(errorMeans, cov,
+         1.0e-12 * cov.getNorm(), rawGenerator);
+        
+        // Now start generating models.  Use Longley X matrix on LHS
+        // and Longley OLS beta vector as "true" beta.  Generate
+        // Y values by XB + u where u is a CorrelatedRandomVector generated
+        // from cov.
+        OLSMultipleLinearRegression ols = new OLSMultipleLinearRegression();
+        ols.newSampleData(longley, nObs, 6);
+        final RealVector b = ols.calculateBeta().copy();
+        final RealMatrix x = ols.X.copy();
+        
+        // Create a GLS model to reuse
+        GLSMultipleLinearRegression gls = new GLSMultipleLinearRegression();
+        gls.newSampleData(longley, nObs, 6);
+        gls.newCovarianceData(cov.getData());
+        
+        // Create aggregators for stats measuring model performance
+        DescriptiveStatistics olsBetaStats = new DescriptiveStatistics();
+        DescriptiveStatistics glsBetaStats = new DescriptiveStatistics();
+        
+        // Generate Y vectors for 10000 models, estimate GLS and OLS and
+        // Verify that OLS estimates are better
+        final int nModels = 10000;
+        for (int i = 0; i < nModels; i++) {
+            
+            // Generate y = xb + u with u cov
+            RealVector u = MatrixUtils.createRealVector(gen.nextVector());
+            double[] y = u.add(x.operate(b)).getData();
+            
+            // Estimate OLS parameters
+            ols.newYSampleData(y);
+            RealVector olsBeta = ols.calculateBeta();
+            
+            // Estimate GLS parameters
+            gls.newYSampleData(y);
+            RealVector glsBeta = gls.calculateBeta();
+            
+            // Record deviations from "true" beta
+            double dist = olsBeta.getDistance(b);
+            olsBetaStats.addValue(dist * dist);
+            dist = glsBeta.getDistance(b);
+            glsBetaStats.addValue(dist * dist);
+            
+        }
+        
+        // Verify that GLS is on average more efficient, lower variance
+        assert(olsBetaStats.getMean() > 1.5 * glsBetaStats.getMean());
+        assert(olsBetaStats.getStandardDeviation() > glsBetaStats.getStandardDeviation());  
+    }
+    
+}
diff --git a/src/test/java/org/apache/commons/math/stat/regression/MultipleLinearRegressionAbstractTest.java b/src/test/java/org/apache/commons/math/stat/regression/MultipleLinearRegressionAbstractTest.java
new file mode 100644
index 0000000..08801fd
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/regression/MultipleLinearRegressionAbstractTest.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.RealMatrix;
+import org.apache.commons.math.linear.RealVector;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Before;
+import org.junit.Test;
+
+
+public abstract class MultipleLinearRegressionAbstractTest {
+
+    protected AbstractMultipleLinearRegression regression;
+
+    @Before
+    public void setUp(){
+        regression = createRegression();
+    }
+
+    protected abstract AbstractMultipleLinearRegression createRegression();
+
+    protected abstract int getNumberOfRegressors();
+
+    protected abstract int getSampleSize();
+
+    @Test
+    public void canEstimateRegressionParameters(){
+        double[] beta = regression.estimateRegressionParameters();
+        assertEquals(getNumberOfRegressors(), beta.length);
+    }
+
+    @Test
+    public void canEstimateResiduals(){
+        double[] e = regression.estimateResiduals();
+        assertEquals(getSampleSize(), e.length);
+    }
+
+    @Test
+    public void canEstimateRegressionParametersVariance(){
+        double[][] variance = regression.estimateRegressionParametersVariance();
+        assertEquals(getNumberOfRegressors(), variance.length);
+    }
+
+    @Test
+    public void canEstimateRegressandVariance(){
+        if (getSampleSize() > getNumberOfRegressors()) {
+            double variance = regression.estimateRegressandVariance();
+            assertTrue(variance > 0.0);
+        }
+    }
+    
+    /**
+     * Verifies that newSampleData methods consistently insert unitary columns
+     * in design matrix.  Confirms the fix for MATH-411.
+     */
+    @Test
+    public void testNewSample() throws Exception {
+        double[] design = new double[] {
+          1, 19, 22, 33,
+          2, 20, 30, 40,
+          3, 25, 35, 45,
+          4, 27, 37, 47
+        };
+        double[] y = new double[] {1, 2, 3, 4}; 
+        double[][] x = new double[][] {
+          {19, 22, 33},
+          {20, 30, 40},
+          {25, 35, 45},
+          {27, 37, 47}   
+        };
+        AbstractMultipleLinearRegression regression = createRegression();
+        regression.newSampleData(design, 4, 3);
+        RealMatrix flatX = regression.X.copy();
+        RealVector flatY = regression.Y.copy();
+        regression.newXSampleData(x);
+        regression.newYSampleData(y);
+        assertEquals(flatX, regression.X);
+        assertEquals(flatY, regression.Y);
+        
+        // No intercept
+        regression.setNoIntercept(true);
+        regression.newSampleData(design, 4, 3);
+        flatX = regression.X.copy();
+        flatY = regression.Y.copy();
+        regression.newXSampleData(x);
+        regression.newYSampleData(y);
+        assertEquals(flatX, regression.X);
+        assertEquals(flatY, regression.Y);
+    }
+    
+    @Test(expected=IllegalArgumentException.class)
+    public void testNewSampleNullData() throws Exception {
+        double[] data = null;
+        createRegression().newSampleData(data, 2, 3); 
+    }
+    
+    @Test(expected=IllegalArgumentException.class)
+    public void testNewSampleInvalidData() throws Exception {
+        double[] data = new double[] {1, 2, 3, 4};
+        createRegression().newSampleData(data, 2, 3);
+    }
+    
+    @Test(expected=IllegalArgumentException.class)
+    public void testNewSampleInsufficientData() throws Exception {
+        double[] data = new double[] {1, 2, 3, 4};
+        createRegression().newSampleData(data, 1, 3);
+    }
+    
+    @Test(expected=IllegalArgumentException.class)
+    public void testXSampleDataNull() {
+        createRegression().newXSampleData(null);
+    }
+    
+    @Test(expected=IllegalArgumentException.class)
+    public void testYSampleDataNull() {
+        createRegression().newYSampleData(null);
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/stat/regression/OLSMultipleLinearRegressionTest.java b/src/test/java/org/apache/commons/math/stat/regression/OLSMultipleLinearRegressionTest.java
new file mode 100644
index 0000000..52d9d65
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/regression/OLSMultipleLinearRegressionTest.java
@@ -0,0 +1,512 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 static org.junit.Assert.assertEquals;
+
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.linear.DefaultRealMatrixChangingVisitor;
+import org.apache.commons.math.linear.MatrixUtils;
+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.stat.StatUtils;
+import org.junit.Before;
+import org.junit.Test;
+
+public class OLSMultipleLinearRegressionTest extends MultipleLinearRegressionAbstractTest {
+
+    private double[] y;
+    private double[][] x;
+
+    @Before
+    @Override
+    public void setUp(){
+        y = new double[]{11.0, 12.0, 13.0, 14.0, 15.0, 16.0};
+        x = new double[6][];
+        x[0] = new double[]{0, 0, 0, 0, 0};
+        x[1] = new double[]{2.0, 0, 0, 0, 0};
+        x[2] = new double[]{0, 3.0, 0, 0, 0};
+        x[3] = new double[]{0, 0, 4.0, 0, 0};
+        x[4] = new double[]{0, 0, 0, 5.0, 0};
+        x[5] = new double[]{0, 0, 0, 0, 6.0};
+        super.setUp();
+    }
+
+    @Override
+    protected OLSMultipleLinearRegression createRegression() {
+        OLSMultipleLinearRegression regression = new OLSMultipleLinearRegression();
+        regression.newSampleData(y, x);
+        return regression;
+    }
+
+    @Override
+    protected int getNumberOfRegressors() {
+        return x[0].length + 1;
+    }
+
+    @Override
+    protected int getSampleSize() {
+        return y.length;
+    }
+    
+    @Test(expected=IllegalArgumentException.class)
+    public void cannotAddSampleDataWithSizeMismatch() {
+        double[] y = new double[]{1.0, 2.0};
+        double[][] x = new double[1][];
+        x[0] = new double[]{1.0, 0};
+        createRegression().newSampleData(y, x);
+    }
+
+    @Test
+    public void testPerfectFit() throws Exception {
+        double[] betaHat = regression.estimateRegressionParameters();
+        TestUtils.assertEquals(betaHat,
+                               new double[]{ 11.0, 1.0 / 2.0, 2.0 / 3.0, 3.0 / 4.0, 4.0 / 5.0, 5.0 / 6.0 },
+                               1e-14);
+        double[] residuals = regression.estimateResiduals();
+        TestUtils.assertEquals(residuals, new double[]{0d,0d,0d,0d,0d,0d},
+                               1e-14);
+        RealMatrix errors =
+            new Array2DRowRealMatrix(regression.estimateRegressionParametersVariance(), false);
+        final double[] s = { 1.0, -1.0 /  2.0, -1.0 /  3.0, -1.0 /  4.0, -1.0 /  5.0, -1.0 /  6.0 };
+        RealMatrix referenceVariance = new Array2DRowRealMatrix(s.length, s.length);
+        referenceVariance.walkInOptimizedOrder(new DefaultRealMatrixChangingVisitor() {
+            @Override
+            public double visit(int row, int column, double value) {
+                if (row == 0) {
+                    return s[column];
+                }
+                double x = s[row] * s[column];
+                return (row == column) ? 2 * x : x;
+            }
+        });
+       assertEquals(0.0,
+                     errors.subtract(referenceVariance).getNorm(),
+                     5.0e-16 * referenceVariance.getNorm());
+       assertEquals(1, ((OLSMultipleLinearRegression) regression).calculateRSquared(), 1E-12);
+    }
+
+
+    /**
+     * Test Longley dataset against certified values provided by NIST.
+     * Data Source: J. Longley (1967) "An Appraisal of Least Squares
+     * Programs for the Electronic Computer from the Point of View of the User"
+     * Journal of the American Statistical Association, vol. 62. September,
+     * pp. 819-841.
+     *
+     * Certified values (and data) are from NIST:
+     * http://www.itl.nist.gov/div898/strd/lls/data/LINKS/DATA/Longley.dat
+     */
+    @Test
+    public void testLongly() throws Exception {
+        // Y values are first, then independent vars
+        // Each row is one observation
+        double[] design = new double[] {
+            60323,83.0,234289,2356,1590,107608,1947,
+            61122,88.5,259426,2325,1456,108632,1948,
+            60171,88.2,258054,3682,1616,109773,1949,
+            61187,89.5,284599,3351,1650,110929,1950,
+            63221,96.2,328975,2099,3099,112075,1951,
+            63639,98.1,346999,1932,3594,113270,1952,
+            64989,99.0,365385,1870,3547,115094,1953,
+            63761,100.0,363112,3578,3350,116219,1954,
+            66019,101.2,397469,2904,3048,117388,1955,
+            67857,104.6,419180,2822,2857,118734,1956,
+            68169,108.4,442769,2936,2798,120445,1957,
+            66513,110.8,444546,4681,2637,121950,1958,
+            68655,112.6,482704,3813,2552,123366,1959,
+            69564,114.2,502601,3931,2514,125368,1960,
+            69331,115.7,518173,4806,2572,127852,1961,
+            70551,116.9,554894,4007,2827,130081,1962
+        };
+
+        final int nobs = 16;
+        final int nvars = 6;
+
+        // Estimate the model
+        OLSMultipleLinearRegression model = new OLSMultipleLinearRegression();
+        model.newSampleData(design, nobs, nvars);
+
+        // Check expected beta values from NIST
+        double[] betaHat = model.estimateRegressionParameters();
+        TestUtils.assertEquals(betaHat,
+          new double[]{-3482258.63459582, 15.0618722713733,
+                -0.358191792925910E-01,-2.02022980381683,
+                -1.03322686717359,-0.511041056535807E-01,
+                 1829.15146461355}, 2E-8); //
+
+        // Check expected residuals from R
+        double[] residuals = model.estimateResiduals();
+        TestUtils.assertEquals(residuals, new double[]{
+                267.340029759711,-94.0139423988359,46.28716775752924,
+                -410.114621930906,309.7145907602313,-249.3112153297231,
+                -164.0489563956039,-13.18035686637081,14.30477260005235,
+                 455.394094551857,-17.26892711483297,-39.0550425226967,
+                -155.5499735953195,-85.6713080421283,341.9315139607727,
+                -206.7578251937366},
+                      1E-8);
+
+        // Check standard errors from NIST
+        double[] errors = model.estimateRegressionParametersStandardErrors();
+        TestUtils.assertEquals(new double[] {890420.383607373,
+                       84.9149257747669,
+                       0.334910077722432E-01,
+                       0.488399681651699,
+                       0.214274163161675,
+                       0.226073200069370,
+                       455.478499142212}, errors, 1E-6);
+        
+        // Check regression standard error against R
+        assertEquals(304.8540735619638, model.estimateRegressionStandardError(), 1E-10);
+        
+        // Check R-Square statistics against R
+        assertEquals(0.995479004577296, model.calculateRSquared(), 1E-12);
+        assertEquals(0.992465007628826, model.calculateAdjustedRSquared(), 1E-12);
+        
+        checkVarianceConsistency(model);
+        
+        // Estimate model without intercept
+        model.setNoIntercept(true);
+        model.newSampleData(design, nobs, nvars);
+        
+        // Check expected beta values from R
+        betaHat = model.estimateRegressionParameters();
+        TestUtils.assertEquals(betaHat,
+          new double[]{-52.99357013868291, 0.07107319907358,
+                -0.42346585566399,-0.57256866841929,
+                -0.41420358884978, 48.41786562001326}, 1E-11); 
+        
+        // Check standard errors from R
+        errors = model.estimateRegressionParametersStandardErrors();
+        TestUtils.assertEquals(new double[] {129.54486693117232, 0.03016640003786,
+                0.41773654056612, 0.27899087467676, 0.32128496193363,
+                17.68948737819961}, errors, 1E-11);
+        
+        // Check expected residuals from R
+        residuals = model.estimateResiduals();
+        TestUtils.assertEquals(residuals, new double[]{
+                279.90274927293092, -130.32465380836874, 90.73228661967445, -401.31252201634948,
+                -440.46768772620027, -543.54512853774793, 201.32111639536299, 215.90889365977932,
+                73.09368242049943, 913.21694494481869, 424.82484953610174, -8.56475876776709,
+                -361.32974610842876, 27.34560497213464, 151.28955976355002, -492.49937355336846},
+                      1E-10);
+        
+        // Check regression standard error against R
+        assertEquals(475.1655079819517, model.estimateRegressionStandardError(), 1E-10);
+        
+        // Check R-Square statistics against R
+        assertEquals(0.9999670130706, model.calculateRSquared(), 1E-12);
+        assertEquals(0.999947220913, model.calculateAdjustedRSquared(), 1E-12);
+         
+    }
+
+    /**
+     * Test R Swiss fertility dataset against R.
+     * Data Source: R datasets package
+     */
+    @Test
+    public void testSwissFertility() throws Exception {
+        double[] design = new double[] {
+            80.2,17.0,15,12,9.96,
+            83.1,45.1,6,9,84.84,
+            92.5,39.7,5,5,93.40,
+            85.8,36.5,12,7,33.77,
+            76.9,43.5,17,15,5.16,
+            76.1,35.3,9,7,90.57,
+            83.8,70.2,16,7,92.85,
+            92.4,67.8,14,8,97.16,
+            82.4,53.3,12,7,97.67,
+            82.9,45.2,16,13,91.38,
+            87.1,64.5,14,6,98.61,
+            64.1,62.0,21,12,8.52,
+            66.9,67.5,14,7,2.27,
+            68.9,60.7,19,12,4.43,
+            61.7,69.3,22,5,2.82,
+            68.3,72.6,18,2,24.20,
+            71.7,34.0,17,8,3.30,
+            55.7,19.4,26,28,12.11,
+            54.3,15.2,31,20,2.15,
+            65.1,73.0,19,9,2.84,
+            65.5,59.8,22,10,5.23,
+            65.0,55.1,14,3,4.52,
+            56.6,50.9,22,12,15.14,
+            57.4,54.1,20,6,4.20,
+            72.5,71.2,12,1,2.40,
+            74.2,58.1,14,8,5.23,
+            72.0,63.5,6,3,2.56,
+            60.5,60.8,16,10,7.72,
+            58.3,26.8,25,19,18.46,
+            65.4,49.5,15,8,6.10,
+            75.5,85.9,3,2,99.71,
+            69.3,84.9,7,6,99.68,
+            77.3,89.7,5,2,100.00,
+            70.5,78.2,12,6,98.96,
+            79.4,64.9,7,3,98.22,
+            65.0,75.9,9,9,99.06,
+            92.2,84.6,3,3,99.46,
+            79.3,63.1,13,13,96.83,
+            70.4,38.4,26,12,5.62,
+            65.7,7.7,29,11,13.79,
+            72.7,16.7,22,13,11.22,
+            64.4,17.6,35,32,16.92,
+            77.6,37.6,15,7,4.97,
+            67.6,18.7,25,7,8.65,
+            35.0,1.2,37,53,42.34,
+            44.7,46.6,16,29,50.43,
+            42.8,27.7,22,29,58.33
+        };
+        
+        final int nobs = 47;
+        final int nvars = 4;
+
+        // Estimate the model
+        OLSMultipleLinearRegression model = new OLSMultipleLinearRegression();
+        model.newSampleData(design, nobs, nvars);
+
+        // Check expected beta values from R
+        double[] betaHat = model.estimateRegressionParameters();
+        TestUtils.assertEquals(betaHat,
+                new double[]{91.05542390271397,
+                -0.22064551045715,
+                -0.26058239824328,
+                -0.96161238456030,
+                 0.12441843147162}, 1E-12);
+
+        // Check expected residuals from R
+        double[] residuals = model.estimateResiduals();
+        TestUtils.assertEquals(residuals, new double[]{
+                7.1044267859730512,1.6580347433531366,
+                4.6944952770029644,8.4548022690166160,13.6547432343186212,
+               -9.3586864458500774,7.5822446330520386,15.5568995563859289,
+                0.8113090736598980,7.1186762732484308,7.4251378771228724,
+                2.6761316873234109,0.8351584810309354,7.1769991119615177,
+               -3.8746753206299553,-3.1337779476387251,-0.1412575244091504,
+                1.1186809170469780,-6.3588097346816594,3.4039270429434074,
+                2.3374058329820175,-7.9272368576900503,-7.8361010968497959,
+               -11.2597369269357070,0.9445333697827101,6.6544245101380328,
+               -0.9146136301118665,-4.3152449403848570,-4.3536932047009183,
+               -3.8907885169304661,-6.3027643926302188,-7.8308982189289091,
+               -3.1792280015332750,-6.7167298771158226,-4.8469946718041754,
+               -10.6335664353633685,11.1031134362036958,6.0084032641811733,
+                5.4326230830188482,-7.2375578629692230,2.1671550814448222,
+                15.0147574652763112,4.8625103516321015,-7.1597256413907706,
+                -0.4515205619767598,-10.2916870903837587,-15.7812984571900063},
+                1E-12);
+
+        // Check standard errors from R
+        double[] errors = model.estimateRegressionParametersStandardErrors();
+        TestUtils.assertEquals(new double[] {6.94881329475087,
+                0.07360008972340,
+                0.27410957467466,
+                0.19454551679325,
+                0.03726654773803}, errors, 1E-10);
+        
+        // Check regression standard error against R
+        assertEquals(7.73642194433223, model.estimateRegressionStandardError(), 1E-12);
+        
+        // Check R-Square statistics against R
+        assertEquals(0.649789742860228, model.calculateRSquared(), 1E-12);
+        assertEquals(0.6164363850373927, model.calculateAdjustedRSquared(), 1E-12);
+        
+        checkVarianceConsistency(model);
+        
+        // Estimate the model with no intercept
+        model = new OLSMultipleLinearRegression();
+        model.setNoIntercept(true);
+        model.newSampleData(design, nobs, nvars);
+
+        // Check expected beta values from R
+        betaHat = model.estimateRegressionParameters();
+        TestUtils.assertEquals(betaHat,
+                new double[]{0.52191832900513,
+                  2.36588087917963,
+                  -0.94770353802795, 
+                  0.30851985863609}, 1E-12);
+
+        // Check expected residuals from R
+        residuals = model.estimateResiduals();
+        TestUtils.assertEquals(residuals, new double[]{
+                44.138759883538249, 27.720705122356215, 35.873200836126799, 
+                34.574619581211977, 26.600168342080213, 15.074636243026923, -12.704904871199814,
+                1.497443824078134, 2.691972687079431, 5.582798774291231, -4.422986561283165, 
+                -9.198581600334345, 4.481765170730647, 2.273520207553216, -22.649827853221336,
+                -17.747900013943308, 20.298314638496436, 6.861405135329779, -8.684712790954924,
+                -10.298639278062371, -9.896618896845819, 4.568568616351242, -15.313570491727944,
+                -13.762961360873966, 7.156100301980509, 16.722282219843990, 26.716200609071898,
+                -1.991466398777079, -2.523342564719335, 9.776486693095093, -5.297535127628603,
+                -16.639070567471094, -10.302057295211819, -23.549487860816846, 1.506624392156384,
+                -17.939174438345930, 13.105792202765040, -1.943329906928462, -1.516005841666695,
+                -0.759066561832886, 20.793137744128977, -2.485236153005426, 27.588238710486976,
+                2.658333257106881, -15.998337823623046, -5.550742066720694, -14.219077806826615},
+                1E-12);
+
+        // Check standard errors from R
+        errors = model.estimateRegressionParametersStandardErrors();
+        TestUtils.assertEquals(new double[] {0.10470063765677, 0.41684100584290,
+                0.43370143099691, 0.07694953606522}, errors, 1E-10);
+        
+        // Check regression standard error against R
+        assertEquals(17.24710630547, model.estimateRegressionStandardError(), 1E-10);
+        
+        // Check R-Square statistics against R
+        assertEquals(0.946350722085, model.calculateRSquared(), 1E-12);
+        assertEquals(0.9413600915813, model.calculateAdjustedRSquared(), 1E-12);
+    }
+
+    /**
+     * Test hat matrix computation
+     *
+     * @throws Exception
+     */
+    @Test
+    public void testHat() throws Exception {
+
+        /*
+         * This example is from "The Hat Matrix in Regression and ANOVA",
+         * David C. Hoaglin and Roy E. Welsch,
+         * The American Statistician, Vol. 32, No. 1 (Feb., 1978), pp. 17-22.
+         *
+         */
+        double[] design = new double[] {
+                11.14, .499, 11.1,
+                12.74, .558, 8.9,
+                13.13, .604, 8.8,
+                11.51, .441, 8.9,
+                12.38, .550, 8.8,
+                12.60, .528, 9.9,
+                11.13, .418, 10.7,
+                11.7, .480, 10.5,
+                11.02, .406, 10.5,
+                11.41, .467, 10.7
+        };
+
+        int nobs = 10;
+        int nvars = 2;
+
+        // Estimate the model
+        OLSMultipleLinearRegression model = new OLSMultipleLinearRegression();
+        model.newSampleData(design, nobs, nvars);
+
+        RealMatrix hat = model.calculateHat();
+
+        // Reference data is upper half of symmetric hat matrix
+        double[] referenceData = new double[] {
+                .418, -.002,  .079, -.274, -.046,  .181,  .128,  .222,  .050,  .242,
+                       .242,  .292,  .136,  .243,  .128, -.041,  .033, -.035,  .004,
+                              .417, -.019,  .273,  .187, -.126,  .044, -.153,  .004,
+                                     .604,  .197, -.038,  .168, -.022,  .275, -.028,
+                                            .252,  .111, -.030,  .019, -.010, -.010,
+                                                   .148,  .042,  .117,  .012,  .111,
+                                                          .262,  .145,  .277,  .174,
+                                                                 .154,  .120,  .168,
+                                                                        .315,  .148,
+                                                                               .187
+        };
+
+        // Check against reference data and verify symmetry
+        int k = 0;
+        for (int i = 0; i < 10; i++) {
+            for (int j = i; j < 10; j++) {
+                assertEquals(referenceData[k], hat.getEntry(i, j), 10e-3);
+                assertEquals(hat.getEntry(i, j), hat.getEntry(j, i), 10e-12);
+                k++;
+            }
+        }
+
+        /*
+         * Verify that residuals computed using the hat matrix are close to
+         * what we get from direct computation, i.e. r = (I - H) y
+         */
+        double[] residuals = model.estimateResiduals();
+        RealMatrix I = MatrixUtils.createRealIdentityMatrix(10);
+        double[] hatResiduals = I.subtract(hat).operate(model.Y).getData();
+        TestUtils.assertEquals(residuals, hatResiduals, 10e-12);
+    }
+
+    /**
+     * test calculateYVariance
+     */
+    @Test
+    public void testYVariance() {
+
+        // assumes: y = new double[]{11.0, 12.0, 13.0, 14.0, 15.0, 16.0};
+
+        OLSMultipleLinearRegression model = new OLSMultipleLinearRegression();
+        model.newSampleData(y, x);
+        TestUtils.assertEquals(model.calculateYVariance(), 3.5, 0);
+    }
+    
+    /**
+     * Verifies that calculateYVariance and calculateResidualVariance return consistent
+     * values with direct variance computation from Y, residuals, respectively.
+     */
+    protected void checkVarianceConsistency(OLSMultipleLinearRegression model) throws Exception {
+        // Check Y variance consistency
+        TestUtils.assertEquals(StatUtils.variance(model.Y.getData()), model.calculateYVariance(), 0);
+        
+        // Check residual variance consistency
+        double[] residuals = model.calculateResiduals().getData();
+        RealMatrix X = model.X;
+        TestUtils.assertEquals(
+                StatUtils.variance(model.calculateResiduals().getData()) * (residuals.length - 1),
+                model.calculateErrorVariance() * (X.getRowDimension() - X.getColumnDimension()), 1E-20);
+        
+    }
+    
+    /**
+     * Verifies that setting X and Y separately has the same effect as newSample(X,Y).
+     */
+    @Test
+    public void testNewSample2() throws Exception {
+        double[] y = new double[] {1, 2, 3, 4}; 
+        double[][] x = new double[][] {
+          {19, 22, 33},
+          {20, 30, 40},
+          {25, 35, 45},
+          {27, 37, 47}   
+        };
+        OLSMultipleLinearRegression regression = new OLSMultipleLinearRegression();
+        regression.newSampleData(y, x);
+        RealMatrix combinedX = regression.X.copy();
+        RealVector combinedY = regression.Y.copy();
+        regression.newXSampleData(x);
+        regression.newYSampleData(y);
+        assertEquals(combinedX, regression.X);
+        assertEquals(combinedY, regression.Y);
+        
+        // No intercept
+        regression.setNoIntercept(true);
+        regression.newSampleData(y, x);
+        combinedX = regression.X.copy();
+        combinedY = regression.Y.copy();
+        regression.newXSampleData(x);
+        regression.newYSampleData(y);
+        assertEquals(combinedX, regression.X);
+        assertEquals(combinedY, regression.Y);
+    }
+    
+    @Test(expected=IllegalArgumentException.class)
+    public void testNewSampleDataYNull() {
+        createRegression().newSampleData(null, new double[][] {});
+    }
+    
+    @Test(expected=IllegalArgumentException.class)
+    public void testNewSampleDataXNull() {
+        createRegression().newSampleData(new double[] {}, null);
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/stat/regression/SimpleRegressionTest.java b/src/test/java/org/apache/commons/math/stat/regression/SimpleRegressionTest.java
new file mode 100644
index 0000000..c71a877
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/stat/regression/SimpleRegressionTest.java
@@ -0,0 +1,363 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.stat.regression;
+
+import java.util.Random;
+
+import junit.framework.TestCase;
+
+/**
+ * Test cases for the TestStatistic class.
+ *
+ * @version $Revision: 902201 $ $Date: 2010-01-22 19:18:16 +0100 (ven. 22 janv. 2010) $
+ */
+
+public final class SimpleRegressionTest extends TestCase {
+
+    /*
+     * NIST "Norris" refernce data set from
+     * http://www.itl.nist.gov/div898/strd/lls/data/LINKS/DATA/Norris.dat
+     * Strangely, order is {y,x}
+     */
+    private double[][] data = { { 0.1, 0.2 }, {338.8, 337.4 }, {118.1, 118.2 },
+            {888.0, 884.6 }, {9.2, 10.1 }, {228.1, 226.5 }, {668.5, 666.3 }, {998.5, 996.3 },
+            {449.1, 448.6 }, {778.9, 777.0 }, {559.2, 558.2 }, {0.3, 0.4 }, {0.1, 0.6 }, {778.1, 775.5 },
+            {668.8, 666.9 }, {339.3, 338.0 }, {448.9, 447.5 }, {10.8, 11.6 }, {557.7, 556.0 },
+            {228.3, 228.1 }, {998.0, 995.8 }, {888.8, 887.6 }, {119.6, 120.2 }, {0.3, 0.3 },
+            {0.6, 0.3 }, {557.6, 556.8 }, {339.3, 339.1 }, {888.0, 887.2 }, {998.5, 999.0 },
+            {778.9, 779.0 }, {10.2, 11.1 }, {117.6, 118.3 }, {228.9, 229.2 }, {668.4, 669.1 },
+            {449.2, 448.9 }, {0.2, 0.5 }
+    };
+
+    /*
+     * Correlation example from
+     * http://www.xycoon.com/correlation.htm
+     */
+    private double[][] corrData = { { 101.0, 99.2 }, {100.1, 99.0 }, {100.0, 100.0 },
+            {90.6, 111.6 }, {86.5, 122.2 }, {89.7, 117.6 }, {90.6, 121.1 }, {82.8, 136.0 },
+            {70.1, 154.2 }, {65.4, 153.6 }, {61.3, 158.5 }, {62.5, 140.6 }, {63.6, 136.2 },
+            {52.6, 168.0 }, {59.7, 154.3 }, {59.5, 149.0 }, {61.3, 165.5 }
+    };
+
+    /*
+     * From Moore and Mcabe, "Introduction to the Practice of Statistics"
+     * Example 10.3
+     */
+    private double[][] infData = { { 15.6, 5.2 }, {26.8, 6.1 }, {37.8, 8.7 }, {36.4, 8.5 },
+            {35.5, 8.8 }, {18.6, 4.9 }, {15.3, 4.5 }, {7.9, 2.5 }, {0.0, 1.1 }
+    };
+
+    /*
+     * Points to remove in the remove tests
+     */
+    private double[][] removeSingle = {infData[1]};
+    private double[][] removeMultiple = { infData[1], infData[2] };
+    private double removeX = infData[0][0];
+    private double removeY = infData[0][1];
+
+
+    /*
+     * Data with bad linear fit
+     */
+    private double[][] infData2 = { { 1, 1 }, {2, 0 }, {3, 5 }, {4, 2 },
+            {5, -1 }, {6, 12 }
+    };
+
+    public SimpleRegressionTest(String name) {
+        super(name);
+    }
+
+    public void testNorris() {
+        SimpleRegression regression = new SimpleRegression();
+        for (int i = 0; i < data.length; i++) {
+            regression.addData(data[i][1], data[i][0]);
+        }
+        // Tests against certified values from
+        // http://www.itl.nist.gov/div898/strd/lls/data/LINKS/DATA/Norris.dat
+        assertEquals("slope", 1.00211681802045, regression.getSlope(), 10E-12);
+        assertEquals("slope std err", 0.429796848199937E-03,
+                regression.getSlopeStdErr(),10E-12);
+        assertEquals("number of observations", 36, regression.getN());
+        assertEquals( "intercept", -0.262323073774029,
+            regression.getIntercept(),10E-12);
+        assertEquals("std err intercept", 0.232818234301152,
+            regression.getInterceptStdErr(),10E-12);
+        assertEquals("r-square", 0.999993745883712,
+            regression.getRSquare(), 10E-12);
+        assertEquals("SSR", 4255954.13232369,
+            regression.getRegressionSumSquares(), 10E-9);
+        assertEquals("MSE", 0.782864662630069,
+            regression.getMeanSquareError(), 10E-10);
+        assertEquals("SSE", 26.6173985294224,
+            regression.getSumSquaredErrors(),10E-9);
+        // ------------  End certified data tests
+
+        assertEquals( "predict(0)",  -0.262323073774029,
+            regression.predict(0), 10E-12);
+        assertEquals("predict(1)", 1.00211681802045 - 0.262323073774029,
+            regression.predict(1), 10E-12);
+    }
+
+    public void testCorr() {
+        SimpleRegression regression = new SimpleRegression();
+        regression.addData(corrData);
+        assertEquals("number of observations", 17, regression.getN());
+        assertEquals("r-square", .896123, regression.getRSquare(), 10E-6);
+        assertEquals("r", -0.94663767742, regression.getR(), 1E-10);
+    }
+
+    public void testNaNs() {
+        SimpleRegression regression = new SimpleRegression();
+        assertTrue("intercept not NaN", Double.isNaN(regression.getIntercept()));
+        assertTrue("slope not NaN", Double.isNaN(regression.getSlope()));
+        assertTrue("slope std err not NaN", Double.isNaN(regression.getSlopeStdErr()));
+        assertTrue("intercept std err not NaN", Double.isNaN(regression.getInterceptStdErr()));
+        assertTrue("MSE not NaN", Double.isNaN(regression.getMeanSquareError()));
+        assertTrue("e not NaN", Double.isNaN(regression.getR()));
+        assertTrue("r-square not NaN", Double.isNaN(regression.getRSquare()));
+        assertTrue( "RSS not NaN", Double.isNaN(regression.getRegressionSumSquares()));
+        assertTrue("SSE not NaN",Double.isNaN(regression.getSumSquaredErrors()));
+        assertTrue("SSTO not NaN", Double.isNaN(regression.getTotalSumSquares()));
+        assertTrue("predict not NaN", Double.isNaN(regression.predict(0)));
+
+        regression.addData(1, 2);
+        regression.addData(1, 3);
+
+        // No x variation, so these should still blow...
+        assertTrue("intercept not NaN", Double.isNaN(regression.getIntercept()));
+        assertTrue("slope not NaN", Double.isNaN(regression.getSlope()));
+        assertTrue("slope std err not NaN", Double.isNaN(regression.getSlopeStdErr()));
+        assertTrue("intercept std err not NaN", Double.isNaN(regression.getInterceptStdErr()));
+        assertTrue("MSE not NaN", Double.isNaN(regression.getMeanSquareError()));
+        assertTrue("e not NaN", Double.isNaN(regression.getR()));
+        assertTrue("r-square not NaN", Double.isNaN(regression.getRSquare()));
+        assertTrue("RSS not NaN", Double.isNaN(regression.getRegressionSumSquares()));
+        assertTrue("SSE not NaN", Double.isNaN(regression.getSumSquaredErrors()));
+        assertTrue("predict not NaN", Double.isNaN(regression.predict(0)));
+
+        // but SSTO should be OK
+        assertTrue("SSTO NaN", !Double.isNaN(regression.getTotalSumSquares()));
+
+        regression = new SimpleRegression();
+
+        regression.addData(1, 2);
+        regression.addData(3, 3);
+
+        // All should be OK except MSE, s(b0), s(b1) which need one more df
+        assertTrue("interceptNaN", !Double.isNaN(regression.getIntercept()));
+        assertTrue("slope NaN", !Double.isNaN(regression.getSlope()));
+        assertTrue ("slope std err not NaN", Double.isNaN(regression.getSlopeStdErr()));
+        assertTrue("intercept std err not NaN", Double.isNaN(regression.getInterceptStdErr()));
+        assertTrue("MSE not NaN", Double.isNaN(regression.getMeanSquareError()));
+        assertTrue("r NaN", !Double.isNaN(regression.getR()));
+        assertTrue("r-square NaN", !Double.isNaN(regression.getRSquare()));
+        assertTrue("RSS NaN", !Double.isNaN(regression.getRegressionSumSquares()));
+        assertTrue("SSE NaN", !Double.isNaN(regression.getSumSquaredErrors()));
+        assertTrue("SSTO NaN", !Double.isNaN(regression.getTotalSumSquares()));
+        assertTrue("predict NaN", !Double.isNaN(regression.predict(0)));
+
+        regression.addData(1, 4);
+
+        // MSE, MSE, s(b0), s(b1) should all be OK now
+        assertTrue("MSE NaN", !Double.isNaN(regression.getMeanSquareError()));
+        assertTrue("slope std err NaN", !Double.isNaN(regression.getSlopeStdErr()));
+        assertTrue("intercept std err NaN", !Double.isNaN(regression.getInterceptStdErr()));
+    }
+
+    public void testClear() {
+        SimpleRegression regression = new SimpleRegression();
+        regression.addData(corrData);
+        assertEquals("number of observations", 17, regression.getN());
+        regression.clear();
+        assertEquals("number of observations", 0, regression.getN());
+        regression.addData(corrData);
+        assertEquals("r-square", .896123, regression.getRSquare(), 10E-6);
+        regression.addData(data);
+        assertEquals("number of observations", 53, regression.getN());
+    }
+
+    public void testInference() throws Exception {
+        //----------  verified against R, version 1.8.1 -----
+        // infData
+        SimpleRegression regression = new SimpleRegression();
+        regression.addData(infData);
+        assertEquals("slope std err", 0.011448491,
+                regression.getSlopeStdErr(), 1E-10);
+        assertEquals("std err intercept", 0.286036932,
+                regression.getInterceptStdErr(),1E-8);
+        assertEquals("significance", 4.596e-07,
+                regression.getSignificance(),1E-8);
+        assertEquals("slope conf interval half-width", 0.0270713794287,
+                regression.getSlopeConfidenceInterval(),1E-8);
+        // infData2
+        regression = new SimpleRegression();
+        regression.addData(infData2);
+        assertEquals("slope std err", 1.07260253,
+                regression.getSlopeStdErr(), 1E-8);
+        assertEquals("std err intercept",4.17718672,
+                regression.getInterceptStdErr(),1E-8);
+        assertEquals("significance", 0.261829133982,
+                regression.getSignificance(),1E-11);
+        assertEquals("slope conf interval half-width", 2.97802204827,
+                regression.getSlopeConfidenceInterval(),1E-8);
+        //------------- End R-verified tests -------------------------------
+
+        //FIXME: get a real example to test against with alpha = .01
+        assertTrue("tighter means wider",
+                regression.getSlopeConfidenceInterval() < regression.getSlopeConfidenceInterval(0.01));
+
+        try {
+            regression.getSlopeConfidenceInterval(1);
+            fail("expecting IllegalArgumentException for alpha = 1");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+
+    }
+
+    public void testPerfect() throws Exception {
+        SimpleRegression regression = new SimpleRegression();
+        int n = 100;
+        for (int i = 0; i < n; i++) {
+            regression.addData(((double) i) / (n - 1), i);
+        }
+        assertEquals(0.0, regression.getSignificance(), 1.0e-5);
+        assertTrue(regression.getSlope() > 0.0);
+        assertTrue(regression.getSumSquaredErrors() >= 0.0);
+    }
+
+    public void testPerfectNegative() throws Exception {
+        SimpleRegression regression = new SimpleRegression();
+        int n = 100;
+        for (int i = 0; i < n; i++) {
+            regression.addData(- ((double) i) / (n - 1), i);
+        }
+
+        assertEquals(0.0, regression.getSignificance(), 1.0e-5);
+        assertTrue(regression.getSlope() < 0.0);
+    }
+
+    public void testRandom() throws Exception {
+        SimpleRegression regression = new SimpleRegression();
+        Random random = new Random(1);
+        int n = 100;
+        for (int i = 0; i < n; i++) {
+            regression.addData(((double) i) / (n - 1), random.nextDouble());
+        }
+
+        assertTrue( 0.0 < regression.getSignificance()
+                    && regression.getSignificance() < 1.0);
+    }
+
+
+    // Jira MATH-85 = Bugzilla 39432
+    public void testSSENonNegative() {
+        double[] y = { 8915.102, 8919.302, 8923.502 };
+        double[] x = { 1.107178495E2, 1.107264895E2, 1.107351295E2 };
+        SimpleRegression reg = new SimpleRegression();
+        for (int i = 0; i < x.length; i++) {
+            reg.addData(x[i], y[i]);
+        }
+        assertTrue(reg.getSumSquaredErrors() >= 0.0);
+    }
+
+    // Test remove X,Y (single observation)
+    public void testRemoveXY() throws Exception {
+        // Create regression with inference data then remove to test
+        SimpleRegression regression = new SimpleRegression();
+        regression.addData(infData);
+        regression.removeData(removeX, removeY);
+        regression.addData(removeX, removeY);
+        // Use the inference assertions to make sure that everything worked
+        assertEquals("slope std err", 0.011448491,
+                regression.getSlopeStdErr(), 1E-10);
+        assertEquals("std err intercept", 0.286036932,
+                regression.getInterceptStdErr(),1E-8);
+        assertEquals("significance", 4.596e-07,
+                regression.getSignificance(),1E-8);
+        assertEquals("slope conf interval half-width", 0.0270713794287,
+                regression.getSlopeConfidenceInterval(),1E-8);
+     }
+
+
+    // Test remove single observation in array
+    public void testRemoveSingle() throws Exception {
+        // Create regression with inference data then remove to test
+        SimpleRegression regression = new SimpleRegression();
+        regression.addData(infData);
+        regression.removeData(removeSingle);
+        regression.addData(removeSingle);
+        // Use the inference assertions to make sure that everything worked
+        assertEquals("slope std err", 0.011448491,
+                regression.getSlopeStdErr(), 1E-10);
+        assertEquals("std err intercept", 0.286036932,
+                regression.getInterceptStdErr(),1E-8);
+        assertEquals("significance", 4.596e-07,
+                regression.getSignificance(),1E-8);
+        assertEquals("slope conf interval half-width", 0.0270713794287,
+                regression.getSlopeConfidenceInterval(),1E-8);
+     }
+
+    // Test remove multiple observations
+    public void testRemoveMultiple() throws Exception {
+        // Create regression with inference data then remove to test
+        SimpleRegression regression = new SimpleRegression();
+        regression.addData(infData);
+        regression.removeData(removeMultiple);
+        regression.addData(removeMultiple);
+        // Use the inference assertions to make sure that everything worked
+        assertEquals("slope std err", 0.011448491,
+                regression.getSlopeStdErr(), 1E-10);
+        assertEquals("std err intercept", 0.286036932,
+                regression.getInterceptStdErr(),1E-8);
+        assertEquals("significance", 4.596e-07,
+                regression.getSignificance(),1E-8);
+        assertEquals("slope conf interval half-width", 0.0270713794287,
+                regression.getSlopeConfidenceInterval(),1E-8);
+     }
+
+    // Remove observation when empty
+    public void testRemoveObsFromEmpty() {
+        SimpleRegression regression = new SimpleRegression();
+        regression.removeData(removeX, removeY);
+        assertEquals(regression.getN(), 0);
+    }
+
+    // Remove single observation to empty
+    public void testRemoveObsFromSingle() {
+        SimpleRegression regression = new SimpleRegression();
+        regression.addData(removeX, removeY);
+        regression.removeData(removeX, removeY);
+        assertEquals(regression.getN(), 0);
+    }
+
+    // Remove multiple observations to empty
+    public void testRemoveMultipleToEmpty() {
+        SimpleRegression regression = new SimpleRegression();
+        regression.addData(removeMultiple);
+        regression.removeData(removeMultiple);
+        assertEquals(regression.getN(), 0);
+    }
+
+    // Remove multiple observations past empty (i.e. size of array > n)
+    public void testRemoveMultiplePastEmpty() {
+        SimpleRegression regression = new SimpleRegression();
+        regression.addData(removeX, removeY);
+        regression.removeData(removeMultiple);
+        assertEquals(regression.getN(), 0);
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/transform/FastCosineTransformerTest.java b/src/test/java/org/apache/commons/math/transform/FastCosineTransformerTest.java
new file mode 100644
index 0000000..cb1edf4
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/transform/FastCosineTransformerTest.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.transform;
+
+import org.apache.commons.math.analysis.*;
+import org.apache.commons.math.util.FastMath;
+import junit.framework.TestCase;
+
+/**
+ * Testcase for fast cosine transformer.
+ * <p>
+ * FCT algorithm is exact, the small tolerance number is used only
+ * to account for round-off errors.
+ *
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ */
+public final class FastCosineTransformerTest extends TestCase {
+
+    /**
+     * Test of transformer for the ad hoc data.
+     */
+    public void testAdHocData() {
+        FastCosineTransformer transformer = new FastCosineTransformer();
+        double result[], tolerance = 1E-12;
+
+        double x[] = { 0.0, 1.0, 4.0, 9.0, 16.0, 25.0, 36.0, 49.0, 64.0 };
+        double y[] = { 172.0, -105.096569476353, 27.3137084989848,
+                      -12.9593152353742, 8.0, -5.78585076868676,
+                       4.68629150101524, -4.15826451958632, 4.0 };
+
+        result = transformer.transform(x);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(y[i], result[i], tolerance);
+        }
+
+        result = transformer.inversetransform(y);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(x[i], result[i], tolerance);
+        }
+
+        FastFourierTransformer.scaleArray(x, FastMath.sqrt(0.5 * (x.length-1)));
+
+        result = transformer.transform2(y);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(x[i], result[i], tolerance);
+        }
+
+        result = transformer.inversetransform2(x);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(y[i], result[i], tolerance);
+        }
+    }
+
+    /**
+     * Test of transformer for the sine function.
+     */
+    public void testSinFunction() throws Exception {
+        UnivariateRealFunction f = new SinFunction();
+        FastCosineTransformer transformer = new FastCosineTransformer();
+        double min, max, result[], tolerance = 1E-12; int N = 9;
+
+        double expected[] = { 0.0, 3.26197262739567, 0.0,
+                             -2.17958042710327, 0.0, -0.648846697642915,
+                              0.0, -0.433545502649478, 0.0 };
+        min = 0.0; max = 2.0 * FastMath.PI * N / (N-1);
+        result = transformer.transform(f, min, max, N);
+        for (int i = 0; i < N; i++) {
+            assertEquals(expected[i], result[i], tolerance);
+        }
+
+        min = -FastMath.PI; max = FastMath.PI * (N+1) / (N-1);
+        result = transformer.transform(f, min, max, N);
+        for (int i = 0; i < N; i++) {
+            assertEquals(-expected[i], result[i], tolerance);
+        }
+    }
+
+    /**
+     * Test of parameters for the transformer.
+     */
+    public void testParameters() throws Exception {
+        UnivariateRealFunction f = new SinFunction();
+        FastCosineTransformer transformer = new FastCosineTransformer();
+
+        try {
+            // bad interval
+            transformer.transform(f, 1, -1, 65);
+            fail("Expecting IllegalArgumentException - bad interval");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            // bad samples number
+            transformer.transform(f, -1, 1, 1);
+            fail("Expecting IllegalArgumentException - bad samples number");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            // bad samples number
+            transformer.transform(f, -1, 1, 64);
+            fail("Expecting IllegalArgumentException - bad samples number");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/transform/FastFourierTransformerTest.java b/src/test/java/org/apache/commons/math/transform/FastFourierTransformerTest.java
new file mode 100644
index 0000000..4148048
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/transform/FastFourierTransformerTest.java
@@ -0,0 +1,174 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.transform;
+
+import org.apache.commons.math.analysis.*;
+import org.apache.commons.math.complex.*;
+import org.apache.commons.math.util.FastMath;
+import junit.framework.TestCase;
+
+/**
+ * Testcase for fast Fourier transformer.
+ * <p>
+ * FFT algorithm is exact, the small tolerance number is used only
+ * to account for round-off errors.
+ *
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ */
+public final class FastFourierTransformerTest extends TestCase {
+
+    /**
+     * Test of transformer for the ad hoc data taken from Mathematica.
+     */
+    public void testAdHocData() {
+        FastFourierTransformer transformer = new FastFourierTransformer();
+        Complex result[]; double tolerance = 1E-12;
+
+        double x[] = {1.3, 2.4, 1.7, 4.1, 2.9, 1.7, 5.1, 2.7};
+        Complex y[] = {
+            new Complex(21.9, 0.0),
+            new Complex(-2.09497474683058, 1.91507575950825),
+            new Complex(-2.6, 2.7),
+            new Complex(-1.10502525316942, -4.88492424049175),
+            new Complex(0.1, 0.0),
+            new Complex(-1.10502525316942, 4.88492424049175),
+            new Complex(-2.6, -2.7),
+            new Complex(-2.09497474683058, -1.91507575950825)};
+
+        result = transformer.transform(x);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(y[i].getReal(), result[i].getReal(), tolerance);
+            assertEquals(y[i].getImaginary(), result[i].getImaginary(), tolerance);
+        }
+
+        result = transformer.inversetransform(y);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(x[i], result[i].getReal(), tolerance);
+            assertEquals(0.0, result[i].getImaginary(), tolerance);
+        }
+
+        double x2[] = {10.4, 21.6, 40.8, 13.6, 23.2, 32.8, 13.6, 19.2};
+        FastFourierTransformer.scaleArray(x2, 1.0 / FastMath.sqrt(x2.length));
+        Complex y2[] = y;
+
+        result = transformer.transform2(y2);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(x2[i], result[i].getReal(), tolerance);
+            assertEquals(0.0, result[i].getImaginary(), tolerance);
+        }
+
+        result = transformer.inversetransform2(x2);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(y2[i].getReal(), result[i].getReal(), tolerance);
+            assertEquals(y2[i].getImaginary(), result[i].getImaginary(), tolerance);
+        }
+    }
+
+    public void test2DData() {
+        FastFourierTransformer transformer = new FastFourierTransformer();
+        double tolerance = 1E-12;
+        Complex[][] input = new Complex[][] {new Complex[] {new Complex(1, 0),
+                                                            new Complex(2, 0)},
+                                             new Complex[] {new Complex(3, 1),
+                                                            new Complex(4, 2)}};
+        Complex[][] goodOutput = new Complex[][] {new Complex[] {new Complex(5,
+                1.5), new Complex(-1, -.5)}, new Complex[] {new Complex(-2,
+                -1.5), new Complex(0, .5)}};
+        Complex[][] output = (Complex[][])transformer.mdfft(input, true);
+        Complex[][] output2 = (Complex[][])transformer.mdfft(output, false);
+
+        assertEquals(input.length, output.length);
+        assertEquals(input.length, output2.length);
+        assertEquals(input[0].length, output[0].length);
+        assertEquals(input[0].length, output2[0].length);
+        assertEquals(input[1].length, output[1].length);
+        assertEquals(input[1].length, output2[1].length);
+
+        for (int i = 0; i < input.length; i++) {
+            for (int j = 0; j < input[0].length; j++) {
+                assertEquals(input[i][j].getImaginary(), output2[i][j].getImaginary(),
+                             tolerance);
+                assertEquals(input[i][j].getReal(), output2[i][j].getReal(), tolerance);
+                assertEquals(goodOutput[i][j].getImaginary(), output[i][j].getImaginary(),
+                             tolerance);
+                assertEquals(goodOutput[i][j].getReal(), output[i][j].getReal(), tolerance);
+            }
+        }
+    }
+
+    /**
+     * Test of transformer for the sine function.
+     */
+    public void testSinFunction() throws Exception {
+        UnivariateRealFunction f = new SinFunction();
+        FastFourierTransformer transformer = new FastFourierTransformer();
+        Complex result[]; int N = 1 << 8;
+        double min, max, tolerance = 1E-12;
+
+        min = 0.0; max = 2.0 * FastMath.PI;
+        result = transformer.transform(f, min, max, N);
+        assertEquals(0.0, result[1].getReal(), tolerance);
+        assertEquals(-(N >> 1), result[1].getImaginary(), tolerance);
+        assertEquals(0.0, result[N-1].getReal(), tolerance);
+        assertEquals(N >> 1, result[N-1].getImaginary(), tolerance);
+        for (int i = 0; i < N-1; i += (i == 0 ? 2 : 1)) {
+            assertEquals(0.0, result[i].getReal(), tolerance);
+            assertEquals(0.0, result[i].getImaginary(), tolerance);
+        }
+
+        min = -FastMath.PI; max = FastMath.PI;
+        result = transformer.inversetransform(f, min, max, N);
+        assertEquals(0.0, result[1].getReal(), tolerance);
+        assertEquals(-0.5, result[1].getImaginary(), tolerance);
+        assertEquals(0.0, result[N-1].getReal(), tolerance);
+        assertEquals(0.5, result[N-1].getImaginary(), tolerance);
+        for (int i = 0; i < N-1; i += (i == 0 ? 2 : 1)) {
+            assertEquals(0.0, result[i].getReal(), tolerance);
+            assertEquals(0.0, result[i].getImaginary(), tolerance);
+        }
+    }
+
+    /**
+     * Test of parameters for the transformer.
+     */
+    public void testParameters() throws Exception {
+        UnivariateRealFunction f = new SinFunction();
+        FastFourierTransformer transformer = new FastFourierTransformer();
+
+        try {
+            // bad interval
+            transformer.transform(f, 1, -1, 64);
+            fail("Expecting IllegalArgumentException - bad interval");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            // bad samples number
+            transformer.transform(f, -1, 1, 0);
+            fail("Expecting IllegalArgumentException - bad samples number");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            // bad samples number
+            transformer.transform(f, -1, 1, 100);
+            fail("Expecting IllegalArgumentException - bad samples number");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/transform/FastHadamardTransformerTest.java b/src/test/java/org/apache/commons/math/transform/FastHadamardTransformerTest.java
new file mode 100644
index 0000000..09ebed1
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/transform/FastHadamardTransformerTest.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.transform;
+
+import junit.framework.TestCase;
+
+/**
+ * JUnit Test for HadamardTransformerTest
+ * @see org.apache.commons.math.transform.FastHadamardTransformer
+ */
+public final class FastHadamardTransformerTest extends TestCase {
+
+    /**
+     * Test of transformer for the a 8-point FHT (means n=8)
+     */
+    public void test8Points() {
+        checkAllTransforms(new int[] { 1, 4, -2, 3, 0, 1, 4, -1 },
+                       new int[] { 10, -4, 2, -4, 2, -12, 6, 8 });
+    }
+
+    /**
+     * Test of transformer for the a 4-points FHT (means n=4)
+     */
+    public void test4Points() {
+        checkAllTransforms(new int[] { 1, 2, 3, 4 },
+                           new int[] { 10, -2, -4, 0 });
+    }
+
+    /**
+     * Test the inverse transform of an integer vector is not always an integer vector
+     */
+    public void testNoIntInverse() {
+        FastHadamardTransformer transformer = new FastHadamardTransformer();
+        double[] x = transformer.inversetransform(new double[] { 0, 1, 0, 1});
+        assertEquals( 0.5, x[0], 0);
+        assertEquals(-0.5, x[1], 0);
+        assertEquals( 0.0, x[2], 0);
+        assertEquals( 0.0, x[3], 0);
+    }
+
+    /**
+     * Test of transformer for wrong number of points
+     */
+    public void test3Points() {
+        try {
+            new FastHadamardTransformer().transform(new double[3]);
+            fail("an exception should have been thrown");
+        } catch (IllegalArgumentException iae) {
+            // expected
+        }
+    }
+
+    private void checkAllTransforms(int[]x, int[] y) {
+        checkDoubleTransform(x, y);
+        checkInverseDoubleTransform(x, y);
+        checkIntTransform(x, y);
+    }
+
+    private void checkDoubleTransform(int[]x, int[] y) {
+        // Initiate the transformer
+        FastHadamardTransformer transformer = new FastHadamardTransformer();
+
+        // check double transform
+        double[] dX = new double[x.length];
+        for (int i = 0; i < dX.length; ++i) {
+            dX[i] = x[i];
+        }
+        double dResult[] = transformer.transform(dX);
+        for (int i = 0; i < dResult.length; i++) {
+            // compare computed results to precomputed results
+            assertEquals((double) y[i], dResult[i]);
+        }
+    }
+
+    private void checkIntTransform(int[]x, int[] y) {
+        // Initiate the transformer
+        FastHadamardTransformer transformer = new FastHadamardTransformer();
+
+        // check integer transform
+        int iResult[] = transformer.transform(x);
+        for (int i = 0; i < iResult.length; i++) {
+            // compare computed results to precomputed results
+            assertEquals(y[i], iResult[i]);
+        }
+
+    }
+
+    private void checkInverseDoubleTransform(int[]x, int[] y) {
+        // Initiate the transformer
+        FastHadamardTransformer transformer = new FastHadamardTransformer();
+
+        // check double transform
+        double[] dY = new double[y.length];
+        for (int i = 0; i < dY.length; ++i) {
+            dY[i] = y[i];
+        }
+        double dResult[] = transformer.inversetransform(dY);
+        for (int i = 0; i < dResult.length; i++) {
+            // compare computed results to precomputed results
+            assertEquals((double) x[i], dResult[i]);
+        }
+
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/transform/FastSineTransformerTest.java b/src/test/java/org/apache/commons/math/transform/FastSineTransformerTest.java
new file mode 100644
index 0000000..02a16f0
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/transform/FastSineTransformerTest.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.transform;
+
+import org.apache.commons.math.analysis.*;
+import org.apache.commons.math.util.FastMath;
+import junit.framework.TestCase;
+
+/**
+ * Testcase for fast sine transformer.
+ * <p>
+ * FST algorithm is exact, the small tolerance number is used only
+ * to account for round-off errors.
+ *
+ * @version $Revision: 1070725 $ $Date: 2011-02-15 02:31:12 +0100 (mar. 15 févr. 2011) $
+ */
+public final class FastSineTransformerTest extends TestCase {
+
+    /**
+     * Test of transformer for the ad hoc data.
+     */
+    public void testAdHocData() {
+        FastSineTransformer transformer = new FastSineTransformer();
+        double result[], tolerance = 1E-12;
+
+        double x[] = { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0 };
+        double y[] = { 0.0, 20.1093579685034, -9.65685424949238,
+                       5.98642305066196, -4.0, 2.67271455167720,
+                      -1.65685424949238, 0.795649469518633 };
+
+        result = transformer.transform(x);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(y[i], result[i], tolerance);
+        }
+
+        result = transformer.inversetransform(y);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(x[i], result[i], tolerance);
+        }
+
+        FastFourierTransformer.scaleArray(x, FastMath.sqrt(x.length / 2.0));
+
+        result = transformer.transform2(y);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(x[i], result[i], tolerance);
+        }
+
+        result = transformer.inversetransform2(x);
+        for (int i = 0; i < result.length; i++) {
+            assertEquals(y[i], result[i], tolerance);
+        }
+    }
+
+    /**
+     * Test of transformer for the sine function.
+     */
+    public void testSinFunction() throws Exception {
+        UnivariateRealFunction f = new SinFunction();
+        FastSineTransformer transformer = new FastSineTransformer();
+        double min, max, result[], tolerance = 1E-12; int N = 1 << 8;
+
+        min = 0.0; max = 2.0 * FastMath.PI;
+        result = transformer.transform(f, min, max, N);
+        assertEquals(N >> 1, result[2], tolerance);
+        for (int i = 0; i < N; i += (i == 1 ? 2 : 1)) {
+            assertEquals(0.0, result[i], tolerance);
+        }
+
+        min = -FastMath.PI; max = FastMath.PI;
+        result = transformer.transform(f, min, max, N);
+        assertEquals(-(N >> 1), result[2], tolerance);
+        for (int i = 0; i < N; i += (i == 1 ? 2 : 1)) {
+            assertEquals(0.0, result[i], tolerance);
+        }
+    }
+
+    /**
+     * Test of parameters for the transformer.
+     */
+    public void testParameters() throws Exception {
+        UnivariateRealFunction f = new SinFunction();
+        FastSineTransformer transformer = new FastSineTransformer();
+
+        try {
+            // bad interval
+            transformer.transform(f, 1, -1, 64);
+            fail("Expecting IllegalArgumentException - bad interval");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            // bad samples number
+            transformer.transform(f, -1, 1, 0);
+            fail("Expecting IllegalArgumentException - bad samples number");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        try {
+            // bad samples number
+            transformer.transform(f, -1, 1, 100);
+            fail("Expecting IllegalArgumentException - bad samples number");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/util/BigRealFieldTest.java b/src/test/java/org/apache/commons/math/util/BigRealFieldTest.java
new file mode 100644
index 0000000..541a005
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/util/BigRealFieldTest.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.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.commons.math.TestUtils;
+import org.junit.Test;
+
+public class BigRealFieldTest {
+
+    @Test
+    public void testZero() {
+        assertEquals(BigReal.ZERO, BigRealField.getInstance().getZero());
+    }
+
+    @Test
+    public void testOne() {
+        assertEquals(BigReal.ONE, BigRealField.getInstance().getOne());
+    }
+
+    @Test
+    public void testSerial() {
+        // deserializing the singleton should give the singleton itself back
+        BigRealField field = BigRealField.getInstance();
+        assertTrue(field == TestUtils.serializeAndRecover(field));
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/util/BigRealTest.java b/src/test/java/org/apache/commons/math/util/BigRealTest.java
new file mode 100644
index 0000000..d8060a0
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/util/BigRealTest.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.math.MathContext;
+
+import org.apache.commons.math.TestUtils;
+import org.junit.Test;
+
+public class BigRealTest {
+
+    @Test
+    public void testConstructor() {
+        assertEquals(1.625, new BigReal(new BigDecimal("1.625")).doubleValue(), 1.0e-15);
+        assertEquals(-5.0, new BigReal(new BigInteger("-5")).doubleValue(), 1.0e-15);
+        assertEquals(-5.0, new BigReal(new BigInteger("-5"), MathContext.DECIMAL64).doubleValue(), 1.0e-15);
+        assertEquals(0.125, new BigReal(new BigInteger("125"), 3).doubleValue(), 1.0e-15);
+        assertEquals(0.125, new BigReal(new BigInteger("125"), 3, MathContext.DECIMAL64).doubleValue(), 1.0e-15);
+        assertEquals(1.625, new BigReal(new char[] { '1', '.', '6', '2', '5' }).doubleValue(), 1.0e-15);
+        assertEquals(1.625, new BigReal(new char[] { 'A', 'A', '1', '.', '6', '2', '5', '9' }, 2, 5).doubleValue(), 1.0e-15);
+        assertEquals(1.625, new BigReal(new char[] { 'A', 'A', '1', '.', '6', '2', '5', '9' }, 2, 5, MathContext.DECIMAL64).doubleValue(), 1.0e-15);
+        assertEquals(1.625, new BigReal(new char[] { '1', '.', '6', '2', '5' }, MathContext.DECIMAL64).doubleValue(), 1.0e-15);
+        assertEquals(1.625, new BigReal(1.625).doubleValue(), 1.0e-15);
+        assertEquals(1.625, new BigReal(1.625, MathContext.DECIMAL64).doubleValue(), 1.0e-15);
+        assertEquals(-5.0, new BigReal(-5).doubleValue(), 1.0e-15);
+        assertEquals(-5.0, new BigReal(-5, MathContext.DECIMAL64).doubleValue(), 1.0e-15);
+        assertEquals(-5.0, new BigReal(-5l).doubleValue(), 1.0e-15);
+        assertEquals(-5.0, new BigReal(-5l, MathContext.DECIMAL64).doubleValue(), 1.0e-15);
+        assertEquals(1.625, new BigReal("1.625").doubleValue(), 1.0e-15);
+        assertEquals(1.625, new BigReal("1.625", MathContext.DECIMAL64).doubleValue(), 1.0e-15);
+    }
+
+    @Test
+    public void testCompareTo() {
+        BigReal first = new BigReal(1.0 / 2.0);
+        BigReal second = new BigReal(1.0 / 3.0);
+        BigReal third = new BigReal(1.0 / 2.0);
+
+        assertEquals(0, first.compareTo(first));
+        assertEquals(0, first.compareTo(third));
+        assertEquals(1, first.compareTo(second));
+        assertEquals(-1, second.compareTo(first));
+
+    }
+
+    public void testAdd() {
+        BigReal a = new BigReal("1.2345678");
+        BigReal b = new BigReal("8.7654321");
+        assertEquals(9.9999999, a.add(b).doubleValue(), 1.0e-15);
+    }
+
+    public void testSubtract() {
+        BigReal a = new BigReal("1.2345678");
+        BigReal b = new BigReal("8.7654321");
+        assertEquals( -7.5308643, a.subtract(b).doubleValue(), 1.0e-15);
+    }
+
+    public void testDivide() {
+        BigReal a = new BigReal("1.0000000000");
+        BigReal b = new BigReal("0.0009765625");
+        assertEquals(1024.0, a.divide(b).doubleValue(), 1.0e-15);
+    }
+
+    public void testMultiply() {
+        BigReal a = new BigReal("1024.0");
+        BigReal b = new BigReal("0.0009765625");
+        assertEquals(1.0, a.multiply(b).doubleValue(), 1.0e-15);
+    }
+
+    @Test
+    public void testDoubleValue() {
+        assertEquals(0.5, new BigReal(0.5).doubleValue(), 1.0e-15);
+    }
+
+    @Test
+    public void testBigDecimalValue() {
+        BigDecimal pi = new BigDecimal("3.1415926535897932384626433832795028841971693993751");
+        assertEquals(pi, new BigReal(pi).bigDecimalValue());
+        assertEquals(new BigDecimal(0.5), new BigReal(1.0 / 2.0).bigDecimalValue());
+    }
+
+    @Test
+    public void testEqualsAndHashCode() {
+        BigReal zero = new BigReal(0.0);
+        BigReal nullReal = null;
+        assertTrue(zero.equals(zero));
+        assertFalse(zero.equals(nullReal));
+        assertFalse(zero.equals(Double.valueOf(0)));
+        BigReal zero2 = new BigReal(0.0);
+        assertTrue(zero.equals(zero2));
+        assertEquals(zero.hashCode(), zero2.hashCode());
+        BigReal one = new BigReal(1.0);
+        assertFalse((one.equals(zero) || zero.equals(one)));
+        assertTrue(one.equals(BigReal.ONE));
+    }
+
+    public void testSerial() {
+        BigReal[] Reals = {
+            new BigReal(3.0), BigReal.ONE, BigReal.ZERO,
+            new BigReal(17), new BigReal(FastMath.PI),
+            new BigReal(-2.5)
+        };
+        for (BigReal Real : Reals) {
+            assertEquals(Real, TestUtils.serializeAndRecover(Real));
+        }
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/util/ContinuedFractionTest.java b/src/test/java/org/apache/commons/math/util/ContinuedFractionTest.java
new file mode 100644
index 0000000..4bf30b6
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/util/ContinuedFractionTest.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.util;
+
+import org.apache.commons.math.MathException;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+public class ContinuedFractionTest extends TestCase {
+    /**
+     * Constructor for ContinuedFractionTest.
+     * @param name
+     */
+    public ContinuedFractionTest(String name) {
+        super(name);
+    }
+
+    public void testGoldenRatio(){
+        ContinuedFraction cf = new ContinuedFraction() {
+
+            @Override
+            public double getA(int n, double x) {
+                return 1.0;
+            }
+
+            @Override
+            public double getB(int n, double x) {
+                return 1.0;
+            }
+        };
+
+        try {
+            double gr = cf.evaluate(0.0, 10e-9);
+            assertEquals(1.61803399, gr, 10e-9);
+        } catch (MathException e) {
+            fail(e.getMessage());
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/util/DefaultTransformerTest.java b/src/test/java/org/apache/commons/math/util/DefaultTransformerTest.java
new file mode 100644
index 0000000..7c02fd5
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/util/DefaultTransformerTest.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.util;
+
+import java.math.BigDecimal;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.math.MathException;
+import org.apache.commons.math.TestUtils;
+
+/**
+ * @version $Revision: 1073658 $ $Date: 2011-02-23 10:45:42 +0100 (mer. 23 févr. 2011) $
+ */
+public class DefaultTransformerTest extends TestCase {
+    /**
+     *
+     */
+    public void testTransformDouble() throws Exception {
+        double expected = 1.0;
+        Double input = Double.valueOf(expected);
+        DefaultTransformer t = new DefaultTransformer();
+        assertEquals(expected, t.transform(input), 1.0e-4);
+    }
+
+    /**
+     *
+     */
+    public void testTransformNull() throws Exception {
+        DefaultTransformer t = new DefaultTransformer();
+        try {
+            t.transform(null);
+            fail("Expecting MathException");
+        } catch (MathException e) {
+            // expected
+        }
+    }
+
+    /**
+     *
+     */
+    public void testTransformInteger() throws Exception {
+        double expected = 1.0;
+        Integer input = Integer.valueOf(1);
+        DefaultTransformer t = new DefaultTransformer();
+        assertEquals(expected, t.transform(input), 1.0e-4);
+    }
+
+    /**
+     *
+     */
+    public void testTransformBigDecimal() throws Exception {
+        double expected = 1.0;
+        BigDecimal input = new BigDecimal("1.0");
+        DefaultTransformer t = new DefaultTransformer();
+        assertEquals(expected, t.transform(input), 1.0e-4);
+    }
+
+    /**
+     *
+     */
+    public void testTransformString() throws Exception {
+        double expected = 1.0;
+        String input = "1.0";
+        DefaultTransformer t = new DefaultTransformer();
+        assertEquals(expected, t.transform(input), 1.0e-4);
+    }
+
+    /**
+     *
+     */
+    public void testTransformObject(){
+        Boolean input = Boolean.TRUE;
+        DefaultTransformer t = new DefaultTransformer();
+        try {
+            t.transform(input);
+            fail("Expecting MathException");
+        } catch (MathException e) {
+            // expected
+        }
+    }
+
+    public void testSerial() {
+        assertEquals(new DefaultTransformer(), TestUtils.serializeAndRecover(new DefaultTransformer()));
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/util/DoubleArrayAbstractTest.java b/src/test/java/org/apache/commons/math/util/DoubleArrayAbstractTest.java
new file mode 100644
index 0000000..23c5cca
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/util/DoubleArrayAbstractTest.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.util;
+
+import org.apache.commons.math.stat.StatUtils;
+
+import junit.framework.TestCase;
+
+/**
+ * This class contains test cases for the ExpandableDoubleArray.
+ *
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+public abstract class DoubleArrayAbstractTest extends TestCase {
+
+    protected DoubleArray da = null;
+
+    // Array used to test rolling
+    protected DoubleArray ra = null;
+
+    public DoubleArrayAbstractTest(String name) {
+        super(name);
+    }
+
+    public void testAdd1000() {
+
+        for (int i = 0; i < 1000; i++) {
+            da.addElement(i);
+        }
+
+        assertEquals(
+            "Number of elements should be equal to 1000 after adding 1000 values",
+            1000,
+            da.getNumElements());
+
+        assertEquals(
+            "The element at the 56th index should be 56",
+            56.0,
+            da.getElement(56),
+            Double.MIN_VALUE);
+
+    }
+
+    public void testGetValues() {
+        double[] controlArray = { 2.0, 4.0, 6.0 };
+
+        da.addElement(2.0);
+        da.addElement(4.0);
+        da.addElement(6.0);
+        double[] testArray = da.getElements();
+
+        for (int i = 0; i < da.getNumElements(); i++) {
+            assertEquals(
+                "The testArray values should equal the controlArray values, index i: "
+                    + i
+                    + " does not match",
+                testArray[i],
+                controlArray[i],
+                Double.MIN_VALUE);
+        }
+
+    }
+
+    public void testAddElementRolling() {
+        ra.addElement(0.5);
+        ra.addElement(1.0);
+        ra.addElement(1.0);
+        ra.addElement(1.0);
+        ra.addElement(1.0);
+        ra.addElement(1.0);
+        ra.addElementRolling(2.0);
+
+        assertEquals(
+            "There should be 6 elements in the eda",
+            6,
+            ra.getNumElements());
+        assertEquals(
+            "The max element should be 2.0",
+            2.0,
+            StatUtils.max(ra.getElements()),
+            Double.MIN_VALUE);
+        assertEquals(
+            "The min element should be 1.0",
+            1.0,
+            StatUtils.min(ra.getElements()),
+            Double.MIN_VALUE);
+
+        for (int i = 0; i < 1024; i++) {
+            ra.addElementRolling(i);
+        }
+
+        assertEquals(
+            "We just inserted 1024 rolling elements, num elements should still be 6",
+            6,
+            ra.getNumElements());
+    }
+
+    public void testMinMax() {
+        da.addElement(2.0);
+        da.addElement(22.0);
+        da.addElement(-2.0);
+        da.addElement(21.0);
+        da.addElement(22.0);
+        da.addElement(42.0);
+        da.addElement(62.0);
+        da.addElement(22.0);
+        da.addElement(122.0);
+        da.addElement(1212.0);
+
+        assertEquals("Min should be -2.0", -2.0, StatUtils.min(da.getElements()), Double.MIN_VALUE);
+        assertEquals(
+            "Max should be 1212.0",
+            1212.0,
+            StatUtils.max(da.getElements()),
+            Double.MIN_VALUE);
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/util/FastMathStrictComparisonTest.java b/src/test/java/org/apache/commons/math/util/FastMathStrictComparisonTest.java
new file mode 100644
index 0000000..59ad385
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/util/FastMathStrictComparisonTest.java
@@ -0,0 +1,248 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Test to compare FastMath results against StrictMath results for boundary values.
+ * <p>
+ * Running all tests independently: <br/>
+ * {@code mvn test -Dtest=FastMathStrictComparisonTest}<br/>
+ * or just run tests against a single method (e.g. scalb):<br/>
+ * {@code mvn test -Dtest=FastMathStrictComparisonTest -DargLine="-DtestMethod=scalb"}
+ */
+ at RunWith(Parameterized.class)
+public class FastMathStrictComparisonTest {
+
+    // Values which often need special handling
+    private static final Double[] DOUBLE_SPECIAL_VALUES = {
+        -0.0, +0.0,                                         // 1,2
+        Double.NaN,                                         // 3
+        Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, // 4,5
+        -Double.MAX_VALUE, Double.MAX_VALUE,                // 6,7
+        // decreasing order of absolute value to help catch first failure
+        -MathUtils.EPSILON, MathUtils.EPSILON,              // 8,9
+        -MathUtils.SAFE_MIN, MathUtils.SAFE_MIN,            // 10,11
+        -Double.MIN_VALUE, Double.MIN_VALUE,                // 12,13
+    };
+
+    private static final Float [] FLOAT_SPECIAL_VALUES = {
+        -0.0f, +0.0f,                                       // 1,2
+        Float.NaN,                                          // 3
+        Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY,   // 4,5
+        Float.MIN_VALUE, Float.MAX_VALUE,                   // 6,7
+        -Float.MIN_VALUE, -Float.MAX_VALUE,                 // 8,9
+    };
+
+    private static final Object [] LONG_SPECIAL_VALUES = {
+        -1,0,1,                                             // 1,2,3
+        Long.MIN_VALUE, Long.MAX_VALUE,                     // 4,5
+    };
+
+    private static final Object[] INT_SPECIAL_VALUES = {
+        -1,0,1,                                             // 1,2,3
+        Integer.MIN_VALUE, Integer.MAX_VALUE,               // 4,5
+    };
+
+    private final Method mathMethod;
+    private final Method fastMethod;
+    private final Type[] types;
+    private final Object[][] valueArrays;
+
+    public FastMathStrictComparisonTest(Method m, Method f, Type[] types, Object[][] data) throws Exception{
+        this.mathMethod=m;
+        this.fastMethod=f;
+        this.types=types;
+        this.valueArrays=data;
+    }
+
+    @Test
+    public void test1() throws Exception{
+        setupMethodCall(mathMethod, fastMethod, types, valueArrays);
+    }
+    private static boolean isNumber(Double d) {
+        return !(d.isInfinite() || d.isNaN());
+    }
+
+    private static boolean isNumber(Float f) {
+        return !(f.isInfinite() || f.isNaN());
+    }
+
+    private static void reportFailedResults(Method mathMethod, Object[] params, Object expected, Object actual, int[] entries){
+        final String methodName = mathMethod.getName();
+        String format = null;
+        long actL=0;
+        long expL=0;
+        if (expected instanceof Double) {
+            Double exp = (Double) expected;
+            Double act = (Double) actual;
+            if (isNumber(exp) && isNumber(act) && exp != 0) { // show difference as hex
+                actL = Double.doubleToLongBits(act);
+                expL = Double.doubleToLongBits(exp);
+                if (Math.abs(actL-expL)==1) {
+                    // Not 100% sure off-by-one errors are allowed everywhere, so only allow for these methods
+                    if (methodName.equals("toRadians") || methodName.equals("atan2")) {
+                        return;
+                    }
+                }
+                format = "%016x";
+            }
+        } else if (expected instanceof Float ){
+            Float exp = (Float) expected;
+            Float act = (Float) actual;
+            if (isNumber(exp) && isNumber(act) && exp != 0) { // show difference as hex
+                actL = Float.floatToIntBits(act);
+                expL = Float.floatToIntBits(exp);
+                format = "%08x";
+            }
+        }
+        StringBuilder sb = new StringBuilder();
+        sb.append(mathMethod.getReturnType().getSimpleName());
+        sb.append(" ");
+        sb.append(methodName);
+        sb.append("(");
+        String sep = "";
+        for(Object o : params){
+            sb.append(sep);
+            sb.append(o);
+            sep=", ";
+        }
+        sb.append(") expected ");
+        if (format != null){
+            sb.append(String.format(format, expL));
+        } else {
+            sb.append(expected);
+        }
+        sb.append(" actual ");
+        if (format != null){
+            sb.append(String.format(format, actL));
+        } else {
+            sb.append(actual);
+        }
+        sb.append(" entries ");
+        sb.append(Arrays.toString(entries));
+        String message = sb.toString();
+        final boolean fatal = true;
+        if (fatal) {
+            Assert.fail(message);
+        } else {
+            System.out.println(message);
+        }
+    }
+
+    private static void callMethods(Method mathMethod, Method fastMethod,
+            Object[] params, int[] entries) throws IllegalAccessException,
+            InvocationTargetException {
+        try {
+            Object expected = mathMethod.invoke(mathMethod, params);
+            Object actual = fastMethod.invoke(mathMethod, params);
+            if (!expected.equals(actual)) {
+                reportFailedResults(mathMethod, params, expected, actual, entries);
+            }
+        } catch (IllegalArgumentException e) {
+            Assert.fail(mathMethod+" "+e);
+        }
+    }
+
+    private static void setupMethodCall(Method mathMethod, Method fastMethod,
+            Type[] types, Object[][] valueArrays) throws Exception {
+        Object[] params = new Object[types.length];
+        int entry1 = 0;
+        int[] entries = new int[types.length];
+        for(Object d : valueArrays[0]) {
+            entry1++;
+            params[0] = d;
+            entries[0] = entry1;
+            if (params.length > 1){
+                int entry2 = 0;
+                for(Object d1 : valueArrays[1]) {
+                    entry2++;
+                    params[1] = d1;
+                    entries[1] = entry2;
+                    callMethods(mathMethod, fastMethod, params, entries);
+                }
+            } else {
+                callMethods(mathMethod, fastMethod, params, entries);
+            }
+        }
+    }
+
+    @Parameters
+    public static List<Object[]> data() throws Exception {
+        String singleMethod = System.getProperty("testMethod");
+        List<Object[]> list = new ArrayList<Object[]>();
+        for(Method mathMethod : StrictMath.class.getDeclaredMethods()) {
+            method:
+            if (Modifier.isPublic(mathMethod.getModifiers())){// Only test public methods
+                Type []types = mathMethod.getGenericParameterTypes();
+                if (types.length >=1) { // Only check methods with at least one parameter
+                    try {
+                        // Get the corresponding FastMath method
+                        Method fastMethod = FastMath.class.getDeclaredMethod(mathMethod.getName(), (Class[]) types);
+                        if (Modifier.isPublic(fastMethod.getModifiers())) { // It must be public too
+                            if (singleMethod != null && !fastMethod.getName().equals(singleMethod)) {
+                                break method;
+                            }
+                            Object [][] values = new Object[types.length][];
+                            int index = 0;
+                            for(Type t : types) {
+                                if (t.equals(double.class)){
+                                    values[index]=DOUBLE_SPECIAL_VALUES;
+                                } else if (t.equals(float.class)) {
+                                    values[index]=FLOAT_SPECIAL_VALUES;
+                                } else if (t.equals(long.class)) {
+                                    values[index]=LONG_SPECIAL_VALUES;
+                                } else if (t.equals(int.class)) {
+                                    values[index]=INT_SPECIAL_VALUES;
+                                } else {
+                                    System.out.println("Cannot handle class "+t+" for "+mathMethod);
+                                    break method;
+                                }
+                                index++;
+                            }
+//                            System.out.println(fastMethod);
+                            /*
+                             * The current implementation runs each method as a separate test.
+                             * Could be amended to run each value as a separate test
+                             */
+                            list.add(new Object[]{mathMethod, fastMethod, types, values});
+//                            setupMethodCall(mathMethod, fastMethod, params, data);
+                        } else {
+                            System.out.println("Cannot find public FastMath method corresponding to: "+mathMethod);
+                        }
+                    } catch (NoSuchMethodException e) {
+                        System.out.println("Cannot find FastMath method corresponding to: "+mathMethod);
+                    }
+                }
+            }
+        }
+        return list;
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/util/FastMathTest.java b/src/test/java/org/apache/commons/math/util/FastMathTest.java
new file mode 100644
index 0000000..7c708e2
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/util/FastMathTest.java
@@ -0,0 +1,1118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.Type;
+
+import org.apache.commons.math.dfp.Dfp;
+import org.apache.commons.math.dfp.DfpField;
+import org.apache.commons.math.dfp.DfpMath;
+import org.apache.commons.math.random.MersenneTwister;
+import org.apache.commons.math.random.RandomGenerator;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+public class FastMathTest {
+
+    private static final double MAX_ERROR_ULP = 0.51;
+    private static final int NUMBER_OF_TRIALS = 1000;
+
+
+    private DfpField field;
+    private RandomGenerator generator;
+
+    @Before
+    public void setUp() {
+        field = new DfpField(40);
+        generator = new MersenneTwister(6176597458463500194l);
+    }
+
+    @Test
+    public void testMinMaxDouble() {
+        double[][] pairs = {
+            { -50.0, 50.0 },
+            {  Double.POSITIVE_INFINITY, 1.0 },
+            {  Double.NEGATIVE_INFINITY, 1.0 },
+            {  Double.NaN, 1.0 },
+            {  Double.POSITIVE_INFINITY, 0.0 },
+            {  Double.NEGATIVE_INFINITY, 0.0 },
+            {  Double.NaN, 0.0 },
+            {  Double.NaN, Double.NEGATIVE_INFINITY },
+            {  Double.NaN, Double.POSITIVE_INFINITY },
+            { MathUtils.SAFE_MIN, MathUtils.EPSILON }
+        };
+        for (double[] pair : pairs) {
+            Assert.assertEquals("min(" + pair[0] + ", " + pair[1] + ")",
+                                Math.min(pair[0], pair[1]),
+                                FastMath.min(pair[0], pair[1]),
+                                MathUtils.EPSILON);
+            Assert.assertEquals("min(" + pair[1] + ", " + pair[0] + ")",
+                                Math.min(pair[1], pair[0]),
+                                FastMath.min(pair[1], pair[0]),
+                                MathUtils.EPSILON);
+            Assert.assertEquals("max(" + pair[0] + ", " + pair[1] + ")",
+                                Math.max(pair[0], pair[1]),
+                                FastMath.max(pair[0], pair[1]),
+                                MathUtils.EPSILON);
+            Assert.assertEquals("max(" + pair[1] + ", " + pair[0] + ")",
+                                Math.max(pair[1], pair[0]),
+                                FastMath.max(pair[1], pair[0]),
+                                MathUtils.EPSILON);
+        }
+    }
+
+    @Test
+    public void testMinMaxFloat() {
+        float[][] pairs = {
+            { -50.0f, 50.0f },
+            {  Float.POSITIVE_INFINITY, 1.0f },
+            {  Float.NEGATIVE_INFINITY, 1.0f },
+            {  Float.NaN, 1.0f },
+            {  Float.POSITIVE_INFINITY, 0.0f },
+            {  Float.NEGATIVE_INFINITY, 0.0f },
+            {  Float.NaN, 0.0f },
+            {  Float.NaN, Float.NEGATIVE_INFINITY },
+            {  Float.NaN, Float.POSITIVE_INFINITY }
+        };
+        for (float[] pair : pairs) {
+            Assert.assertEquals("min(" + pair[0] + ", " + pair[1] + ")",
+                                Math.min(pair[0], pair[1]),
+                                FastMath.min(pair[0], pair[1]),
+                                MathUtils.EPSILON);
+            Assert.assertEquals("min(" + pair[1] + ", " + pair[0] + ")",
+                                Math.min(pair[1], pair[0]),
+                                FastMath.min(pair[1], pair[0]),
+                                MathUtils.EPSILON);
+            Assert.assertEquals("max(" + pair[0] + ", " + pair[1] + ")",
+                                Math.max(pair[0], pair[1]),
+                                FastMath.max(pair[0], pair[1]),
+                                MathUtils.EPSILON);
+            Assert.assertEquals("max(" + pair[1] + ", " + pair[0] + ")",
+                                Math.max(pair[1], pair[0]),
+                                FastMath.max(pair[1], pair[0]),
+                                MathUtils.EPSILON);
+        }
+    }
+
+    @Test
+    public void testConstants() {
+        Assert.assertEquals(Math.PI, FastMath.PI, 1.0e-20);
+        Assert.assertEquals(Math.E, FastMath.E, 1.0e-20);
+    }
+
+    @Test
+    public void testAtan2() {
+        double y1 = 1.2713504628280707e10;
+        double x1 = -5.674940885228782e-10;
+        Assert.assertEquals(Math.atan2(y1, x1), FastMath.atan2(y1, x1), 2 * MathUtils.EPSILON);
+        double y2 = 0.0;
+        double x2 = Double.POSITIVE_INFINITY;
+        Assert.assertEquals(Math.atan2(y2, x2), FastMath.atan2(y2, x2), MathUtils.SAFE_MIN);
+    }
+
+    @Test
+    public void testHyperbolic() {
+        double maxErr = 0;
+        for (double x = -30; x < 30; x += 0.001) {
+            double tst = FastMath.sinh(x);
+            double ref = Math.sinh(x);
+            maxErr = FastMath.max(maxErr, FastMath.abs(ref - tst) / FastMath.ulp(ref));
+        }
+        Assert.assertEquals(0, maxErr, 2);
+
+        maxErr = 0;
+        for (double x = -30; x < 30; x += 0.001) {
+            double tst = FastMath.cosh(x);
+            double ref = Math.cosh(x);
+            maxErr = FastMath.max(maxErr, FastMath.abs(ref - tst) / FastMath.ulp(ref));
+        }
+        Assert.assertEquals(0, maxErr, 2);
+
+        maxErr = 0;
+        for (double x = -0.5; x < 0.5; x += 0.001) {
+            double tst = FastMath.tanh(x);
+            double ref = Math.tanh(x);
+            maxErr = FastMath.max(maxErr, FastMath.abs(ref - tst) / FastMath.ulp(ref));
+        }
+        Assert.assertEquals(0, maxErr, 4);
+
+    }
+
+    @Test
+    public void testHyperbolicInverses() {
+        double maxErr = 0;
+        for (double x = -30; x < 30; x += 0.01) {
+            maxErr = FastMath.max(maxErr, FastMath.abs(x - FastMath.sinh(FastMath.asinh(x))) / (2 * FastMath.ulp(x)));
+        }
+        Assert.assertEquals(0, maxErr, 3);
+
+        maxErr = 0;
+        for (double x = 1; x < 30; x += 0.01) {
+            maxErr = FastMath.max(maxErr, FastMath.abs(x - FastMath.cosh(FastMath.acosh(x))) / (2 * FastMath.ulp(x)));
+        }
+        Assert.assertEquals(0, maxErr, 2);
+
+        maxErr = 0;
+        for (double x = -1 + MathUtils.EPSILON; x < 1 - MathUtils.EPSILON; x += 0.0001) {
+            maxErr = FastMath.max(maxErr, FastMath.abs(x - FastMath.tanh(FastMath.atanh(x))) / (2 * FastMath.ulp(x)));
+        }
+        Assert.assertEquals(0, maxErr, 2);
+
+    }
+
+    @Test
+    public void testLogAccuracy() {
+        double maxerrulp = 0.0;
+
+        for (int i = 0; i < NUMBER_OF_TRIALS; i++) {
+            double x = Math.exp(generator.nextDouble() * 1416.0 - 708.0) * generator.nextDouble();
+            // double x = generator.nextDouble()*2.0;
+            double tst = FastMath.log(x);
+            double ref = DfpMath.log(field.newDfp(x)).toDouble();
+            double err = (tst - ref) / ref;
+
+            if (err != 0.0) {
+                double ulp = Math.abs(ref -
+                                      Double.longBitsToDouble((Double
+                                          .doubleToLongBits(ref) ^ 1)));
+                double errulp = field.newDfp(tst).subtract(DfpMath.log(field.newDfp(x))).divide(field.newDfp(ulp)).toDouble();
+//                System.out.println(x + "\t" + tst + "\t" + ref + "\t" + err + "\t" + errulp);
+
+                maxerrulp = Math.max(maxerrulp, Math.abs(errulp));
+            }
+        }
+
+        Assert.assertTrue("log() had errors in excess of " + MAX_ERROR_ULP + " ULP", maxerrulp < MAX_ERROR_ULP);
+    }
+
+    @Test
+    public void testLog10Accuracy() {
+        double maxerrulp = 0.0;
+
+        for (int i = 0; i < NUMBER_OF_TRIALS; i++) {
+            double x = Math.exp(generator.nextDouble() * 1416.0 - 708.0) * generator.nextDouble();
+            // double x = generator.nextDouble()*2.0;
+            double tst = FastMath.log10(x);
+            double ref = DfpMath.log(field.newDfp(x)).divide(DfpMath.log(field.newDfp("10"))).toDouble();
+            double err = (tst - ref) / ref;
+
+            if (err != 0.0) {
+                double ulp = Math.abs(ref -
+                                      Double.longBitsToDouble((Double.doubleToLongBits(ref) ^ 1)));
+                double errulp = field.newDfp(tst).subtract(DfpMath.log(field.newDfp(x)).divide(DfpMath.log(field.newDfp("10")))).divide(field.newDfp(ulp)).toDouble();
+//                System.out.println(x + "\t" + tst + "\t" + ref + "\t" + err + "\t" + errulp);
+
+                maxerrulp = Math.max(maxerrulp, Math.abs(errulp));
+            }
+        }
+
+        Assert.assertTrue("log10() had errors in excess of " + MAX_ERROR_ULP + " ULP", maxerrulp < MAX_ERROR_ULP);
+    }
+
+    @Test
+    public void testLog1pAccuracy() {
+        double maxerrulp = 0.0;
+
+        for (int i = 0; i < NUMBER_OF_TRIALS; i++) {
+            double x = Math.exp(generator.nextDouble() * 10.0 - 5.0) * generator.nextDouble();
+            // double x = generator.nextDouble()*2.0;
+            double tst = FastMath.log1p(x);
+            double ref = DfpMath.log(field.newDfp(x).add(field.getOne())).toDouble();
+            double err = (tst - ref) / ref;
+
+            if (err != 0.0) {
+                double ulp = Math.abs(ref -
+                                      Double.longBitsToDouble((Double.doubleToLongBits(ref) ^ 1)));
+                double errulp = field.newDfp(tst).subtract(DfpMath.log(field.newDfp(x).add(field.getOne()))).divide(field.newDfp(ulp)).toDouble();
+//                System.out.println(x + "\t" + tst + "\t" + ref + "\t" + err + "\t" + errulp);
+
+                maxerrulp = Math.max(maxerrulp, Math.abs(errulp));
+            }
+        }
+
+        Assert.assertTrue("log1p() had errors in excess of " + MAX_ERROR_ULP + " ULP", maxerrulp < MAX_ERROR_ULP);
+    }
+
+    @Test
+    public void testLogSpecialCases() {
+        double x;
+
+        x = FastMath.log(0.0);
+        if (x != Double.NEGATIVE_INFINITY)
+            throw new RuntimeException("Log of zero should be -Inf");
+
+        x = FastMath.log(-0.0);
+        if (x != Double.NEGATIVE_INFINITY)
+            throw new RuntimeException("Log of zero should be -Inf");
+
+        x = FastMath.log(Double.NaN);
+        if (x == x)
+            throw new RuntimeException("Log of NaN should be NaN");
+
+        x = FastMath.log(-1.0);
+        if (x == x)
+            throw new RuntimeException("Log of negative number should be NaN");
+
+        x = FastMath.log(Double.MIN_VALUE);
+        if (x != -744.4400719213812)
+            throw new RuntimeException(
+                                       "Log of Double.MIN_VALUE should be -744.4400719213812");
+
+        x = FastMath.log(-1.0);
+        if (x == x)
+            throw new RuntimeException("Log of negative number should be NaN");
+
+        x = FastMath.log(Double.POSITIVE_INFINITY);
+        if (x != Double.POSITIVE_INFINITY)
+            throw new RuntimeException("Log of infinity should be infinity");
+    }
+
+    @Test
+    public void testExpSpecialCases() {
+        double x;
+
+        /* Smallest value that will round up to Double.MIN_VALUE */
+        x = FastMath.exp(-745.1332191019411);
+        if (x != Double.MIN_VALUE)
+            throw new RuntimeException(
+                                       "exp(-745.1332191019411) should be Double.MIN_VALUE");
+
+        x = FastMath.exp(-745.1332191019412);
+        if (x != 0.0)
+            throw new RuntimeException("exp(-745.1332191019412) should be 0.0");
+
+        x = FastMath.exp(Double.NaN);
+        if (x == x)
+            throw new RuntimeException("exp of NaN should be NaN");
+
+        x = FastMath.exp(Double.POSITIVE_INFINITY);
+        if (x != Double.POSITIVE_INFINITY)
+            throw new RuntimeException("exp of infinity should be infinity");
+
+        x = FastMath.exp(Double.NEGATIVE_INFINITY);
+        if (x != 0.0)
+            throw new RuntimeException("exp of -infinity should be 0.0");
+
+        x = FastMath.exp(1.0);
+        if (x != Math.E)
+            throw new RuntimeException("exp(1) should be Math.E");
+    }
+
+    @Test
+    public void testPowSpecialCases() {
+        double x;
+
+        x = FastMath.pow(-1.0, 0.0);
+        if (x != 1.0)
+            throw new RuntimeException("pow(x, 0) should be 1.0");
+
+        x = FastMath.pow(-1.0, -0.0);
+        if (x != 1.0)
+            throw new RuntimeException("pow(x, -0) should be 1.0");
+
+        x = FastMath.pow(Math.PI, 1.0);
+        if (x != Math.PI)
+            throw new RuntimeException("pow(PI, 1.0) should be PI");
+
+        x = FastMath.pow(-Math.PI, 1.0);
+        if (x != -Math.PI)
+            throw new RuntimeException("pow(-PI, 1.0) should be PI");
+
+        x = FastMath.pow(Math.PI, Double.NaN);
+        if (x == x)
+            throw new RuntimeException("pow(PI, NaN) should be NaN");
+
+        x = FastMath.pow(Double.NaN, Math.PI);
+        if (x == x)
+            throw new RuntimeException("pow(NaN, PI) should be NaN");
+
+        x = FastMath.pow(2.0, Double.POSITIVE_INFINITY);
+        if (x != Double.POSITIVE_INFINITY)
+            throw new RuntimeException("pow(2.0, Infinity) should be Infinity");
+
+        x = FastMath.pow(0.5, Double.NEGATIVE_INFINITY);
+        if (x != Double.POSITIVE_INFINITY)
+            throw new RuntimeException("pow(0.5, -Infinity) should be Infinity");
+
+        x = FastMath.pow(0.5, Double.POSITIVE_INFINITY);
+        if (x != 0.0)
+            throw new RuntimeException("pow(0.5, Infinity) should be 0.0");
+
+        x = FastMath.pow(2.0, Double.NEGATIVE_INFINITY);
+        if (x != 0.0)
+            throw new RuntimeException("pow(2.0, -Infinity) should be 0.0");
+
+        x = FastMath.pow(0.0, 0.5);
+        if (x != 0.0)
+            throw new RuntimeException("pow(0.0, 0.5) should be 0.0");
+
+        x = FastMath.pow(Double.POSITIVE_INFINITY, -0.5);
+        if (x != 0.0)
+            throw new RuntimeException("pow(Inf, -0.5) should be 0.0");
+
+        x = FastMath.pow(0.0, -0.5);
+        if (x != Double.POSITIVE_INFINITY)
+            throw new RuntimeException("pow(0.0, -0.5) should be Inf");
+
+        x = FastMath.pow(Double.POSITIVE_INFINITY, 0.5);
+        if (x != Double.POSITIVE_INFINITY)
+            throw new RuntimeException("pow(Inf, 0.5) should be Inf");
+
+        x = FastMath.pow(-0.0, -3.0);
+        if (x != Double.NEGATIVE_INFINITY)
+            throw new RuntimeException("pow(-0.0, -3.0) should be -Inf");
+
+        x = FastMath.pow(Double.NEGATIVE_INFINITY, 3.0);
+        if (x != Double.NEGATIVE_INFINITY)
+            throw new RuntimeException("pow(-Inf, -3.0) should be -Inf");
+
+        x = FastMath.pow(-0.0, -3.5);
+        if (x != Double.POSITIVE_INFINITY)
+            throw new RuntimeException("pow(-0.0, -3.5) should be Inf");
+
+        x = FastMath.pow(Double.POSITIVE_INFINITY, 3.5);
+        if (x != Double.POSITIVE_INFINITY)
+            throw new RuntimeException("pow(Inf, 3.5) should be Inf");
+
+        x = FastMath.pow(-2.0, 3.0);
+        if (x != -8.0)
+            throw new RuntimeException("pow(-2.0, 3.0) should be -8.0");
+
+        x = FastMath.pow(-2.0, 3.5);
+        if (x == x)
+            throw new RuntimeException("pow(-2.0, 3.5) should be NaN");
+    }
+
+    @Test
+    public void testAtan2SpecialCases() {
+        double x;
+
+        x = FastMath.atan2(Double.NaN, 0.0);
+        if (x == x)
+            throw new RuntimeException("atan2(NaN, 0.0) should be NaN");
+
+        x = FastMath.atan2(0.0, Double.NaN);
+        if (x == x)
+            throw new RuntimeException("atan2(0.0, NaN) should be NaN");
+
+        x = FastMath.atan2(0.0, 0.0);
+        if (x != 0.0 || 1 / x != Double.POSITIVE_INFINITY)
+            throw new RuntimeException("atan2(0.0, 0.0) should be 0.0");
+
+        x = FastMath.atan2(0.0, 0.001);
+        if (x != 0.0 || 1 / x != Double.POSITIVE_INFINITY)
+            throw new RuntimeException("atan2(0.0, 0.001) should be 0.0");
+
+        x = FastMath.atan2(0.1, Double.POSITIVE_INFINITY);
+        if (x != 0.0 || 1 / x != Double.POSITIVE_INFINITY)
+            throw new RuntimeException("atan2(0.1, +Inf) should be 0.0");
+
+        x = FastMath.atan2(-0.0, 0.0);
+        if (x != 0.0 || 1 / x != Double.NEGATIVE_INFINITY)
+            throw new RuntimeException("atan2(-0.0, 0.0) should be -0.0");
+
+        x = FastMath.atan2(-0.0, 0.001);
+        if (x != 0.0 || 1 / x != Double.NEGATIVE_INFINITY)
+            throw new RuntimeException("atan2(-0.0, 0.001) should be -0.0");
+
+        x = FastMath.atan2(-0.1, Double.POSITIVE_INFINITY);
+        if (x != 0.0 || 1 / x != Double.NEGATIVE_INFINITY)
+            throw new RuntimeException("atan2(-0.0, +Inf) should be -0.0");
+
+        x = FastMath.atan2(0.0, -0.0);
+        if (x != Math.PI)
+            throw new RuntimeException("atan2(0.0, -0.0) should be PI");
+
+        x = FastMath.atan2(0.1, Double.NEGATIVE_INFINITY);
+        if (x != Math.PI)
+            throw new RuntimeException("atan2(0.1, -Inf) should be PI");
+
+        x = FastMath.atan2(-0.0, -0.0);
+        if (x != -Math.PI)
+            throw new RuntimeException("atan2(-0.0, -0.0) should be -PI");
+
+        x = FastMath.atan2(-0.1, Double.NEGATIVE_INFINITY);
+        if (x != -Math.PI)
+            throw new RuntimeException("atan2(0.1, -Inf) should be -PI");
+
+        x = FastMath.atan2(0.1, 0.0);
+        if (x != Math.PI / 2)
+            throw new RuntimeException("atan2(0.1, 0.0) should be PI/2");
+
+        x = FastMath.atan2(0.1, -0.0);
+        if (x != Math.PI / 2)
+            throw new RuntimeException("atan2(0.1, -0.0) should be PI/2");
+
+        x = FastMath.atan2(Double.POSITIVE_INFINITY, 0.1);
+        if (x != Math.PI / 2)
+            throw new RuntimeException("atan2(Inf, 0.1) should be PI/2");
+
+        x = FastMath.atan2(Double.POSITIVE_INFINITY, -0.1);
+        if (x != Math.PI / 2)
+            throw new RuntimeException("atan2(Inf, -0.1) should be PI/2");
+
+        x = FastMath.atan2(-0.1, 0.0);
+        if (x != -Math.PI / 2)
+            throw new RuntimeException("atan2(-0.1, 0.0) should be -PI/2");
+
+        x = FastMath.atan2(-0.1, -0.0);
+        if (x != -Math.PI / 2)
+            throw new RuntimeException("atan2(-0.1, -0.0) should be -PI/2");
+
+        x = FastMath.atan2(Double.NEGATIVE_INFINITY, 0.1);
+        if (x != -Math.PI / 2)
+            throw new RuntimeException("atan2(-Inf, 0.1) should be -PI/2");
+
+        x = FastMath.atan2(Double.NEGATIVE_INFINITY, -0.1);
+        if (x != -Math.PI / 2)
+            throw new RuntimeException("atan2(-Inf, -0.1) should be -PI/2");
+
+        x = FastMath.atan2(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY);
+        if (x != Math.PI / 4)
+            throw new RuntimeException("atan2(Inf, Inf) should be PI/4");
+
+        x = FastMath.atan2(Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY);
+        if (x != Math.PI * 3.0 / 4.0)
+            throw new RuntimeException("atan2(Inf, -Inf) should be PI * 3/4");
+
+        x = FastMath.atan2(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
+        if (x != -Math.PI / 4)
+            throw new RuntimeException("atan2(-Inf, Inf) should be -PI/4");
+
+        x = FastMath.atan2(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY);
+        if (x != -Math.PI * 3.0 / 4.0)
+            throw new RuntimeException("atan2(-Inf, -Inf) should be -PI * 3/4");
+    }
+
+    @Test
+    public void testPowAccuracy() {
+        double maxerrulp = 0.0;
+
+        for (int i = 0; i < NUMBER_OF_TRIALS; i++) {
+            double x = (generator.nextDouble() * 2.0 + 0.25);
+            double y = (generator.nextDouble() * 1200.0 - 600.0) * generator.nextDouble();
+            /*
+             * double x = FastMath.floor(generator.nextDouble()*1024.0 - 512.0); double
+             * y; if (x != 0) y = FastMath.floor(512.0 / FastMath.abs(x)); else
+             * y = generator.nextDouble()*1200.0; y = y - y/2; x = FastMath.pow(2.0, x) *
+             * generator.nextDouble(); y = y * generator.nextDouble();
+             */
+
+            // double x = generator.nextDouble()*2.0;
+            double tst = FastMath.pow(x, y);
+            double ref = DfpMath.pow(field.newDfp(x), field.newDfp(y)).toDouble();
+            double err = (tst - ref) / ref;
+
+            if (err != 0) {
+                double ulp = Math.abs(ref -
+                                      Double.longBitsToDouble((Double
+                                          .doubleToLongBits(ref) ^ 1)));
+                double errulp = field.newDfp(tst).subtract(DfpMath.pow(field.newDfp(x), field.newDfp(y))).divide(field.newDfp(ulp)).toDouble();
+//                System.out.println(x + "\t" + y + "\t" + tst + "\t" + ref + "\t" + err + "\t" + errulp);
+
+                maxerrulp = Math.max(maxerrulp, Math.abs(errulp));
+            }
+        }
+
+        Assert.assertTrue("pow() had errors in excess of " + MAX_ERROR_ULP + " ULP", maxerrulp < MAX_ERROR_ULP);
+    }
+
+    @Test
+    public void testExpAccuracy() {
+        double maxerrulp = 0.0;
+
+        for (int i = 0; i < NUMBER_OF_TRIALS; i++) {
+            /* double x = 1.0 + i/1024.0/2.0; */
+            double x = ((generator.nextDouble() * 1416.0) - 708.0) * generator.nextDouble();
+            // double x = (generator.nextDouble() * 20.0) - 10.0;
+            // double x = ((generator.nextDouble() * 2.0) - 1.0) * generator.nextDouble();
+            /* double x = 3.0 / 512.0 * i - 3.0; */
+            double tst = FastMath.exp(x);
+            double ref = DfpMath.exp(field.newDfp(x)).toDouble();
+            double err = (tst - ref) / ref;
+
+            if (err != 0) {
+                double ulp = Math.abs(ref -
+                                      Double.longBitsToDouble((Double.doubleToLongBits(ref) ^ 1)));
+                double errulp = field.newDfp(tst).subtract(DfpMath.exp(field.newDfp(x))).divide(field.newDfp(ulp)).toDouble();
+//                System.out.println(x + "\t" + tst + "\t" + ref + "\t" + err + "\t" + errulp);
+
+                maxerrulp = Math.max(maxerrulp, Math.abs(errulp));
+            }
+        }
+
+        Assert.assertTrue("exp() had errors in excess of " + MAX_ERROR_ULP + " ULP", maxerrulp < MAX_ERROR_ULP);
+    }
+
+    @Test
+    public void testSinAccuracy() {
+        double maxerrulp = 0.0;
+
+        for (int i = 0; i < NUMBER_OF_TRIALS; i++) {
+            /* double x = 1.0 + i/1024.0/2.0; */
+            // double x = ((generator.nextDouble() * 1416.0) - 708.0) * generator.nextDouble();
+            double x = ((generator.nextDouble() * Math.PI) - Math.PI / 2.0) *
+                       Math.pow(2, 21) * generator.nextDouble();
+            // double x = (generator.nextDouble() * 20.0) - 10.0;
+            // double x = ((generator.nextDouble() * 2.0) - 1.0) * generator.nextDouble();
+            /* double x = 3.0 / 512.0 * i - 3.0; */
+            double tst = FastMath.sin(x);
+            double ref = DfpMath.sin(field.newDfp(x)).toDouble();
+            double err = (tst - ref) / ref;
+
+            if (err != 0) {
+                double ulp = Math.abs(ref -
+                                      Double.longBitsToDouble((Double.doubleToLongBits(ref) ^ 1)));
+                double errulp = field.newDfp(tst).subtract(DfpMath.sin(field.newDfp(x))).divide(field.newDfp(ulp)).toDouble();
+//                System.out.println(x + "\t" + tst + "\t" + ref + "\t" + err + "\t" + errulp);
+
+                maxerrulp = Math.max(maxerrulp, Math.abs(errulp));
+            }
+        }
+
+        Assert.assertTrue("sin() had errors in excess of " + MAX_ERROR_ULP + " ULP", maxerrulp < MAX_ERROR_ULP);
+    }
+
+    @Test
+    public void testCosAccuracy() {
+        double maxerrulp = 0.0;
+
+        for (int i = 0; i < NUMBER_OF_TRIALS; i++) {
+            /* double x = 1.0 + i/1024.0/2.0; */
+            // double x = ((generator.nextDouble() * 1416.0) - 708.0) * generator.nextDouble();
+            double x = ((generator.nextDouble() * Math.PI) - Math.PI / 2.0) *
+                       Math.pow(2, 21) * generator.nextDouble();
+            // double x = (generator.nextDouble() * 20.0) - 10.0;
+            // double x = ((generator.nextDouble() * 2.0) - 1.0) * generator.nextDouble();
+            /* double x = 3.0 / 512.0 * i - 3.0; */
+            double tst = FastMath.cos(x);
+            double ref = DfpMath.cos(field.newDfp(x)).toDouble();
+            double err = (tst - ref) / ref;
+
+            if (err != 0) {
+                double ulp = Math.abs(ref -
+                                      Double.longBitsToDouble((Double.doubleToLongBits(ref) ^ 1)));
+                double errulp = field.newDfp(tst).subtract(DfpMath.cos(field.newDfp(x))).divide(field.newDfp(ulp)).toDouble();
+//                System.out.println(x + "\t" + tst + "\t" + ref + "\t" + err + "\t" + errulp);
+
+                maxerrulp = Math.max(maxerrulp, Math.abs(errulp));
+            }
+        }
+
+        Assert.assertTrue("cos() had errors in excess of " + MAX_ERROR_ULP + " ULP", maxerrulp < MAX_ERROR_ULP);
+    }
+
+    @Test
+    public void testTanAccuracy() {
+        double maxerrulp = 0.0;
+
+        for (int i = 0; i < NUMBER_OF_TRIALS; i++) {
+            /* double x = 1.0 + i/1024.0/2.0; */
+            // double x = ((generator.nextDouble() * 1416.0) - 708.0) * generator.nextDouble();
+            double x = ((generator.nextDouble() * Math.PI) - Math.PI / 2.0) *
+                       Math.pow(2, 12) * generator.nextDouble();
+            // double x = (generator.nextDouble() * 20.0) - 10.0;
+            // double x = ((generator.nextDouble() * 2.0) - 1.0) * generator.nextDouble();
+            /* double x = 3.0 / 512.0 * i - 3.0; */
+            double tst = FastMath.tan(x);
+            double ref = DfpMath.tan(field.newDfp(x)).toDouble();
+            double err = (tst - ref) / ref;
+
+            if (err != 0) {
+                double ulp = Math.abs(ref -
+                                      Double.longBitsToDouble((Double.doubleToLongBits(ref) ^ 1)));
+                double errulp = field.newDfp(tst).subtract(DfpMath.tan(field.newDfp(x))).divide(field.newDfp(ulp)).toDouble();
+//                System.out.println(x + "\t" + tst + "\t" + ref + "\t" + err + "\t" + errulp);
+
+                maxerrulp = Math.max(maxerrulp, Math.abs(errulp));
+            }
+        }
+
+        Assert.assertTrue("tan() had errors in excess of " + MAX_ERROR_ULP + " ULP", maxerrulp < MAX_ERROR_ULP);
+    }
+
+    @Test
+    public void testAtanAccuracy() {
+        double maxerrulp = 0.0;
+
+        for (int i = 0; i < NUMBER_OF_TRIALS; i++) {
+            /* double x = 1.0 + i/1024.0/2.0; */
+            // double x = ((generator.nextDouble() * 1416.0) - 708.0) * generator.nextDouble();
+            // double x = ((generator.nextDouble() * Math.PI) - Math.PI/2.0) *
+            // generator.nextDouble();
+            double x = ((generator.nextDouble() * 16.0) - 8.0) * generator.nextDouble();
+
+            // double x = (generator.nextDouble() * 20.0) - 10.0;
+            // double x = ((generator.nextDouble() * 2.0) - 1.0) * generator.nextDouble();
+            /* double x = 3.0 / 512.0 * i - 3.0; */
+            double tst = FastMath.atan(x);
+            double ref = DfpMath.atan(field.newDfp(x)).toDouble();
+            double err = (tst - ref) / ref;
+
+            if (err != 0) {
+                double ulp = Math.abs(ref -
+                                      Double.longBitsToDouble((Double.doubleToLongBits(ref) ^ 1)));
+                double errulp = field.newDfp(tst).subtract(DfpMath.atan(field.newDfp(x))).divide(field.newDfp(ulp)).toDouble();
+//                System.out.println(x + "\t" + tst + "\t" + ref + "\t" + err + "\t" + errulp);
+
+                maxerrulp = Math.max(maxerrulp, Math.abs(errulp));
+            }
+        }
+
+        Assert.assertTrue("atan() had errors in excess of " + MAX_ERROR_ULP + " ULP", maxerrulp < MAX_ERROR_ULP);
+    }
+
+    @Test
+    public void testAtan2Accuracy() {
+        double maxerrulp = 0.0;
+
+        for (int i = 0; i < NUMBER_OF_TRIALS; i++) {
+            /* double x = 1.0 + i/1024.0/2.0; */
+            // double x = ((generator.nextDouble() * 1416.0) - 708.0) * generator.nextDouble();
+            double x = generator.nextDouble() - 0.5;
+            double y = generator.nextDouble() - 0.5;
+            // double x = (generator.nextDouble() * 20.0) - 10.0;
+            // double x = ((generator.nextDouble() * 2.0) - 1.0) * generator.nextDouble();
+            /* double x = 3.0 / 512.0 * i - 3.0; */
+            double tst = FastMath.atan2(y, x);
+            Dfp refdfp = DfpMath.atan(field.newDfp(y)
+                .divide(field.newDfp(x)));
+            /* Make adjustments for sign */
+            if (x < 0.0) {
+                if (y > 0.0)
+                    refdfp = field.getPi().add(refdfp);
+                else
+                    refdfp = refdfp.subtract(field.getPi());
+            }
+
+            double ref = refdfp.toDouble();
+            double err = (tst - ref) / ref;
+
+            if (err != 0) {
+                double ulp = Math.abs(ref -
+                                      Double.longBitsToDouble((Double
+                                          .doubleToLongBits(ref) ^ 1)));
+                double errulp = field.newDfp(tst).subtract(refdfp).divide(field.newDfp(ulp)).toDouble();
+//                System.out.println(x + "\t" + y + "\t" + tst + "\t" + ref + "\t" + errulp);
+
+                maxerrulp = Math.max(maxerrulp, Math.abs(errulp));
+            }
+        }
+
+        Assert.assertTrue("atan2() had errors in excess of " + MAX_ERROR_ULP + " ULP", maxerrulp < MAX_ERROR_ULP);
+    }
+
+    @Test
+    public void testExpm1Accuracy() {
+        double maxerrulp = 0.0;
+
+        for (int i = 0; i < NUMBER_OF_TRIALS; i++) {
+            /* double x = 1.0 + i/1024.0/2.0; */
+            // double x = (generator.nextDouble() * 20.0) - 10.0;
+            double x = ((generator.nextDouble() * 16.0) - 8.0) * generator.nextDouble();
+            /* double x = 3.0 / 512.0 * i - 3.0; */
+            double tst = FastMath.expm1(x);
+            double ref = DfpMath.exp(field.newDfp(x)).subtract(field.getOne()).toDouble();
+            double err = (tst - ref) / ref;
+
+            if (err != 0) {
+                double ulp = Math.abs(ref -
+                                      Double.longBitsToDouble((Double
+                                          .doubleToLongBits(ref) ^ 1)));
+                double errulp = field.newDfp(tst).subtract(DfpMath.exp(field.newDfp(x)).subtract(field.getOne())).divide(field.newDfp(ulp)).toDouble();
+//                System.out.println(x + "\t" + tst + "\t" + ref + "\t" + err + "\t" + errulp);
+
+                maxerrulp = Math.max(maxerrulp, Math.abs(errulp));
+            }
+        }
+
+        Assert.assertTrue("expm1() had errors in excess of " + MAX_ERROR_ULP + " ULP", maxerrulp < MAX_ERROR_ULP);
+    }
+
+    @Test
+    public void testAsinAccuracy() {
+        double maxerrulp = 0.0;
+
+        for (int i=0; i<10000; i++) {
+            double x = ((generator.nextDouble() * 2.0) - 1.0) * generator.nextDouble(); 
+
+            double tst = FastMath.asin(x);
+            double ref = DfpMath.asin(field.newDfp(x)).toDouble();
+            double err = (tst - ref) / ref;
+
+            if (err != 0) {
+                double ulp = Math.abs(ref - Double.longBitsToDouble((Double.doubleToLongBits(ref) ^ 1)));
+                double errulp = field.newDfp(tst).subtract(DfpMath.asin(field.newDfp(x))).divide(field.newDfp(ulp)).toDouble();
+                //System.out.println(x+"\t"+tst+"\t"+ref+"\t"+err+"\t"+errulp);
+
+                maxerrulp = Math.max(maxerrulp, Math.abs(errulp));
+            }
+        }
+
+        Assert.assertTrue("asin() had errors in excess of " + MAX_ERROR_ULP + " ULP", maxerrulp < MAX_ERROR_ULP);
+    }
+
+    @Test
+    public void testAcosAccuracy() {
+        double maxerrulp = 0.0;
+
+        for (int i=0; i<10000; i++) {
+            double x = ((generator.nextDouble() * 2.0) - 1.0) * generator.nextDouble(); 
+
+            double tst = FastMath.acos(x);
+            double ref = DfpMath.acos(field.newDfp(x)).toDouble();
+            double err = (tst - ref) / ref;
+
+            if (err != 0) {
+                double ulp = Math.abs(ref - Double.longBitsToDouble((Double.doubleToLongBits(ref) ^ 1)));
+                double errulp = field.newDfp(tst).subtract(DfpMath.acos(field.newDfp(x))).divide(field.newDfp(ulp)).toDouble();
+                //System.out.println(x+"\t"+tst+"\t"+ref+"\t"+err+"\t"+errulp);
+
+                maxerrulp = Math.max(maxerrulp, Math.abs(errulp));
+            }
+        }
+
+        Assert.assertTrue("acos() had errors in excess of " + MAX_ERROR_ULP + " ULP", maxerrulp < MAX_ERROR_ULP);
+    }
+
+    private Dfp cosh(Dfp x) {
+      return DfpMath.exp(x).add(DfpMath.exp(x.negate())).divide(2);
+    }
+
+    private Dfp sinh(Dfp x) {
+      return DfpMath.exp(x).subtract(DfpMath.exp(x.negate())).divide(2);
+    }
+
+    private Dfp tanh(Dfp x) {
+      return sinh(x).divide(cosh(x));
+    }
+
+    @Test
+    public void testSinhAccuracy() {
+        double maxerrulp = 0.0;
+
+        for (int i=0; i<10000; i++) {
+            double x = ((generator.nextDouble() * 16.0) - 8.0) * generator.nextDouble(); 
+
+            double tst = FastMath.sinh(x);
+            double ref = sinh(field.newDfp(x)).toDouble();
+            double err = (tst - ref) / ref;
+
+            if (err != 0) {
+                double ulp = Math.abs(ref - Double.longBitsToDouble((Double.doubleToLongBits(ref) ^ 1)));
+                double errulp = field.newDfp(tst).subtract(sinh(field.newDfp(x))).divide(field.newDfp(ulp)).toDouble(); 
+                //System.out.println(x+"\t"+tst+"\t"+ref+"\t"+err+"\t"+errulp); 
+                maxerrulp = Math.max(maxerrulp, Math.abs(errulp));
+            }
+        }
+
+        Assert.assertTrue("sinh() had errors in excess of " + MAX_ERROR_ULP + " ULP", maxerrulp < MAX_ERROR_ULP);
+    }
+
+    @Test
+    public void testCoshAccuracy() {
+        double maxerrulp = 0.0;
+
+        for (int i=0; i<10000; i++) {
+            double x = ((generator.nextDouble() * 16.0) - 8.0) * generator.nextDouble(); 
+
+            double tst = FastMath.cosh(x);
+            double ref = cosh(field.newDfp(x)).toDouble();
+            double err = (tst - ref) / ref;
+
+            if (err != 0) {
+                double ulp = Math.abs(ref - Double.longBitsToDouble((Double.doubleToLongBits(ref) ^ 1)));
+                double errulp = field.newDfp(tst).subtract(cosh(field.newDfp(x))).divide(field.newDfp(ulp)).toDouble(); 
+                //System.out.println(x+"\t"+tst+"\t"+ref+"\t"+err+"\t"+errulp); 
+                maxerrulp = Math.max(maxerrulp, Math.abs(errulp));
+            }
+        }
+
+        Assert.assertTrue("cosh() had errors in excess of " + MAX_ERROR_ULP + " ULP", maxerrulp < MAX_ERROR_ULP);
+    }
+
+    @Test
+    public void testTanhAccuracy() {
+        double maxerrulp = 0.0;
+
+        for (int i=0; i<10000; i++) {
+            double x = ((generator.nextDouble() * 16.0) - 8.0) * generator.nextDouble(); 
+
+            double tst = FastMath.tanh(x);
+            double ref = tanh(field.newDfp(x)).toDouble();
+            double err = (tst - ref) / ref;
+
+            if (err != 0) {
+                double ulp = Math.abs(ref - Double.longBitsToDouble((Double.doubleToLongBits(ref) ^ 1)));
+                double errulp = field.newDfp(tst).subtract(tanh(field.newDfp(x))).divide(field.newDfp(ulp)).toDouble(); 
+                //System.out.println(x+"\t"+tst+"\t"+ref+"\t"+err+"\t"+errulp); 
+                maxerrulp = Math.max(maxerrulp, Math.abs(errulp));
+            }
+        }
+
+        Assert.assertTrue("tanh() had errors in excess of " + MAX_ERROR_ULP + " ULP", maxerrulp < MAX_ERROR_ULP);
+    }
+
+    @Test
+    public void testCbrtAccuracy() {
+        double maxerrulp = 0.0;
+
+        for (int i=0; i<10000; i++) {
+            double x = ((generator.nextDouble() * 200.0) - 100.0) * generator.nextDouble(); 
+
+            double tst = FastMath.cbrt(x);
+            double ref = cbrt(field.newDfp(x)).toDouble();
+            double err = (tst - ref) / ref;
+
+            if (err != 0) {
+                double ulp = Math.abs(ref - Double.longBitsToDouble((Double.doubleToLongBits(ref) ^ 1)));
+                double errulp = field.newDfp(tst).subtract(cbrt(field.newDfp(x))).divide(field.newDfp(ulp)).toDouble(); 
+                //System.out.println(x+"\t"+tst+"\t"+ref+"\t"+err+"\t"+errulp); 
+                maxerrulp = Math.max(maxerrulp, Math.abs(errulp));
+            }
+        }
+
+        Assert.assertTrue("cbrt() had errors in excess of " + MAX_ERROR_ULP + " ULP", maxerrulp < MAX_ERROR_ULP);
+    }
+
+    private Dfp cbrt(Dfp x) {
+      boolean negative=false;
+
+      if (x.lessThan(field.getZero())) {
+          negative = true;
+          x = x.negate();
+      }
+
+      Dfp y = DfpMath.pow(x, field.getOne().divide(3));
+
+      if (negative) {
+          y = y.negate();
+      }
+
+      return y;
+    }
+
+    @Test
+    public void testToDegrees() {
+        double maxerrulp = 0.0;
+        for (int i = 0; i < NUMBER_OF_TRIALS; i++) {
+            double x = generator.nextDouble();
+            double tst = field.newDfp(x).multiply(180).divide(field.getPi()).toDouble();
+            double ref = FastMath.toDegrees(x);
+            double err = (tst - ref) / ref;
+
+            if (err != 0) {
+                double ulp = Math.abs(ref -
+                                      Double.longBitsToDouble((Double.doubleToLongBits(ref) ^ 1)));
+                double errulp = field.newDfp(tst).subtract(DfpMath.exp(field.newDfp(x)).subtract(field.getOne())).divide(field.newDfp(ulp)).toDouble();
+//                System.out.println(x + "\t" + tst + "\t" + ref + "\t" + err + "\t" + errulp);
+
+                maxerrulp = Math.max(maxerrulp, Math.abs(errulp));
+            }
+        }
+        Assert.assertTrue("toDegrees() had errors in excess of " + MAX_ERROR_ULP + " ULP", maxerrulp < MAX_ERROR_ULP);
+
+    }
+
+    @Test
+    public void testToRadians() {
+        double maxerrulp = 0.0;
+        for (int i = 0; i < NUMBER_OF_TRIALS; i++) {
+            double x = generator.nextDouble();
+            double tst = field.newDfp(x).multiply(field.getPi()).divide(180).toDouble();
+            double ref = FastMath.toRadians(x);
+            double err = (tst - ref) / ref;
+
+            if (err != 0) {
+                double ulp = Math.abs(ref -
+                                      Double.longBitsToDouble((Double
+                                          .doubleToLongBits(ref) ^ 1)));
+                double errulp = field.newDfp(tst).subtract(DfpMath.exp(field.newDfp(x)).subtract(field.getOne())).divide(field.newDfp(ulp)).toDouble();
+//                System.out.println(x + "\t" + tst + "\t" + ref + "\t" + err + "\t" + errulp);
+
+                maxerrulp = Math.max(maxerrulp, Math.abs(errulp));
+            }
+        }
+        Assert.assertTrue("toRadians() had errors in excess of " + MAX_ERROR_ULP + " ULP", maxerrulp < MAX_ERROR_ULP);
+
+    }
+
+    @Test
+    public void testNextAfter() {
+        // 0x402fffffffffffff 0x404123456789abcd -> 4030000000000000
+        Assert.assertEquals(16.0, FastMath.nextAfter(15.999999999999998, 34.27555555555555), 0.0);
+
+        // 0xc02fffffffffffff 0x404123456789abcd -> c02ffffffffffffe
+        Assert.assertEquals(-15.999999999999996, FastMath.nextAfter(-15.999999999999998, 34.27555555555555), 0.0);
+
+        // 0x402fffffffffffff 0x400123456789abcd -> 402ffffffffffffe
+        Assert.assertEquals(15.999999999999996, FastMath.nextAfter(15.999999999999998, 2.142222222222222), 0.0);
+
+        // 0xc02fffffffffffff 0x400123456789abcd -> c02ffffffffffffe
+        Assert.assertEquals(-15.999999999999996, FastMath.nextAfter(-15.999999999999998, 2.142222222222222), 0.0);
+
+        // 0x4020000000000000 0x404123456789abcd -> 4020000000000001
+        Assert.assertEquals(8.000000000000002, FastMath.nextAfter(8.0, 34.27555555555555), 0.0);
+
+        // 0xc020000000000000 0x404123456789abcd -> c01fffffffffffff
+        Assert.assertEquals(-7.999999999999999, FastMath.nextAfter(-8.0, 34.27555555555555), 0.0);
+
+        // 0x4020000000000000 0x400123456789abcd -> 401fffffffffffff
+        Assert.assertEquals(7.999999999999999, FastMath.nextAfter(8.0, 2.142222222222222), 0.0);
+
+        // 0xc020000000000000 0x400123456789abcd -> c01fffffffffffff
+        Assert.assertEquals(-7.999999999999999, FastMath.nextAfter(-8.0, 2.142222222222222), 0.0);
+
+        // 0x3f2e43753d36a223 0x3f2e43753d36a224 -> 3f2e43753d36a224
+        Assert.assertEquals(2.308922399667661E-4, FastMath.nextAfter(2.3089223996676606E-4, 2.308922399667661E-4), 0.0);
+
+        // 0x3f2e43753d36a223 0x3f2e43753d36a223 -> 3f2e43753d36a223
+        Assert.assertEquals(2.3089223996676606E-4, FastMath.nextAfter(2.3089223996676606E-4, 2.3089223996676606E-4), 0.0);
+
+        // 0x3f2e43753d36a223 0x3f2e43753d36a222 -> 3f2e43753d36a222
+        Assert.assertEquals(2.3089223996676603E-4, FastMath.nextAfter(2.3089223996676606E-4, 2.3089223996676603E-4), 0.0);
+
+        // 0x3f2e43753d36a223 0xbf2e43753d36a224 -> 3f2e43753d36a222
+        Assert.assertEquals(2.3089223996676603E-4, FastMath.nextAfter(2.3089223996676606E-4, -2.308922399667661E-4), 0.0);
+
+        // 0x3f2e43753d36a223 0xbf2e43753d36a223 -> 3f2e43753d36a222
+        Assert.assertEquals(2.3089223996676603E-4, FastMath.nextAfter(2.3089223996676606E-4, -2.3089223996676606E-4), 0.0);
+
+        // 0x3f2e43753d36a223 0xbf2e43753d36a222 -> 3f2e43753d36a222
+        Assert.assertEquals(2.3089223996676603E-4, FastMath.nextAfter(2.3089223996676606E-4, -2.3089223996676603E-4), 0.0);
+
+        // 0xbf2e43753d36a223 0x3f2e43753d36a224 -> bf2e43753d36a222
+        Assert.assertEquals(-2.3089223996676603E-4, FastMath.nextAfter(-2.3089223996676606E-4, 2.308922399667661E-4), 0.0);
+
+        // 0xbf2e43753d36a223 0x3f2e43753d36a223 -> bf2e43753d36a222
+        Assert.assertEquals(-2.3089223996676603E-4, FastMath.nextAfter(-2.3089223996676606E-4, 2.3089223996676606E-4), 0.0);
+
+        // 0xbf2e43753d36a223 0x3f2e43753d36a222 -> bf2e43753d36a222
+        Assert.assertEquals(-2.3089223996676603E-4, FastMath.nextAfter(-2.3089223996676606E-4, 2.3089223996676603E-4), 0.0);
+
+        // 0xbf2e43753d36a223 0xbf2e43753d36a224 -> bf2e43753d36a224
+        Assert.assertEquals(-2.308922399667661E-4, FastMath.nextAfter(-2.3089223996676606E-4, -2.308922399667661E-4), 0.0);
+
+        // 0xbf2e43753d36a223 0xbf2e43753d36a223 -> bf2e43753d36a223
+        Assert.assertEquals(-2.3089223996676606E-4, FastMath.nextAfter(-2.3089223996676606E-4, -2.3089223996676606E-4), 0.0);
+
+        // 0xbf2e43753d36a223 0xbf2e43753d36a222 -> bf2e43753d36a222
+        Assert.assertEquals(-2.3089223996676603E-4, FastMath.nextAfter(-2.3089223996676606E-4, -2.3089223996676603E-4), 0.0);
+
+    }
+
+    @Test
+    public void testDoubleNextAfterSpecialCases() {
+        Assert.assertEquals(-Double.MAX_VALUE,FastMath.nextAfter(Double.NEGATIVE_INFINITY, 0D), 0D);
+        Assert.assertEquals(Double.MAX_VALUE,FastMath.nextAfter(Double.POSITIVE_INFINITY, 0D), 0D);
+        Assert.assertEquals(Double.NaN,FastMath.nextAfter(Double.NaN, 0D), 0D);
+        Assert.assertEquals(Double.POSITIVE_INFINITY,FastMath.nextAfter(Double.MAX_VALUE, Double.POSITIVE_INFINITY), 0D);
+        Assert.assertEquals(Double.NEGATIVE_INFINITY,FastMath.nextAfter(-Double.MAX_VALUE, Double.NEGATIVE_INFINITY), 0D);
+        Assert.assertEquals(Double.MIN_VALUE, FastMath.nextAfter(0D, 1D), 0D);
+        Assert.assertEquals(-Double.MIN_VALUE, FastMath.nextAfter(0D, -1D), 0D);
+        Assert.assertEquals(0D, FastMath.nextAfter(Double.MIN_VALUE, -1), 0D);
+        Assert.assertEquals(0D, FastMath.nextAfter(-Double.MIN_VALUE, 1), 0D);
+    }
+
+    @Test
+    public void testFloatNextAfterSpecialCases() {
+        Assert.assertEquals(-Float.MAX_VALUE,FastMath.nextAfter(Float.NEGATIVE_INFINITY, 0F), 0F);
+        Assert.assertEquals(Float.MAX_VALUE,FastMath.nextAfter(Float.POSITIVE_INFINITY, 0F), 0F);
+        Assert.assertEquals(Float.NaN,FastMath.nextAfter(Float.NaN, 0F), 0F);
+        Assert.assertEquals(Float.POSITIVE_INFINITY,FastMath.nextAfter(Float.MAX_VALUE, Float.POSITIVE_INFINITY), 0F);
+        Assert.assertEquals(Float.NEGATIVE_INFINITY,FastMath.nextAfter(-Float.MAX_VALUE, Float.NEGATIVE_INFINITY), 0F);
+        Assert.assertEquals(Float.MIN_VALUE, FastMath.nextAfter(0F, 1F), 0F);
+        Assert.assertEquals(-Float.MIN_VALUE, FastMath.nextAfter(0F, -1F), 0F);
+        Assert.assertEquals(0F, FastMath.nextAfter(Float.MIN_VALUE, -1F), 0F);
+        Assert.assertEquals(0F, FastMath.nextAfter(-Float.MIN_VALUE, 1F), 0F);
+    }
+
+    @Test
+    public void testDoubleScalbSpecialCases() {
+        Assert.assertEquals(2.5269841324701218E-175,  FastMath.scalb(2.2250738585072014E-308, 442), 0D);
+        Assert.assertEquals(1.307993905256674E297,    FastMath.scalb(1.1102230246251565E-16, 1040), 0D);
+        Assert.assertEquals(7.2520887996488946E-217,  FastMath.scalb(Double.MIN_VALUE,        356), 0D);
+        Assert.assertEquals(8.98846567431158E307,     FastMath.scalb(Double.MIN_VALUE,       2097), 0D);
+        Assert.assertEquals(Double.POSITIVE_INFINITY, FastMath.scalb(Double.MIN_VALUE,       2098), 0D);
+        Assert.assertEquals(1.1125369292536007E-308,  FastMath.scalb(2.225073858507201E-308,   -1), 0D);
+        Assert.assertEquals(1.0E-323,                 FastMath.scalb(Double.MAX_VALUE,      -2097), 0D);
+        Assert.assertEquals(Double.MIN_VALUE,         FastMath.scalb(Double.MAX_VALUE,      -2098), 0D);
+        Assert.assertEquals(0,                        FastMath.scalb(Double.MAX_VALUE,      -2099), 0D);
+        Assert.assertEquals(Double.POSITIVE_INFINITY, FastMath.scalb(Double.POSITIVE_INFINITY, -1000000), 0D);
+        Assert.assertEquals(Double.NEGATIVE_INFINITY, FastMath.scalb(-1.1102230246251565E-16, 1078), 0D);
+        Assert.assertEquals(Double.NEGATIVE_INFINITY, FastMath.scalb(-1.1102230246251565E-16,  1079), 0D);
+        Assert.assertEquals(Double.NEGATIVE_INFINITY, FastMath.scalb(-2.2250738585072014E-308, 2047), 0D);
+        Assert.assertEquals(Double.NEGATIVE_INFINITY, FastMath.scalb(-2.2250738585072014E-308, 2048), 0D);
+        Assert.assertEquals(Double.NEGATIVE_INFINITY, FastMath.scalb(-1.7976931348623157E308,  2147483647), 0D);
+        Assert.assertEquals(Double.POSITIVE_INFINITY, FastMath.scalb( 1.7976931348623157E308,  2147483647), 0D);
+        Assert.assertEquals(Double.NEGATIVE_INFINITY, FastMath.scalb(-1.1102230246251565E-16,  2147483647), 0D);
+        Assert.assertEquals(Double.POSITIVE_INFINITY, FastMath.scalb( 1.1102230246251565E-16,  2147483647), 0D);
+        Assert.assertEquals(Double.NEGATIVE_INFINITY, FastMath.scalb(-2.2250738585072014E-308, 2147483647), 0D);
+        Assert.assertEquals(Double.POSITIVE_INFINITY, FastMath.scalb( 2.2250738585072014E-308, 2147483647), 0D);
+    }
+
+    @Test
+    public void testFloatScalbSpecialCases() {
+        Assert.assertEquals(0f,                       FastMath.scalb(Float.MIN_VALUE,  -30), 0F);
+        Assert.assertEquals(2 * Float.MIN_VALUE,      FastMath.scalb(Float.MIN_VALUE,    1), 0F);
+        Assert.assertEquals(7.555786e22f,             FastMath.scalb(Float.MAX_VALUE,  -52), 0F);
+        Assert.assertEquals(1.7014118e38f,            FastMath.scalb(Float.MIN_VALUE,  276), 0F);
+        Assert.assertEquals(Float.POSITIVE_INFINITY,  FastMath.scalb(Float.MIN_VALUE,  277), 0F);
+        Assert.assertEquals(5.8774718e-39f,           FastMath.scalb(1.1754944e-38f,    -1), 0F);
+        Assert.assertEquals(2 * Float.MIN_VALUE,      FastMath.scalb(Float.MAX_VALUE, -276), 0F);
+        Assert.assertEquals(Float.MIN_VALUE,          FastMath.scalb(Float.MAX_VALUE, -277), 0F);
+        Assert.assertEquals(0,                        FastMath.scalb(Float.MAX_VALUE, -278), 0F);
+        Assert.assertEquals(Float.POSITIVE_INFINITY,  FastMath.scalb(Float.POSITIVE_INFINITY, -1000000), 0F);
+        Assert.assertEquals(-3.13994498e38f,          FastMath.scalb(-1.1e-7f,         151), 0F);
+        Assert.assertEquals(Float.NEGATIVE_INFINITY,  FastMath.scalb(-1.1e-7f,         152), 0F);
+        Assert.assertEquals(Float.POSITIVE_INFINITY,  FastMath.scalb(3.4028235E38f,  2147483647), 0F);
+        Assert.assertEquals(Float.NEGATIVE_INFINITY,  FastMath.scalb(-3.4028235E38f, 2147483647), 0F);
+    }
+
+    private boolean compareClassMethods(Class<?> class1, Class<?> class2){
+        boolean allfound = true;
+        for(Method method1 : class1.getDeclaredMethods()){
+            if (Modifier.isPublic(method1.getModifiers())){
+                Type []params = method1.getGenericParameterTypes();
+                try {
+                    class2.getDeclaredMethod(method1.getName(), (Class[]) params);
+                } catch (NoSuchMethodException e) {
+                    allfound = false;
+                    System.out.println(class2.getSimpleName()+" does not implement: "+method1);
+                }
+            }
+        }
+        return allfound;
+    }
+
+    @Test
+    public void checkMissingFastMathClasses() {
+        boolean ok = compareClassMethods(StrictMath.class, FastMath.class);
+        Assert.assertTrue("FastMath should implement all StrictMath methods", ok);
+    }
+
+    @Ignore
+    @Test
+    public void checkExtraFastMathClasses() {
+        compareClassMethods( FastMath.class, StrictMath.class);
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/util/FastMathTestPerformance.java b/src/test/java/org/apache/commons/math/util/FastMathTestPerformance.java
new file mode 100644
index 0000000..fe4b1ac
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/util/FastMathTestPerformance.java
@@ -0,0 +1,377 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Performance tests for FastMath.
+ * Not enabled by default, as the class does not end in Test.
+ * 
+ * Invoke by running<br/>
+ * {@code mvn test -Dtest=FastMathTestPerformance}<br/>
+ * or by running<br/>
+ * {@code mvn test -Dtest=FastMathTestPerformance -DargLine="-DtestRuns=1234 -server"}<br/>
+ */
+public class FastMathTestPerformance {
+    private static final int RUNS = Integer.parseInt(System.getProperty("testRuns","10000000"));
+
+    // Header format
+    private static final String FMT_HDR = "%-5s %13s %13s %13s Runs=%d Java %s (%s) %s (%s)";
+    // Detail format
+    private static final String FMT_DTL = "%-5s %6d %6.2f %6d %6.2f %6d %6.2f";
+
+    @BeforeClass
+    public static void header() {
+        System.out.println(String.format(FMT_HDR,
+                "Name","StrictMath","FastMath","Math",RUNS,
+                System.getProperty("java.version"),
+                System.getProperty("java.runtime.version","?"),
+                System.getProperty("java.vm.name"),
+                System.getProperty("java.vm.version")
+                ));
+    }
+
+    private static void report(String name, long strictMathTime, long fastMathTime, long mathTime) {
+        long unitTime = strictMathTime;
+        System.out.println(String.format(FMT_DTL,
+                name,
+                strictMathTime / RUNS, (double) strictMathTime / unitTime,
+                fastMathTime / RUNS, (double) fastMathTime / unitTime,
+                mathTime / RUNS, (double) mathTime / unitTime
+                ));
+    }
+    @Test
+    public void testLog() {
+        double x = 0;
+        long time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += StrictMath.log(Math.PI + i/* 1.0 + i/1e9 */);
+        long strictMath = System.nanoTime() - time;
+
+        x = 0;
+        time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += FastMath.log(Math.PI + i/* 1.0 + i/1e9 */);
+        long fastTime = System.nanoTime() - time;
+
+        x = 0;
+        time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += Math.log(Math.PI + i/* 1.0 + i/1e9 */);
+        long mathTime = System.nanoTime() - time;
+
+        report("log",strictMath,fastTime,mathTime);
+    }
+
+    @Test
+    public void testPow() {
+        double x = 0;
+        long time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += StrictMath.pow(Math.PI + i / 1e6, i / 1e6);
+        long strictTime = System.nanoTime() - time;
+
+        x = 0;
+        time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += FastMath.pow(Math.PI + i / 1e6, i / 1e6);
+        long fastTime = System.nanoTime() - time;
+
+        x = 0;
+        time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += Math.pow(Math.PI + i / 1e6, i / 1e6);
+        long mathTime = System.nanoTime() - time;
+        report("pow",strictTime,fastTime,mathTime);
+    }
+
+    @Test
+    public void testExp() {
+        double x = 0;
+        long time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += StrictMath.exp(i / 1000000.0);
+        long strictTime = System.nanoTime() - time;
+
+        x = 0;
+        time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += FastMath.exp(i / 1000000.0);
+        long fastTime = System.nanoTime() - time;
+
+        x = 0;
+        time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += Math.exp(i / 1000000.0);
+        long mathTime = System.nanoTime() - time;
+
+        report("exp",strictTime,fastTime,mathTime);
+    }
+
+    @Test
+    public void testSin() {
+        double x = 0;
+        long time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += StrictMath.sin(i / 1000000.0);
+        long strictTime = System.nanoTime() - time;
+
+        x = 0;
+        time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += FastMath.sin(i / 1000000.0);
+        long fastTime = System.nanoTime() - time;
+
+        x = 0;
+        time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += Math.sin(i / 1000000.0);
+        long mathTime = System.nanoTime() - time;
+
+        report("sin",strictTime,fastTime,mathTime);
+    }
+
+    @Test
+    public void testAsin() {
+        double x = 0;
+        long time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += StrictMath.asin(i / 10000000.0);
+        long strictTime = System.nanoTime() - time;
+
+        x = 0;
+        time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += FastMath.asin(i / 10000000.0);
+        long fastTime = System.nanoTime() - time;
+
+        x = 0;
+        time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += Math.asin(i / 10000000.0);
+        long mathTime = System.nanoTime() - time;
+
+        report("asin",strictTime,fastTime,mathTime);
+    }
+
+    @Test
+    public void testCos() {
+        double x = 0;
+        long time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += StrictMath.cos(i / 1000000.0);
+        long strictTime = System.nanoTime() - time;
+
+        x = 0;
+        time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += FastMath.cos(i / 1000000.0);
+        long fastTime = System.nanoTime() - time;
+
+        x = 0;
+        time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += Math.cos(i / 1000000.0);
+        long mathTime = System.nanoTime() - time;
+
+        report("cos",strictTime,fastTime,mathTime);
+    }
+            
+    @Test
+    public void testAcos() {
+        double x = 0;
+        long time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += StrictMath.acos(i / 10000000.0);
+        long strictTime = System.nanoTime() - time;
+
+        x = 0;
+        time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += FastMath.acos(i / 10000000.0);
+        long fastTime = System.nanoTime() - time;
+
+        x = 0;
+        time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += Math.acos(i / 10000000.0);
+        long mathTime = System.nanoTime() - time;
+        report("acos",strictTime,fastTime,mathTime);
+    }
+
+    @Test
+    public void testTan() {
+        double x = 0;
+        long time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += StrictMath.tan(i / 1000000.0);
+        long strictTime = System.nanoTime() - time;
+
+        x = 0;
+        time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += FastMath.tan(i / 1000000.0);
+        long fastTime = System.nanoTime() - time;
+
+        x = 0;
+        time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += Math.tan(i / 1000000.0);
+        long mathTime = System.nanoTime() - time;
+
+        report("tan",strictTime,fastTime,mathTime);
+    }
+
+    @Test
+    public void testAtan() {
+        double x = 0;
+        long time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += StrictMath.atan(i / 1000000.0);
+        long strictTime = System.nanoTime() - time;
+
+        x = 0;
+        time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += FastMath.atan(i / 1000000.0);
+        long fastTime = System.nanoTime() - time;
+
+        x = 0;
+        time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += Math.atan(i / 1000000.0);
+        long mathTime = System.nanoTime() - time;
+
+        report("atan",strictTime,fastTime,mathTime);
+    }
+     
+    @Test
+    public void testCbrt() {
+        double x = 0;
+        long time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += StrictMath.cbrt(i / 1000000.0);
+        long strictTime = System.nanoTime() - time;
+
+        x = 0;
+        time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += FastMath.cbrt(i / 1000000.0);
+        long fastTime = System.nanoTime() - time;
+
+        x = 0;
+        time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += Math.cbrt(i / 1000000.0);
+        long mathTime = System.nanoTime() - time;
+
+        report("cbrt",strictTime,fastTime,mathTime);
+    }
+
+    @Test
+    public void testCosh() {
+        double x = 0;
+        long time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += StrictMath.cosh(i / 1000000.0);
+        long strictTime = System.nanoTime() - time;
+
+        x = 0;
+        time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += FastMath.cosh(i / 1000000.0);
+        long fastTime = System.nanoTime() - time;
+
+        x = 0;
+        time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += Math.cosh(i / 1000000.0);
+        long mathTime = System.nanoTime() - time;
+
+        report("cosh",strictTime,fastTime,mathTime);
+    }
+
+    @Test
+    public void testSinh() {
+        double x = 0;
+        long time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += StrictMath.sinh(i / 1000000.0);
+        long strictTime = System.nanoTime() - time;
+
+        x = 0;
+        time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += FastMath.sinh(i / 1000000.0);
+        long fastTime = System.nanoTime() - time;
+
+        x = 0;
+        time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += Math.sinh(i / 1000000.0);
+        long mathTime = System.nanoTime() - time;
+
+        report("sinh",strictTime,fastTime,mathTime);
+    }
+
+    @Test
+    public void testTanh() {
+        double x = 0;
+        long time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += StrictMath.tanh(i / 1000000.0);
+        long strictTime = System.nanoTime() - time;
+
+        x = 0;
+        time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += FastMath.tanh(i / 1000000.0);
+        long fastTime = System.nanoTime() - time;
+
+        x = 0;
+        time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += Math.tanh(i / 1000000.0);
+        long mathTime = System.nanoTime() - time;
+
+        report("tanh",strictTime,fastTime,mathTime);
+    }
+     
+    @Test
+    public void testExpm1() {
+        double x = 0;
+        long time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += StrictMath.expm1(-i / 100000.0);
+        long strictTime = System.nanoTime() - time;
+
+        x = 0;
+        time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += FastMath.expm1(-i / 100000.0);
+        long fastTime = System.nanoTime() - time;
+
+        x = 0;
+        time = System.nanoTime();
+        for (int i = 0; i < RUNS; i++)
+            x += Math.expm1(-i / 100000.0);
+        long mathTime = System.nanoTime() - time;
+        report("expm1",strictTime,fastTime,mathTime);
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/util/MathUtilsTest.java b/src/test/java/org/apache/commons/math/util/MathUtilsTest.java
new file mode 100644
index 0000000..30fe299
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/util/MathUtilsTest.java
@@ -0,0 +1,1640 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law
+ * or agreed to in writing, software distributed under the License is
+ * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * 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.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.math.TestUtils;
+import org.apache.commons.math.exception.NonMonotonousSequenceException;
+import org.apache.commons.math.random.RandomDataImpl;
+
+/**
+ * Test cases for the MathUtils class.
+ * @version $Revision: 1070122 $ $Date: 2007-08-16 15:36:33 -0500 (Thu, 16 Aug
+ *          2007) $
+ */
+public final class MathUtilsTest extends TestCase {
+
+    public MathUtilsTest(String name) {
+        super(name);
+    }
+
+    /** cached binomial coefficients */
+    private static final List<Map<Integer, Long>> binomialCache = new ArrayList<Map<Integer, Long>>();
+
+    /**
+     * Exact (caching) recursive implementation to test against
+     */
+    private long binomialCoefficient(int n, int k) throws ArithmeticException {
+        if (binomialCache.size() > n) {
+            Long cachedResult = binomialCache.get(n).get(Integer.valueOf(k));
+            if (cachedResult != null) {
+                return cachedResult.longValue();
+            }
+        }
+        long result = -1;
+        if ((n == k) || (k == 0)) {
+            result = 1;
+        } else if ((k == 1) || (k == n - 1)) {
+            result = n;
+        } else {
+            // Reduce stack depth for larger values of n
+            if (k < n - 100) {
+                binomialCoefficient(n - 100, k);
+            }
+            if (k > 100) {
+                binomialCoefficient(n - 100, k - 100);
+            }
+            result = MathUtils.addAndCheck(binomialCoefficient(n - 1, k - 1),
+                binomialCoefficient(n - 1, k));
+        }
+        if (result == -1) {
+            throw new ArithmeticException(
+                "error computing binomial coefficient");
+        }
+        for (int i = binomialCache.size(); i < n + 1; i++) {
+            binomialCache.add(new HashMap<Integer, Long>());
+        }
+        binomialCache.get(n).put(Integer.valueOf(k), Long.valueOf(result));
+        return result;
+    }
+
+    /**
+     * Exact direct multiplication implementation to test against
+     */
+    private long factorial(int n) {
+        long result = 1;
+        for (int i = 2; i <= n; i++) {
+            result *= i;
+        }
+        return result;
+    }
+
+    /** Verify that b(0,0) = 1 */
+    public void test0Choose0() {
+        assertEquals(MathUtils.binomialCoefficientDouble(0, 0), 1d, 0);
+        assertEquals(MathUtils.binomialCoefficientLog(0, 0), 0d, 0);
+        assertEquals(MathUtils.binomialCoefficient(0, 0), 1);
+    }
+
+    public void testAddAndCheck() {
+        int big = Integer.MAX_VALUE;
+        int bigNeg = Integer.MIN_VALUE;
+        assertEquals(big, MathUtils.addAndCheck(big, 0));
+        try {
+            MathUtils.addAndCheck(big, 1);
+            fail("Expecting ArithmeticException");
+        } catch (ArithmeticException ex) {
+        }
+        try {
+            MathUtils.addAndCheck(bigNeg, -1);
+            fail("Expecting ArithmeticException");
+        } catch (ArithmeticException ex) {
+        }
+    }
+
+    public void testAddAndCheckLong() {
+        long max = Long.MAX_VALUE;
+        long min = Long.MIN_VALUE;
+        assertEquals(max, MathUtils.addAndCheck(max, 0L));
+        assertEquals(min, MathUtils.addAndCheck(min, 0L));
+        assertEquals(max, MathUtils.addAndCheck(0L, max));
+        assertEquals(min, MathUtils.addAndCheck(0L, min));
+        assertEquals(1, MathUtils.addAndCheck(-1L, 2L));
+        assertEquals(1, MathUtils.addAndCheck(2L, -1L));
+        assertEquals(-3, MathUtils.addAndCheck(-2L, -1L));
+        assertEquals(min, MathUtils.addAndCheck(min + 1, -1L));
+        testAddAndCheckLongFailure(max, 1L);
+        testAddAndCheckLongFailure(min, -1L);
+        testAddAndCheckLongFailure(1L, max);
+        testAddAndCheckLongFailure(-1L, min);
+    }
+
+    private void testAddAndCheckLongFailure(long a, long b) {
+        try {
+            MathUtils.addAndCheck(a, b);
+            fail("Expecting ArithmeticException");
+        } catch (ArithmeticException ex) {
+            // success
+        }
+    }
+
+    public void testBinomialCoefficient() {
+        long[] bcoef5 = {
+            1,
+            5,
+            10,
+            10,
+            5,
+            1 };
+        long[] bcoef6 = {
+            1,
+            6,
+            15,
+            20,
+            15,
+            6,
+            1 };
+        for (int i = 0; i < 6; i++) {
+            assertEquals("5 choose " + i, bcoef5[i], MathUtils.binomialCoefficient(5, i));
+        }
+        for (int i = 0; i < 7; i++) {
+            assertEquals("6 choose " + i, bcoef6[i], MathUtils.binomialCoefficient(6, i));
+        }
+
+        for (int n = 1; n < 10; n++) {
+            for (int k = 0; k <= n; k++) {
+                assertEquals(n + " choose " + k, binomialCoefficient(n, k), MathUtils.binomialCoefficient(n, k));
+                assertEquals(n + " choose " + k, binomialCoefficient(n, k), MathUtils.binomialCoefficientDouble(n, k), Double.MIN_VALUE);
+                assertEquals(n + " choose " + k, FastMath.log(binomialCoefficient(n, k)), MathUtils.binomialCoefficientLog(n, k), 10E-12);
+            }
+        }
+
+        int[] n = { 34, 66, 100, 1500, 1500 };
+        int[] k = { 17, 33, 10, 1500 - 4, 4 };
+        for (int i = 0; i < n.length; i++) {
+            long expected = binomialCoefficient(n[i], k[i]);
+            assertEquals(n[i] + " choose " + k[i], expected,
+                MathUtils.binomialCoefficient(n[i], k[i]));
+            assertEquals(n[i] + " choose " + k[i], expected,
+                MathUtils.binomialCoefficientDouble(n[i], k[i]), 0.0);
+            assertEquals("log(" + n[i] + " choose " + k[i] + ")", FastMath.log(expected),
+                MathUtils.binomialCoefficientLog(n[i], k[i]), 0.0);
+        }
+    }
+
+    /**
+     * Tests correctness for large n and sharpness of upper bound in API doc
+     * JIRA: MATH-241
+     */
+    public void testBinomialCoefficientLarge() throws Exception {
+        // This tests all legal and illegal values for n <= 200.
+        for (int n = 0; n <= 200; n++) {
+            for (int k = 0; k <= n; k++) {
+                long ourResult = -1;
+                long exactResult = -1;
+                boolean shouldThrow = false;
+                boolean didThrow = false;
+                try {
+                    ourResult = MathUtils.binomialCoefficient(n, k);
+                } catch (ArithmeticException ex) {
+                    didThrow = true;
+                }
+                try {
+                    exactResult = binomialCoefficient(n, k);
+                } catch (ArithmeticException ex) {
+                    shouldThrow = true;
+                }
+                assertEquals(n + " choose " + k, exactResult, ourResult);
+                assertEquals(n + " choose " + k, shouldThrow, didThrow);
+                assertTrue(n + " choose " + k, (n > 66 || !didThrow));
+
+                if (!shouldThrow && exactResult > 1) {
+                    assertEquals(n + " choose " + k, 1.,
+                        MathUtils.binomialCoefficientDouble(n, k) / exactResult, 1e-10);
+                    assertEquals(n + " choose " + k, 1,
+                        MathUtils.binomialCoefficientLog(n, k) / FastMath.log(exactResult), 1e-10);
+                }
+            }
+        }
+
+        long ourResult = MathUtils.binomialCoefficient(300, 3);
+        long exactResult = binomialCoefficient(300, 3);
+        assertEquals(exactResult, ourResult);
+
+        ourResult = MathUtils.binomialCoefficient(700, 697);
+        exactResult = binomialCoefficient(700, 697);
+        assertEquals(exactResult, ourResult);
+
+        // This one should throw
+        try {
+            MathUtils.binomialCoefficient(700, 300);
+            fail("Expecting ArithmeticException");
+        } catch (ArithmeticException ex) {
+            // Expected
+        }
+
+        int n = 10000;
+        ourResult = MathUtils.binomialCoefficient(n, 3);
+        exactResult = binomialCoefficient(n, 3);
+        assertEquals(exactResult, ourResult);
+        assertEquals(1, MathUtils.binomialCoefficientDouble(n, 3) / exactResult, 1e-10);
+        assertEquals(1, MathUtils.binomialCoefficientLog(n, 3) / FastMath.log(exactResult), 1e-10);
+
+    }
+
+    public void testBinomialCoefficientFail() {
+        try {
+            MathUtils.binomialCoefficient(4, 5);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+
+        try {
+            MathUtils.binomialCoefficientDouble(4, 5);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+
+        try {
+            MathUtils.binomialCoefficientLog(4, 5);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+
+        try {
+            MathUtils.binomialCoefficient(-1, -2);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+        try {
+            MathUtils.binomialCoefficientDouble(-1, -2);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+        try {
+            MathUtils.binomialCoefficientLog(-1, -2);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+
+        try {
+            MathUtils.binomialCoefficient(67, 30);
+            fail("expecting ArithmeticException");
+        } catch (ArithmeticException ex) {
+            // ignored
+        }
+        try {
+            MathUtils.binomialCoefficient(67, 34);
+            fail("expecting ArithmeticException");
+        } catch (ArithmeticException ex) {
+            // ignored
+        }
+        double x = MathUtils.binomialCoefficientDouble(1030, 515);
+        assertTrue("expecting infinite binomial coefficient", Double
+            .isInfinite(x));
+    }
+
+    public void testCompareTo() {
+      assertEquals(0, MathUtils.compareTo(152.33, 152.32, .011));
+      assertTrue(MathUtils.compareTo(152.308, 152.32, .011) < 0);
+      assertTrue(MathUtils.compareTo(152.33, 152.318, .011) > 0);
+    }
+
+    public void testCosh() {
+        double x = 3.0;
+        double expected = 10.06766;
+        assertEquals(expected, MathUtils.cosh(x), 1.0e-5);
+    }
+
+    public void testCoshNaN() {
+        assertTrue(Double.isNaN(MathUtils.cosh(Double.NaN)));
+    }
+
+    public void testEquals() {
+        double[] testArray = {
+            Double.NaN,
+            Double.POSITIVE_INFINITY,
+            Double.NEGATIVE_INFINITY,
+            1d,
+            0d };
+        for (int i = 0; i < testArray.length; i++) {
+            for (int j = 0; j < testArray.length; j++) {
+                if (i == j) {
+                    assertTrue(MathUtils.equals(testArray[i], testArray[j]));
+                    assertTrue(MathUtils.equals(testArray[j], testArray[i]));
+                } else {
+                    assertTrue(!MathUtils.equals(testArray[i], testArray[j]));
+                    assertTrue(!MathUtils.equals(testArray[j], testArray[i]));
+                }
+            }
+        }
+    }
+
+    public void testEqualsIncludingNaN() {
+        double[] testArray = {
+            Double.NaN,
+            Double.POSITIVE_INFINITY,
+            Double.NEGATIVE_INFINITY,
+            1d,
+            0d };
+        for (int i = 0; i < testArray.length; i++) {
+            for (int j = 0; j < testArray.length; j++) {
+                if (i == j) {
+                    assertTrue(MathUtils.equalsIncludingNaN(testArray[i], testArray[j]));
+                    assertTrue(MathUtils.equalsIncludingNaN(testArray[j], testArray[i]));
+                } else {
+                    assertTrue(!MathUtils.equalsIncludingNaN(testArray[i], testArray[j]));
+                    assertTrue(!MathUtils.equalsIncludingNaN(testArray[j], testArray[i]));
+                }
+            }
+        }
+    }
+
+    public void testEqualsWithAllowedDelta() {
+        assertTrue(MathUtils.equals(153.0000, 153.0000, .0625));
+        assertTrue(MathUtils.equals(153.0000, 153.0625, .0625));
+        assertTrue(MathUtils.equals(152.9375, 153.0000, .0625));
+        assertTrue(MathUtils.equals(Double.NaN, Double.NaN, 1.0)); // This will change in 3.0
+        assertTrue(MathUtils.equals(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, 1.0));
+        assertTrue(MathUtils.equals(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, 1.0));
+        assertFalse(MathUtils.equals(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, 1.0));
+        assertFalse(MathUtils.equals(153.0000, 153.0625, .0624));
+        assertFalse(MathUtils.equals(152.9374, 153.0000, .0625));
+    }
+
+    public void testEqualsIncludingNaNWithAllowedDelta() {
+        assertTrue(MathUtils.equalsIncludingNaN(153.0000, 153.0000, .0625));
+        assertTrue(MathUtils.equalsIncludingNaN(153.0000, 153.0625, .0625));
+        assertTrue(MathUtils.equalsIncludingNaN(152.9375, 153.0000, .0625));
+        assertTrue(MathUtils.equalsIncludingNaN(Double.NaN, Double.NaN, 1.0));
+        assertTrue(MathUtils.equalsIncludingNaN(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, 1.0));
+        assertTrue(MathUtils.equalsIncludingNaN(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, 1.0));
+        assertFalse(MathUtils.equalsIncludingNaN(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, 1.0));
+        assertFalse(MathUtils.equalsIncludingNaN(153.0000, 153.0625, .0624));
+        assertFalse(MathUtils.equalsIncludingNaN(152.9374, 153.0000, .0625));
+    }
+
+    // Tests for floating point equality
+    @SuppressWarnings("deprecation") // Math.equals(float, float)
+    public void testFloatEqualsWithAllowedUlps() {
+        assertTrue("+0.0f == -0.0f",MathUtils.equals(0.0f, -0.0f));
+        assertTrue("+0.0f == -0.0f (1 ulp)",MathUtils.equals(0.0f, -0.0f, 1));
+        float oneFloat = 1.0f;
+        // Deprecated method - requires parameters to be strictly equal
+        assertFalse("1.0f != 1.0f + 1 ulp",MathUtils.equals(oneFloat, Float.intBitsToFloat(1 + Float.floatToIntBits(oneFloat))));
+        assertTrue("1.0f == 1.0f + 1 ulp (1 ulp)",MathUtils.equals(oneFloat, Float.intBitsToFloat(1 + Float.floatToIntBits(oneFloat)), 1));
+        assertFalse("1.0f != 1.0f + 2 ulp (1 ulp)",MathUtils.equals(oneFloat, Float.intBitsToFloat(2 + Float.floatToIntBits(oneFloat)), 1));
+
+        assertTrue(MathUtils.equals(153.0f, 153.0f, 1));
+
+        // These tests need adjusting for floating point precision
+//        assertTrue(MathUtils.equals(153.0f, 153.00000000000003f, 1));
+//        assertFalse(MathUtils.equals(153.0f, 153.00000000000006f, 1));
+//        assertTrue(MathUtils.equals(153.0f, 152.99999999999997f, 1));
+//        assertFalse(MathUtils.equals(153f, 152.99999999999994f, 1));
+//
+//        assertTrue(MathUtils.equals(-128.0f, -127.99999999999999f, 1));
+//        assertFalse(MathUtils.equals(-128.0f, -127.99999999999997f, 1));
+//        assertTrue(MathUtils.equals(-128.0f, -128.00000000000003f, 1));
+//        assertFalse(MathUtils.equals(-128.0f, -128.00000000000006f, 1));
+
+        assertTrue(MathUtils.equals(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, 1));
+        assertTrue(MathUtils.equals(Double.MAX_VALUE, Float.POSITIVE_INFINITY, 1));
+
+        assertTrue(MathUtils.equals(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, 1));
+        assertTrue(MathUtils.equals(-Float.MAX_VALUE, Float.NEGATIVE_INFINITY, 1));
+
+        assertFalse(MathUtils.equals(Float.NaN, Float.NaN, 1));
+
+        assertFalse(MathUtils.equals(Float.NEGATIVE_INFINITY, Float.POSITIVE_INFINITY, 100000));
+    }
+
+    public void testEqualsWithAllowedUlps21() { // From 2.1
+        assertTrue(MathUtils.equals(153, 153, 1));
+
+        assertTrue(MathUtils.equals(153, 153.00000000000003, 1));
+        assertFalse(MathUtils.equals(153, 153.00000000000006, 1));
+        assertTrue(MathUtils.equals(153, 152.99999999999997, 1));
+        assertFalse(MathUtils.equals(153, 152.99999999999994, 1));
+
+        assertTrue(MathUtils.equals(-128, -127.99999999999999, 1));
+        assertFalse(MathUtils.equals(-128, -127.99999999999997, 1));
+        assertTrue(MathUtils.equals(-128, -128.00000000000003, 1));
+        assertFalse(MathUtils.equals(-128, -128.00000000000006, 1));
+
+        assertTrue(MathUtils.equals(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, 1));
+        assertTrue(MathUtils.equals(Double.MAX_VALUE, Double.POSITIVE_INFINITY, 1));
+
+        assertTrue(MathUtils.equals(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, 1));
+        assertTrue(MathUtils.equals(-Double.MAX_VALUE, Double.NEGATIVE_INFINITY, 1));
+
+
+        assertTrue(MathUtils.equals(Double.NaN, Double.NaN, 1));
+
+        assertFalse(MathUtils.equals(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, 100000));
+    }
+
+    public void testEqualsWithAllowedUlps() {
+        assertTrue(MathUtils.equals(0.0, -0.0, 1));
+
+        assertTrue(MathUtils.equals(1.0, 1 + FastMath.ulp(1d), 1));
+        assertFalse(MathUtils.equals(1.0, 1 + 2 * FastMath.ulp(1d), 1));
+
+        final double nUp1 = FastMath.nextAfter(1d, Double.POSITIVE_INFINITY);
+        final double nnUp1 = FastMath.nextAfter(nUp1, Double.POSITIVE_INFINITY);
+        assertTrue(MathUtils.equals(1.0, nUp1, 1));
+        assertTrue(MathUtils.equals(nUp1, nnUp1, 1));
+        assertFalse(MathUtils.equals(1.0, nnUp1, 1));
+
+        assertTrue(MathUtils.equals(0.0, FastMath.ulp(0d), 1));
+        assertTrue(MathUtils.equals(0.0, -FastMath.ulp(0d), 1));
+
+        assertTrue(MathUtils.equals(153.0, 153.0, 1));
+
+        assertTrue(MathUtils.equals(153.0, 153.00000000000003, 1));
+        assertFalse(MathUtils.equals(153.0, 153.00000000000006, 1));
+        assertTrue(MathUtils.equals(153.0, 152.99999999999997, 1));
+        assertFalse(MathUtils.equals(153, 152.99999999999994, 1));
+
+        assertTrue(MathUtils.equals(-128.0, -127.99999999999999, 1));
+        assertFalse(MathUtils.equals(-128.0, -127.99999999999997, 1));
+        assertTrue(MathUtils.equals(-128.0, -128.00000000000003, 1));
+        assertFalse(MathUtils.equals(-128.0, -128.00000000000006, 1));
+
+        assertTrue(MathUtils.equals(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, 1));
+        assertTrue(MathUtils.equals(Double.MAX_VALUE, Double.POSITIVE_INFINITY, 1));
+
+        assertTrue(MathUtils.equals(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, 1));
+        assertTrue(MathUtils.equals(-Double.MAX_VALUE, Double.NEGATIVE_INFINITY, 1));
+
+        assertTrue(MathUtils.equals(Double.NaN, Double.NaN, 1)); // This will change in 3.0
+
+        assertFalse(MathUtils.equals(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, 100000));
+    }
+
+    public void testEqualsIncludingNaNWithAllowedUlps() {
+        assertTrue(MathUtils.equalsIncludingNaN(0.0, -0.0, 1));
+
+        assertTrue(MathUtils.equalsIncludingNaN(1.0, 1 + FastMath.ulp(1d), 1));
+        assertFalse(MathUtils.equalsIncludingNaN(1.0, 1 + 2 * FastMath.ulp(1d), 1));
+
+        final double nUp1 = FastMath.nextAfter(1d, Double.POSITIVE_INFINITY);
+        final double nnUp1 = FastMath.nextAfter(nUp1, Double.POSITIVE_INFINITY);
+        assertTrue(MathUtils.equalsIncludingNaN(1.0, nUp1, 1));
+        assertTrue(MathUtils.equalsIncludingNaN(nUp1, nnUp1, 1));
+        assertFalse(MathUtils.equalsIncludingNaN(1.0, nnUp1, 1));
+
+        assertTrue(MathUtils.equalsIncludingNaN(0.0, FastMath.ulp(0d), 1));
+        assertTrue(MathUtils.equalsIncludingNaN(0.0, -FastMath.ulp(0d), 1));
+
+        assertTrue(MathUtils.equalsIncludingNaN(153.0, 153.0, 1));
+
+        assertTrue(MathUtils.equalsIncludingNaN(153.0, 153.00000000000003, 1));
+        assertFalse(MathUtils.equalsIncludingNaN(153.0, 153.00000000000006, 1));
+        assertTrue(MathUtils.equalsIncludingNaN(153.0, 152.99999999999997, 1));
+        assertFalse(MathUtils.equalsIncludingNaN(153, 152.99999999999994, 1));
+
+        assertTrue(MathUtils.equalsIncludingNaN(-128.0, -127.99999999999999, 1));
+        assertFalse(MathUtils.equalsIncludingNaN(-128.0, -127.99999999999997, 1));
+        assertTrue(MathUtils.equalsIncludingNaN(-128.0, -128.00000000000003, 1));
+        assertFalse(MathUtils.equalsIncludingNaN(-128.0, -128.00000000000006, 1));
+
+        assertTrue(MathUtils.equalsIncludingNaN(Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, 1));
+        assertTrue(MathUtils.equalsIncludingNaN(Double.MAX_VALUE, Double.POSITIVE_INFINITY, 1));
+
+        assertTrue(MathUtils.equalsIncludingNaN(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, 1));
+        assertTrue(MathUtils.equalsIncludingNaN(-Double.MAX_VALUE, Double.NEGATIVE_INFINITY, 1));
+
+        assertTrue(MathUtils.equalsIncludingNaN(Double.NaN, Double.NaN, 1));
+
+        assertFalse(MathUtils.equalsIncludingNaN(Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, 100000));
+    }
+
+    @SuppressWarnings("deprecation") // specific tests of deprecated methods
+    public void testArrayEquals() {
+        assertFalse(MathUtils.equals(new double[] { 1d }, null));
+        assertFalse(MathUtils.equals(null, new double[] { 1d }));
+        assertTrue(MathUtils.equals((double[]) null, (double[]) null));
+
+        assertFalse(MathUtils.equals(new double[] { 1d }, new double[0]));
+        assertTrue(MathUtils.equals(new double[] { 1d }, new double[] { 1d }));
+        assertTrue(MathUtils.equals(new double[] {
+                                      Double.NaN, Double.POSITIVE_INFINITY,
+                                      Double.NEGATIVE_INFINITY, 1d, 0d
+                                    }, new double[] {
+                                      Double.NaN, Double.POSITIVE_INFINITY,
+                                      Double.NEGATIVE_INFINITY, 1d, 0d
+                                    }));
+        assertFalse(MathUtils.equals(new double[] { Double.POSITIVE_INFINITY },
+                                     new double[] { Double.NEGATIVE_INFINITY }));
+        assertFalse(MathUtils.equals(new double[] { 1d },
+                                     new double[] { FastMath.nextAfter(1d, 2d) }));
+
+    }
+
+    public void testArrayEqualsIncludingNaN() {
+        assertFalse(MathUtils.equalsIncludingNaN(new double[] { 1d }, null));
+        assertFalse(MathUtils.equalsIncludingNaN(null, new double[] { 1d }));
+        assertTrue(MathUtils.equalsIncludingNaN((double[]) null, (double[]) null));
+
+        assertFalse(MathUtils.equalsIncludingNaN(new double[] { 1d }, new double[0]));
+        assertTrue(MathUtils.equalsIncludingNaN(new double[] { 1d }, new double[] { 1d }));
+        assertTrue(MathUtils.equalsIncludingNaN(new double[] {
+                    Double.NaN, Double.POSITIVE_INFINITY,
+                    Double.NEGATIVE_INFINITY, 1d, 0d
+                }, new double[] {
+                    Double.NaN, Double.POSITIVE_INFINITY,
+                    Double.NEGATIVE_INFINITY, 1d, 0d
+                }));
+        assertFalse(MathUtils.equalsIncludingNaN(new double[] { Double.POSITIVE_INFINITY },
+                                                 new double[] { Double.NEGATIVE_INFINITY }));
+        assertFalse(MathUtils.equalsIncludingNaN(new double[] { 1d },
+                                                 new double[] { FastMath.nextAfter(FastMath.nextAfter(1d, 2d), 2d) }));
+    }
+
+    public void testFloatArrayEqualsIncludingNaN() {
+        assertFalse(MathUtils.equalsIncludingNaN(new float[] { 1f }, null));
+        assertFalse(MathUtils.equalsIncludingNaN(null, new float[] { 1f }));
+        assertTrue(MathUtils.equalsIncludingNaN((float[]) null, (float[]) null));
+
+        assertFalse(MathUtils.equalsIncludingNaN(new float[] { 1f }, new float[0]));
+        assertTrue(MathUtils.equalsIncludingNaN(new float[] { 1f }, new float[] { 1f }));
+        assertTrue(MathUtils.equalsIncludingNaN(new float[] {
+                    Float.NaN, Float.POSITIVE_INFINITY,
+                    Float.NEGATIVE_INFINITY, 1f, 0f
+                }, new float[] {
+                Float.NaN, Float.POSITIVE_INFINITY,
+                Float.NEGATIVE_INFINITY, 1f, 0f
+                }));
+        assertFalse(MathUtils.equalsIncludingNaN(new float[] { Float.POSITIVE_INFINITY },
+                                                 new float[] { Float.NEGATIVE_INFINITY }));
+//        assertFalse(MathUtils.equalsIncludingNaN(new float[] { 1f },
+//                                                 new float[] { FastMath.nextAfter(FastMath.nextAfter(1f, 2f), 2f) }));
+    }
+
+    public void testFactorial() {
+        for (int i = 1; i < 21; i++) {
+            assertEquals(i + "! ", factorial(i), MathUtils.factorial(i));
+            assertEquals(i + "! ", factorial(i), MathUtils.factorialDouble(i), Double.MIN_VALUE);
+            assertEquals(i + "! ", FastMath.log(factorial(i)), MathUtils.factorialLog(i), 10E-12);
+        }
+
+        assertEquals("0", 1, MathUtils.factorial(0));
+        assertEquals("0", 1.0d, MathUtils.factorialDouble(0), 1E-14);
+        assertEquals("0", 0.0d, MathUtils.factorialLog(0), 1E-14);
+    }
+
+    public void testFactorialFail() {
+        try {
+            MathUtils.factorial(-1);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+        try {
+            MathUtils.factorialDouble(-1);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+        try {
+            MathUtils.factorialLog(-1);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // ignored
+        }
+        try {
+            MathUtils.factorial(21);
+            fail("expecting ArithmeticException");
+        } catch (ArithmeticException ex) {
+            // ignored
+        }
+        assertTrue("expecting infinite factorial value", Double.isInfinite(MathUtils.factorialDouble(171)));
+    }
+
+    public void testGcd() {
+        int a = 30;
+        int b = 50;
+        int c = 77;
+
+        assertEquals(0, MathUtils.gcd(0, 0));
+
+        assertEquals(b, MathUtils.gcd(0, b));
+        assertEquals(a, MathUtils.gcd(a, 0));
+        assertEquals(b, MathUtils.gcd(0, -b));
+        assertEquals(a, MathUtils.gcd(-a, 0));
+
+        assertEquals(10, MathUtils.gcd(a, b));
+        assertEquals(10, MathUtils.gcd(-a, b));
+        assertEquals(10, MathUtils.gcd(a, -b));
+        assertEquals(10, MathUtils.gcd(-a, -b));
+
+        assertEquals(1, MathUtils.gcd(a, c));
+        assertEquals(1, MathUtils.gcd(-a, c));
+        assertEquals(1, MathUtils.gcd(a, -c));
+        assertEquals(1, MathUtils.gcd(-a, -c));
+
+        assertEquals(3 * (1<<15), MathUtils.gcd(3 * (1<<20), 9 * (1<<15)));
+
+        assertEquals(Integer.MAX_VALUE, MathUtils.gcd(Integer.MAX_VALUE, 0));
+        assertEquals(Integer.MAX_VALUE, MathUtils.gcd(-Integer.MAX_VALUE, 0));
+        assertEquals(1<<30, MathUtils.gcd(1<<30, -Integer.MIN_VALUE));
+        try {
+            // gcd(Integer.MIN_VALUE, 0) > Integer.MAX_VALUE
+            MathUtils.gcd(Integer.MIN_VALUE, 0);
+            fail("expecting ArithmeticException");
+        } catch (ArithmeticException expected) {
+            // expected
+        }
+        try {
+            // gcd(0, Integer.MIN_VALUE) > Integer.MAX_VALUE
+            MathUtils.gcd(0, Integer.MIN_VALUE);
+            fail("expecting ArithmeticException");
+        } catch (ArithmeticException expected) {
+            // expected
+        }
+        try {
+            // gcd(Integer.MIN_VALUE, Integer.MIN_VALUE) > Integer.MAX_VALUE
+            MathUtils.gcd(Integer.MIN_VALUE, Integer.MIN_VALUE);
+            fail("expecting ArithmeticException");
+        } catch (ArithmeticException expected) {
+            // expected
+        }
+    }
+
+    public void  testGcdLong(){
+        long a = 30;
+        long b = 50;
+        long c = 77;
+
+        assertEquals(0, MathUtils.gcd(0L, 0));
+
+        assertEquals(b, MathUtils.gcd(0, b));
+        assertEquals(a, MathUtils.gcd(a, 0));
+        assertEquals(b, MathUtils.gcd(0, -b));
+        assertEquals(a, MathUtils.gcd(-a, 0));
+
+        assertEquals(10, MathUtils.gcd(a, b));
+        assertEquals(10, MathUtils.gcd(-a, b));
+        assertEquals(10, MathUtils.gcd(a, -b));
+        assertEquals(10, MathUtils.gcd(-a, -b));
+
+        assertEquals(1, MathUtils.gcd(a, c));
+        assertEquals(1, MathUtils.gcd(-a, c));
+        assertEquals(1, MathUtils.gcd(a, -c));
+        assertEquals(1, MathUtils.gcd(-a, -c));
+
+        assertEquals(3L * (1L<<45), MathUtils.gcd(3L * (1L<<50), 9L * (1L<<45)));
+
+        assertEquals(1L<<45, MathUtils.gcd(1L<<45, Long.MIN_VALUE));
+
+        assertEquals(Long.MAX_VALUE, MathUtils.gcd(Long.MAX_VALUE, 0L));
+        assertEquals(Long.MAX_VALUE, MathUtils.gcd(-Long.MAX_VALUE, 0L));
+        assertEquals(1, MathUtils.gcd(60247241209L, 153092023L));
+        try {
+            // gcd(Long.MIN_VALUE, 0) > Long.MAX_VALUE
+            MathUtils.gcd(Long.MIN_VALUE, 0);
+            fail("expecting ArithmeticException");
+        } catch (ArithmeticException expected) {
+            // expected
+        }
+        try {
+            // gcd(0, Long.MIN_VALUE) > Long.MAX_VALUE
+            MathUtils.gcd(0, Long.MIN_VALUE);
+            fail("expecting ArithmeticException");
+        } catch (ArithmeticException expected) {
+            // expected
+        }
+        try {
+            // gcd(Long.MIN_VALUE, Long.MIN_VALUE) > Long.MAX_VALUE
+            MathUtils.gcd(Long.MIN_VALUE, Long.MIN_VALUE);
+            fail("expecting ArithmeticException");
+        } catch (ArithmeticException expected) {
+            // expected
+        }
+    }
+
+    public void testGcdConsistency() {
+        int[] primeList = {19, 23, 53, 67, 73, 79, 101, 103, 111, 131};
+        ArrayList<Integer> primes = new ArrayList<Integer>();
+        for (int i = 0; i < primeList.length; i++) {
+            primes.add(Integer.valueOf(primeList[i]));
+        }
+        RandomDataImpl randomData = new RandomDataImpl();
+        for (int i = 0; i < 20; i++) {
+            Object[] sample = randomData.nextSample(primes, 4);
+            int p1 = ((Integer) sample[0]).intValue();
+            int p2 = ((Integer) sample[1]).intValue();
+            int p3 = ((Integer) sample[2]).intValue();
+            int p4 = ((Integer) sample[3]).intValue();
+            int i1 = p1 * p2 * p3;
+            int i2 = p1 * p2 * p4;
+            int gcd = p1 * p2;
+            assertEquals(gcd, MathUtils.gcd(i1, i2));
+            long l1 = i1;
+            long l2 = i2;
+            assertEquals(gcd, MathUtils.gcd(l1, l2));
+        }
+    }
+
+    public void testHash() {
+        double[] testArray = {
+            Double.NaN,
+            Double.POSITIVE_INFINITY,
+            Double.NEGATIVE_INFINITY,
+            1d,
+            0d,
+            1E-14,
+            (1 + 1E-14),
+            Double.MIN_VALUE,
+            Double.MAX_VALUE };
+        for (int i = 0; i < testArray.length; i++) {
+            for (int j = 0; j < testArray.length; j++) {
+                if (i == j) {
+                    assertEquals(MathUtils.hash(testArray[i]), MathUtils.hash(testArray[j]));
+                    assertEquals(MathUtils.hash(testArray[j]), MathUtils.hash(testArray[i]));
+                } else {
+                    assertTrue(MathUtils.hash(testArray[i]) != MathUtils.hash(testArray[j]));
+                    assertTrue(MathUtils.hash(testArray[j]) != MathUtils.hash(testArray[i]));
+                }
+            }
+        }
+    }
+
+    public void testArrayHash() {
+        assertEquals(0, MathUtils.hash((double[]) null));
+        assertEquals(MathUtils.hash(new double[] {
+                                      Double.NaN, Double.POSITIVE_INFINITY,
+                                      Double.NEGATIVE_INFINITY, 1d, 0d
+                                    }),
+                     MathUtils.hash(new double[] {
+                                      Double.NaN, Double.POSITIVE_INFINITY,
+                                      Double.NEGATIVE_INFINITY, 1d, 0d
+                                    }));
+        assertFalse(MathUtils.hash(new double[] { 1d }) ==
+                    MathUtils.hash(new double[] { FastMath.nextAfter(1d, 2d) }));
+        assertFalse(MathUtils.hash(new double[] { 1d }) ==
+                    MathUtils.hash(new double[] { 1d, 1d }));
+    }
+
+    /**
+     * Make sure that permuted arrays do not hash to the same value.
+     */
+    public void testPermutedArrayHash() {
+        double[] original = new double[10];
+        double[] permuted = new double[10];
+        RandomDataImpl random = new RandomDataImpl();
+
+        // Generate 10 distinct random values
+        for (int i = 0; i < 10; i++) {
+            original[i] = random.nextUniform(i + 0.5, i + 0.75);
+        }
+
+        // Generate a random permutation, making sure it is not the identity
+        boolean isIdentity = true;
+        do {
+            int[] permutation = random.nextPermutation(10, 10);
+            for (int i = 0; i < 10; i++) {
+                if (i != permutation[i]) {
+                    isIdentity = false;
+                }
+                permuted[i] = original[permutation[i]];
+            }
+        } while (isIdentity);
+
+        // Verify that permuted array has different hash
+        assertFalse(MathUtils.hash(original) == MathUtils.hash(permuted));
+    }
+
+    public void testIndicatorByte() {
+        assertEquals((byte)1, MathUtils.indicator((byte)2));
+        assertEquals((byte)1, MathUtils.indicator((byte)0));
+        assertEquals((byte)(-1), MathUtils.indicator((byte)(-2)));
+    }
+
+    public void testIndicatorDouble() {
+        double delta = 0.0;
+        assertEquals(1.0, MathUtils.indicator(2.0), delta);
+        assertEquals(1.0, MathUtils.indicator(0.0), delta);
+        assertEquals(-1.0, MathUtils.indicator(-2.0), delta);
+        assertEquals(Double.NaN, MathUtils.indicator(Double.NaN));
+    }
+
+    public void testIndicatorFloat() {
+        float delta = 0.0F;
+        assertEquals(1.0F, MathUtils.indicator(2.0F), delta);
+        assertEquals(1.0F, MathUtils.indicator(0.0F), delta);
+        assertEquals(-1.0F, MathUtils.indicator(-2.0F), delta);
+    }
+
+    public void testIndicatorInt() {
+        assertEquals(1, MathUtils.indicator((2)));
+        assertEquals(1, MathUtils.indicator((0)));
+        assertEquals((-1), MathUtils.indicator((-2)));
+    }
+
+    public void testIndicatorLong() {
+        assertEquals(1L, MathUtils.indicator(2L));
+        assertEquals(1L, MathUtils.indicator(0L));
+        assertEquals(-1L, MathUtils.indicator(-2L));
+    }
+
+    public void testIndicatorShort() {
+        assertEquals((short)1, MathUtils.indicator((short)2));
+        assertEquals((short)1, MathUtils.indicator((short)0));
+        assertEquals((short)(-1), MathUtils.indicator((short)(-2)));
+    }
+
+    public void testLcm() {
+        int a = 30;
+        int b = 50;
+        int c = 77;
+
+        assertEquals(0, MathUtils.lcm(0, b));
+        assertEquals(0, MathUtils.lcm(a, 0));
+        assertEquals(b, MathUtils.lcm(1, b));
+        assertEquals(a, MathUtils.lcm(a, 1));
+        assertEquals(150, MathUtils.lcm(a, b));
+        assertEquals(150, MathUtils.lcm(-a, b));
+        assertEquals(150, MathUtils.lcm(a, -b));
+        assertEquals(150, MathUtils.lcm(-a, -b));
+        assertEquals(2310, MathUtils.lcm(a, c));
+
+        // Assert that no intermediate value overflows:
+        // The naive implementation of lcm(a,b) would be (a*b)/gcd(a,b)
+        assertEquals((1<<20)*15, MathUtils.lcm((1<<20)*3, (1<<20)*5));
+
+        // Special case
+        assertEquals(0, MathUtils.lcm(0, 0));
+
+        try {
+            // lcm == abs(MIN_VALUE) cannot be represented as a nonnegative int
+            MathUtils.lcm(Integer.MIN_VALUE, 1);
+            fail("Expecting ArithmeticException");
+        } catch (ArithmeticException expected) {
+            // expected
+        }
+
+        try {
+            // lcm == abs(MIN_VALUE) cannot be represented as a nonnegative int
+            MathUtils.lcm(Integer.MIN_VALUE, 1<<20);
+            fail("Expecting ArithmeticException");
+        } catch (ArithmeticException expected) {
+            // expected
+        }
+
+        try {
+            MathUtils.lcm(Integer.MAX_VALUE, Integer.MAX_VALUE - 1);
+            fail("Expecting ArithmeticException");
+        } catch (ArithmeticException expected) {
+            // expected
+        }
+    }
+
+    public void testLcmLong() {
+        long a = 30;
+        long b = 50;
+        long c = 77;
+
+        assertEquals(0, MathUtils.lcm(0, b));
+        assertEquals(0, MathUtils.lcm(a, 0));
+        assertEquals(b, MathUtils.lcm(1, b));
+        assertEquals(a, MathUtils.lcm(a, 1));
+        assertEquals(150, MathUtils.lcm(a, b));
+        assertEquals(150, MathUtils.lcm(-a, b));
+        assertEquals(150, MathUtils.lcm(a, -b));
+        assertEquals(150, MathUtils.lcm(-a, -b));
+        assertEquals(2310, MathUtils.lcm(a, c));
+
+        assertEquals(Long.MAX_VALUE, MathUtils.lcm(60247241209L, 153092023L));
+
+        // Assert that no intermediate value overflows:
+        // The naive implementation of lcm(a,b) would be (a*b)/gcd(a,b)
+        assertEquals((1L<<50)*15, MathUtils.lcm((1L<<45)*3, (1L<<50)*5));
+
+        // Special case
+        assertEquals(0L, MathUtils.lcm(0L, 0L));
+
+        try {
+            // lcm == abs(MIN_VALUE) cannot be represented as a nonnegative int
+            MathUtils.lcm(Long.MIN_VALUE, 1);
+            fail("Expecting ArithmeticException");
+        } catch (ArithmeticException expected) {
+            // expected
+        }
+
+        try {
+            // lcm == abs(MIN_VALUE) cannot be represented as a nonnegative int
+            MathUtils.lcm(Long.MIN_VALUE, 1<<20);
+            fail("Expecting ArithmeticException");
+        } catch (ArithmeticException expected) {
+            // expected
+        }
+
+        assertEquals((long) Integer.MAX_VALUE * (Integer.MAX_VALUE - 1),
+            MathUtils.lcm((long)Integer.MAX_VALUE, Integer.MAX_VALUE - 1));
+        try {
+            MathUtils.lcm(Long.MAX_VALUE, Long.MAX_VALUE - 1);
+            fail("Expecting ArithmeticException");
+        } catch (ArithmeticException expected) {
+            // expected
+        }
+    }
+
+    public void testLog() {
+        assertEquals(2.0, MathUtils.log(2, 4), 0);
+        assertEquals(3.0, MathUtils.log(2, 8), 0);
+        assertTrue(Double.isNaN(MathUtils.log(-1, 1)));
+        assertTrue(Double.isNaN(MathUtils.log(1, -1)));
+        assertTrue(Double.isNaN(MathUtils.log(0, 0)));
+        assertEquals(0, MathUtils.log(0, 10), 0);
+        assertEquals(Double.NEGATIVE_INFINITY, MathUtils.log(10, 0), 0);
+    }
+
+    public void testMulAndCheck() {
+        int big = Integer.MAX_VALUE;
+        int bigNeg = Integer.MIN_VALUE;
+        assertEquals(big, MathUtils.mulAndCheck(big, 1));
+        try {
+            MathUtils.mulAndCheck(big, 2);
+            fail("Expecting ArithmeticException");
+        } catch (ArithmeticException ex) {
+        }
+        try {
+            MathUtils.mulAndCheck(bigNeg, 2);
+            fail("Expecting ArithmeticException");
+        } catch (ArithmeticException ex) {
+        }
+    }
+
+    public void testMulAndCheckLong() {
+        long max = Long.MAX_VALUE;
+        long min = Long.MIN_VALUE;
+        assertEquals(max, MathUtils.mulAndCheck(max, 1L));
+        assertEquals(min, MathUtils.mulAndCheck(min, 1L));
+        assertEquals(0L, MathUtils.mulAndCheck(max, 0L));
+        assertEquals(0L, MathUtils.mulAndCheck(min, 0L));
+        assertEquals(max, MathUtils.mulAndCheck(1L, max));
+        assertEquals(min, MathUtils.mulAndCheck(1L, min));
+        assertEquals(0L, MathUtils.mulAndCheck(0L, max));
+        assertEquals(0L, MathUtils.mulAndCheck(0L, min));
+        assertEquals(1L, MathUtils.mulAndCheck(-1L, -1L));
+        assertEquals(min, MathUtils.mulAndCheck(min / 2, 2));
+        testMulAndCheckLongFailure(max, 2L);
+        testMulAndCheckLongFailure(2L, max);
+        testMulAndCheckLongFailure(min, 2L);
+        testMulAndCheckLongFailure(2L, min);
+        testMulAndCheckLongFailure(min, -1L);
+        testMulAndCheckLongFailure(-1L, min);
+    }
+
+    private void testMulAndCheckLongFailure(long a, long b) {
+        try {
+            MathUtils.mulAndCheck(a, b);
+            fail("Expecting ArithmeticException");
+        } catch (ArithmeticException ex) {
+            // success
+        }
+    }
+
+    public void testNextAfter() { // Test from Math 2.1
+        // 0x402fffffffffffff 0x404123456789abcd -> 4030000000000000
+        assertEquals(16.0, MathUtils.nextAfter(15.999999999999998, 34.27555555555555), 0.0);
+
+        // 0xc02fffffffffffff 0x404123456789abcd -> c02ffffffffffffe
+        assertEquals(-15.999999999999996, MathUtils.nextAfter(-15.999999999999998, 34.27555555555555), 0.0);
+
+        // 0x402fffffffffffff 0x400123456789abcd -> 402ffffffffffffe
+        assertEquals(15.999999999999996, MathUtils.nextAfter(15.999999999999998, 2.142222222222222), 0.0);
+
+        // 0xc02fffffffffffff 0x400123456789abcd -> c02ffffffffffffe
+        assertEquals(-15.999999999999996, MathUtils.nextAfter(-15.999999999999998, 2.142222222222222), 0.0);
+
+        // 0x4020000000000000 0x404123456789abcd -> 4020000000000001
+        assertEquals(8.000000000000002, MathUtils.nextAfter(8.0, 34.27555555555555), 0.0);
+
+        // 0xc020000000000000 0x404123456789abcd -> c01fffffffffffff
+        assertEquals(-7.999999999999999, MathUtils.nextAfter(-8.0, 34.27555555555555), 0.0);
+
+        // 0x4020000000000000 0x400123456789abcd -> 401fffffffffffff
+        assertEquals(7.999999999999999, MathUtils.nextAfter(8.0, 2.142222222222222), 0.0);
+
+        // 0xc020000000000000 0x400123456789abcd -> c01fffffffffffff
+        assertEquals(-7.999999999999999, MathUtils.nextAfter(-8.0, 2.142222222222222), 0.0);
+
+        // 0x3f2e43753d36a223 0x3f2e43753d36a224 -> 3f2e43753d36a224
+        assertEquals(2.308922399667661E-4, MathUtils.nextAfter(2.3089223996676606E-4, 2.308922399667661E-4), 0.0);
+
+        // 0x3f2e43753d36a223 0x3f2e43753d36a223 -> 3f2e43753d36a224
+        assertEquals(2.308922399667661E-4, MathUtils.nextAfter(2.3089223996676606E-4, 2.3089223996676606E-4), 0.0);
+
+        // 0x3f2e43753d36a223 0x3f2e43753d36a222 -> 3f2e43753d36a222
+        assertEquals(2.3089223996676603E-4, MathUtils.nextAfter(2.3089223996676606E-4, 2.3089223996676603E-4), 0.0);
+
+        // 0x3f2e43753d36a223 0xbf2e43753d36a224 -> 3f2e43753d36a222
+        assertEquals(2.3089223996676603E-4, MathUtils.nextAfter(2.3089223996676606E-4, -2.308922399667661E-4), 0.0);
+
+        // 0x3f2e43753d36a223 0xbf2e43753d36a223 -> 3f2e43753d36a222
+        assertEquals(2.3089223996676603E-4, MathUtils.nextAfter(2.3089223996676606E-4, -2.3089223996676606E-4), 0.0);
+
+        // 0x3f2e43753d36a223 0xbf2e43753d36a222 -> 3f2e43753d36a222
+        assertEquals(2.3089223996676603E-4, MathUtils.nextAfter(2.3089223996676606E-4, -2.3089223996676603E-4), 0.0);
+
+        // 0xbf2e43753d36a223 0x3f2e43753d36a224 -> bf2e43753d36a222
+        assertEquals(-2.3089223996676603E-4, MathUtils.nextAfter(-2.3089223996676606E-4, 2.308922399667661E-4), 0.0);
+
+        // 0xbf2e43753d36a223 0x3f2e43753d36a223 -> bf2e43753d36a222
+        assertEquals(-2.3089223996676603E-4, MathUtils.nextAfter(-2.3089223996676606E-4, 2.3089223996676606E-4), 0.0);
+
+        // 0xbf2e43753d36a223 0x3f2e43753d36a222 -> bf2e43753d36a222
+        assertEquals(-2.3089223996676603E-4, MathUtils.nextAfter(-2.3089223996676606E-4, 2.3089223996676603E-4), 0.0);
+
+        // 0xbf2e43753d36a223 0xbf2e43753d36a224 -> bf2e43753d36a224
+        assertEquals(-2.308922399667661E-4, MathUtils.nextAfter(-2.3089223996676606E-4, -2.308922399667661E-4), 0.0);
+
+        // 0xbf2e43753d36a223 0xbf2e43753d36a223 -> bf2e43753d36a224
+        assertEquals(-2.308922399667661E-4, MathUtils.nextAfter(-2.3089223996676606E-4, -2.3089223996676606E-4), 0.0);
+
+        // 0xbf2e43753d36a223 0xbf2e43753d36a222 -> bf2e43753d36a222
+        assertEquals(-2.3089223996676603E-4, MathUtils.nextAfter(-2.3089223996676606E-4, -2.3089223996676603E-4), 0.0);
+
+    }
+
+    public void testNextAfterSpecialCases() {
+        assertTrue(Double.isInfinite(MathUtils.nextAfter(Double.NEGATIVE_INFINITY, 0)));
+        assertTrue(Double.isInfinite(MathUtils.nextAfter(Double.POSITIVE_INFINITY, 0)));
+        assertTrue(Double.isNaN(MathUtils.nextAfter(Double.NaN, 0)));
+        assertTrue(Double.isInfinite(MathUtils.nextAfter(Double.MAX_VALUE, Double.POSITIVE_INFINITY)));
+        assertTrue(Double.isInfinite(MathUtils.nextAfter(-Double.MAX_VALUE, Double.NEGATIVE_INFINITY)));
+        assertEquals(Double.MIN_VALUE, MathUtils.nextAfter(0, 1), 0);
+        assertEquals(-Double.MIN_VALUE, MathUtils.nextAfter(0, -1), 0);
+        assertEquals(0, MathUtils.nextAfter(Double.MIN_VALUE, -1), 0);
+        assertEquals(0, MathUtils.nextAfter(-Double.MIN_VALUE, 1), 0);
+    }
+
+    public void testScalb() { // Math 2.1
+        assertEquals( 0.0, MathUtils.scalb(0.0, 5), 1.0e-15);
+        assertEquals(32.0, MathUtils.scalb(1.0, 5), 1.0e-15);
+        assertEquals(1.0 / 32.0, MathUtils.scalb(1.0,  -5), 1.0e-15);
+        assertEquals(Math.PI, MathUtils.scalb(Math.PI, 0), 1.0e-15);
+        assertTrue(Double.isInfinite(MathUtils.scalb(Double.POSITIVE_INFINITY, 1)));
+        assertTrue(Double.isInfinite(MathUtils.scalb(Double.NEGATIVE_INFINITY, 1)));
+        assertTrue(Double.isNaN(MathUtils.scalb(Double.NaN, 1)));
+    }
+
+    public void testNormalizeAngle() {
+        for (double a = -15.0; a <= 15.0; a += 0.1) {
+            for (double b = -15.0; b <= 15.0; b += 0.2) {
+                double c = MathUtils.normalizeAngle(a, b);
+                assertTrue((b - FastMath.PI) <= c);
+                assertTrue(c <= (b + FastMath.PI));
+                double twoK = FastMath.rint((a - c) / FastMath.PI);
+                assertEquals(c, a - twoK * FastMath.PI, 1.0e-14);
+            }
+        }
+    }
+
+    public void testNormalizeArray() {
+        double[] testValues1 = new double[] {1, 1, 2};
+        TestUtils.assertEquals(
+                new double[] {.25, .25, .5},
+                MathUtils.normalizeArray(testValues1, 1),
+                Double.MIN_VALUE);
+
+        double[] testValues2 = new double[] {-1, -1, 1};
+        TestUtils.assertEquals(
+                new double[] {1, 1, -1},
+                MathUtils.normalizeArray(testValues2, 1),
+                Double.MIN_VALUE);
+
+        // Ignore NaNs
+        double[] testValues3 = new double[] {-1, -1, Double.NaN, 1, Double.NaN};
+        TestUtils.assertEquals(
+                new double[] {1, 1,Double.NaN, -1, Double.NaN},
+                MathUtils.normalizeArray(testValues3, 1),
+                Double.MIN_VALUE);
+
+        // Zero sum -> ArithmeticException
+        double[] zeroSum = new double[] {-1, 1};
+        try {
+            MathUtils.normalizeArray(zeroSum, 1);
+            fail("expecting ArithmeticException");
+        } catch (ArithmeticException ex) {}
+
+        // Infinite elements -> ArithmeticException
+        double[] hasInf = new double[] {1, 2, 1, Double.NEGATIVE_INFINITY};
+        try {
+            MathUtils.normalizeArray(hasInf, 1);
+            fail("expecting ArithmeticException");
+        } catch (ArithmeticException ex) {}
+
+        // Infinite target -> IllegalArgumentException
+        try {
+            MathUtils.normalizeArray(testValues1, Double.POSITIVE_INFINITY);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {}
+
+        // NaN target -> IllegalArgumentException
+        try {
+            MathUtils.normalizeArray(testValues1, Double.NaN);
+            fail("expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {}
+
+    }
+
+    public void testRoundDouble() {
+        double x = 1.234567890;
+        assertEquals(1.23, MathUtils.round(x, 2), 0.0);
+        assertEquals(1.235, MathUtils.round(x, 3), 0.0);
+        assertEquals(1.2346, MathUtils.round(x, 4), 0.0);
+
+        // JIRA MATH-151
+        assertEquals(39.25, MathUtils.round(39.245, 2), 0.0);
+        assertEquals(39.24, MathUtils.round(39.245, 2, BigDecimal.ROUND_DOWN), 0.0);
+        double xx = 39.0;
+        xx = xx + 245d / 1000d;
+        assertEquals(39.25, MathUtils.round(xx, 2), 0.0);
+
+        // BZ 35904
+        assertEquals(30.1d, MathUtils.round(30.095d, 2), 0.0d);
+        assertEquals(30.1d, MathUtils.round(30.095d, 1), 0.0d);
+        assertEquals(33.1d, MathUtils.round(33.095d, 1), 0.0d);
+        assertEquals(33.1d, MathUtils.round(33.095d, 2), 0.0d);
+        assertEquals(50.09d, MathUtils.round(50.085d, 2), 0.0d);
+        assertEquals(50.19d, MathUtils.round(50.185d, 2), 0.0d);
+        assertEquals(50.01d, MathUtils.round(50.005d, 2), 0.0d);
+        assertEquals(30.01d, MathUtils.round(30.005d, 2), 0.0d);
+        assertEquals(30.65d, MathUtils.round(30.645d, 2), 0.0d);
+
+        assertEquals(1.24, MathUtils.round(x, 2, BigDecimal.ROUND_CEILING), 0.0);
+        assertEquals(1.235, MathUtils.round(x, 3, BigDecimal.ROUND_CEILING), 0.0);
+        assertEquals(1.2346, MathUtils.round(x, 4, BigDecimal.ROUND_CEILING), 0.0);
+        assertEquals(-1.23, MathUtils.round(-x, 2, BigDecimal.ROUND_CEILING), 0.0);
+        assertEquals(-1.234, MathUtils.round(-x, 3, BigDecimal.ROUND_CEILING), 0.0);
+        assertEquals(-1.2345, MathUtils.round(-x, 4, BigDecimal.ROUND_CEILING), 0.0);
+
+        assertEquals(1.23, MathUtils.round(x, 2, BigDecimal.ROUND_DOWN), 0.0);
+        assertEquals(1.234, MathUtils.round(x, 3, BigDecimal.ROUND_DOWN), 0.0);
+        assertEquals(1.2345, MathUtils.round(x, 4, BigDecimal.ROUND_DOWN), 0.0);
+        assertEquals(-1.23, MathUtils.round(-x, 2, BigDecimal.ROUND_DOWN), 0.0);
+        assertEquals(-1.234, MathUtils.round(-x, 3, BigDecimal.ROUND_DOWN), 0.0);
+        assertEquals(-1.2345, MathUtils.round(-x, 4, BigDecimal.ROUND_DOWN), 0.0);
+
+        assertEquals(1.23, MathUtils.round(x, 2, BigDecimal.ROUND_FLOOR), 0.0);
+        assertEquals(1.234, MathUtils.round(x, 3, BigDecimal.ROUND_FLOOR), 0.0);
+        assertEquals(1.2345, MathUtils.round(x, 4, BigDecimal.ROUND_FLOOR), 0.0);
+        assertEquals(-1.24, MathUtils.round(-x, 2, BigDecimal.ROUND_FLOOR), 0.0);
+        assertEquals(-1.235, MathUtils.round(-x, 3, BigDecimal.ROUND_FLOOR), 0.0);
+        assertEquals(-1.2346, MathUtils.round(-x, 4, BigDecimal.ROUND_FLOOR), 0.0);
+
+        assertEquals(1.23, MathUtils.round(x, 2, BigDecimal.ROUND_HALF_DOWN), 0.0);
+        assertEquals(1.235, MathUtils.round(x, 3, BigDecimal.ROUND_HALF_DOWN), 0.0);
+        assertEquals(1.2346, MathUtils.round(x, 4, BigDecimal.ROUND_HALF_DOWN), 0.0);
+        assertEquals(-1.23, MathUtils.round(-x, 2, BigDecimal.ROUND_HALF_DOWN), 0.0);
+        assertEquals(-1.235, MathUtils.round(-x, 3, BigDecimal.ROUND_HALF_DOWN), 0.0);
+        assertEquals(-1.2346, MathUtils.round(-x, 4, BigDecimal.ROUND_HALF_DOWN), 0.0);
+        assertEquals(1.234, MathUtils.round(1.2345, 3, BigDecimal.ROUND_HALF_DOWN), 0.0);
+        assertEquals(-1.234, MathUtils.round(-1.2345, 3, BigDecimal.ROUND_HALF_DOWN), 0.0);
+
+        assertEquals(1.23, MathUtils.round(x, 2, BigDecimal.ROUND_HALF_EVEN), 0.0);
+        assertEquals(1.235, MathUtils.round(x, 3, BigDecimal.ROUND_HALF_EVEN), 0.0);
+        assertEquals(1.2346, MathUtils.round(x, 4, BigDecimal.ROUND_HALF_EVEN), 0.0);
+        assertEquals(-1.23, MathUtils.round(-x, 2, BigDecimal.ROUND_HALF_EVEN), 0.0);
+        assertEquals(-1.235, MathUtils.round(-x, 3, BigDecimal.ROUND_HALF_EVEN), 0.0);
+        assertEquals(-1.2346, MathUtils.round(-x, 4, BigDecimal.ROUND_HALF_EVEN), 0.0);
+        assertEquals(1.234, MathUtils.round(1.2345, 3, BigDecimal.ROUND_HALF_EVEN), 0.0);
+        assertEquals(-1.234, MathUtils.round(-1.2345, 3, BigDecimal.ROUND_HALF_EVEN), 0.0);
+        assertEquals(1.236, MathUtils.round(1.2355, 3, BigDecimal.ROUND_HALF_EVEN), 0.0);
+        assertEquals(-1.236, MathUtils.round(-1.2355, 3, BigDecimal.ROUND_HALF_EVEN), 0.0);
+
+        assertEquals(1.23, MathUtils.round(x, 2, BigDecimal.ROUND_HALF_UP), 0.0);
+        assertEquals(1.235, MathUtils.round(x, 3, BigDecimal.ROUND_HALF_UP), 0.0);
+        assertEquals(1.2346, MathUtils.round(x, 4, BigDecimal.ROUND_HALF_UP), 0.0);
+        assertEquals(-1.23, MathUtils.round(-x, 2, BigDecimal.ROUND_HALF_UP), 0.0);
+        assertEquals(-1.235, MathUtils.round(-x, 3, BigDecimal.ROUND_HALF_UP), 0.0);
+        assertEquals(-1.2346, MathUtils.round(-x, 4, BigDecimal.ROUND_HALF_UP), 0.0);
+        assertEquals(1.235, MathUtils.round(1.2345, 3, BigDecimal.ROUND_HALF_UP), 0.0);
+        assertEquals(-1.235, MathUtils.round(-1.2345, 3, BigDecimal.ROUND_HALF_UP), 0.0);
+
+        assertEquals(-1.23, MathUtils.round(-1.23, 2, BigDecimal.ROUND_UNNECESSARY), 0.0);
+        assertEquals(1.23, MathUtils.round(1.23, 2, BigDecimal.ROUND_UNNECESSARY), 0.0);
+
+        try {
+            MathUtils.round(1.234, 2, BigDecimal.ROUND_UNNECESSARY);
+            fail();
+        } catch (ArithmeticException ex) {
+            // success
+        }
+
+        assertEquals(1.24, MathUtils.round(x, 2, BigDecimal.ROUND_UP), 0.0);
+        assertEquals(1.235, MathUtils.round(x, 3, BigDecimal.ROUND_UP), 0.0);
+        assertEquals(1.2346, MathUtils.round(x, 4, BigDecimal.ROUND_UP), 0.0);
+        assertEquals(-1.24, MathUtils.round(-x, 2, BigDecimal.ROUND_UP), 0.0);
+        assertEquals(-1.235, MathUtils.round(-x, 3, BigDecimal.ROUND_UP), 0.0);
+        assertEquals(-1.2346, MathUtils.round(-x, 4, BigDecimal.ROUND_UP), 0.0);
+
+        try {
+            MathUtils.round(1.234, 2, 1923);
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // success
+        }
+
+        // MATH-151
+        assertEquals(39.25, MathUtils.round(39.245, 2, BigDecimal.ROUND_HALF_UP), 0.0);
+
+        // special values
+        TestUtils.assertEquals(Double.NaN, MathUtils.round(Double.NaN, 2), 0.0);
+        assertEquals(0.0, MathUtils.round(0.0, 2), 0.0);
+        assertEquals(Double.POSITIVE_INFINITY, MathUtils.round(Double.POSITIVE_INFINITY, 2), 0.0);
+        assertEquals(Double.NEGATIVE_INFINITY, MathUtils.round(Double.NEGATIVE_INFINITY, 2), 0.0);
+    }
+
+    public void testRoundFloat() {
+        float x = 1.234567890f;
+        assertEquals(1.23f, MathUtils.round(x, 2), 0.0);
+        assertEquals(1.235f, MathUtils.round(x, 3), 0.0);
+        assertEquals(1.2346f, MathUtils.round(x, 4), 0.0);
+
+        // BZ 35904
+        assertEquals(30.1f, MathUtils.round(30.095f, 2), 0.0f);
+        assertEquals(30.1f, MathUtils.round(30.095f, 1), 0.0f);
+        assertEquals(50.09f, MathUtils.round(50.085f, 2), 0.0f);
+        assertEquals(50.19f, MathUtils.round(50.185f, 2), 0.0f);
+        assertEquals(50.01f, MathUtils.round(50.005f, 2), 0.0f);
+        assertEquals(30.01f, MathUtils.round(30.005f, 2), 0.0f);
+        assertEquals(30.65f, MathUtils.round(30.645f, 2), 0.0f);
+
+        assertEquals(1.24f, MathUtils.round(x, 2, BigDecimal.ROUND_CEILING), 0.0);
+        assertEquals(1.235f, MathUtils.round(x, 3, BigDecimal.ROUND_CEILING), 0.0);
+        assertEquals(1.2346f, MathUtils.round(x, 4, BigDecimal.ROUND_CEILING), 0.0);
+        assertEquals(-1.23f, MathUtils.round(-x, 2, BigDecimal.ROUND_CEILING), 0.0);
+        assertEquals(-1.234f, MathUtils.round(-x, 3, BigDecimal.ROUND_CEILING), 0.0);
+        assertEquals(-1.2345f, MathUtils.round(-x, 4, BigDecimal.ROUND_CEILING), 0.0);
+
+        assertEquals(1.23f, MathUtils.round(x, 2, BigDecimal.ROUND_DOWN), 0.0);
+        assertEquals(1.234f, MathUtils.round(x, 3, BigDecimal.ROUND_DOWN), 0.0);
+        assertEquals(1.2345f, MathUtils.round(x, 4, BigDecimal.ROUND_DOWN), 0.0);
+        assertEquals(-1.23f, MathUtils.round(-x, 2, BigDecimal.ROUND_DOWN), 0.0);
+        assertEquals(-1.234f, MathUtils.round(-x, 3, BigDecimal.ROUND_DOWN), 0.0);
+        assertEquals(-1.2345f, MathUtils.round(-x, 4, BigDecimal.ROUND_DOWN), 0.0);
+
+        assertEquals(1.23f, MathUtils.round(x, 2, BigDecimal.ROUND_FLOOR), 0.0);
+        assertEquals(1.234f, MathUtils.round(x, 3, BigDecimal.ROUND_FLOOR), 0.0);
+        assertEquals(1.2345f, MathUtils.round(x, 4, BigDecimal.ROUND_FLOOR), 0.0);
+        assertEquals(-1.24f, MathUtils.round(-x, 2, BigDecimal.ROUND_FLOOR), 0.0);
+        assertEquals(-1.235f, MathUtils.round(-x, 3, BigDecimal.ROUND_FLOOR), 0.0);
+        assertEquals(-1.2346f, MathUtils.round(-x, 4, BigDecimal.ROUND_FLOOR), 0.0);
+
+        assertEquals(1.23f, MathUtils.round(x, 2, BigDecimal.ROUND_HALF_DOWN), 0.0);
+        assertEquals(1.235f, MathUtils.round(x, 3, BigDecimal.ROUND_HALF_DOWN), 0.0);
+        assertEquals(1.2346f, MathUtils.round(x, 4, BigDecimal.ROUND_HALF_DOWN), 0.0);
+        assertEquals(-1.23f, MathUtils.round(-x, 2, BigDecimal.ROUND_HALF_DOWN), 0.0);
+        assertEquals(-1.235f, MathUtils.round(-x, 3, BigDecimal.ROUND_HALF_DOWN), 0.0);
+        assertEquals(-1.2346f, MathUtils.round(-x, 4, BigDecimal.ROUND_HALF_DOWN), 0.0);
+        assertEquals(1.234f, MathUtils.round(1.2345f, 3, BigDecimal.ROUND_HALF_DOWN), 0.0);
+        assertEquals(-1.234f, MathUtils.round(-1.2345f, 3, BigDecimal.ROUND_HALF_DOWN), 0.0);
+
+        assertEquals(1.23f, MathUtils.round(x, 2, BigDecimal.ROUND_HALF_EVEN), 0.0);
+        assertEquals(1.235f, MathUtils.round(x, 3, BigDecimal.ROUND_HALF_EVEN), 0.0);
+        assertEquals(1.2346f, MathUtils.round(x, 4, BigDecimal.ROUND_HALF_EVEN), 0.0);
+        assertEquals(-1.23f, MathUtils.round(-x, 2, BigDecimal.ROUND_HALF_EVEN), 0.0);
+        assertEquals(-1.235f, MathUtils.round(-x, 3, BigDecimal.ROUND_HALF_EVEN), 0.0);
+        assertEquals(-1.2346f, MathUtils.round(-x, 4, BigDecimal.ROUND_HALF_EVEN), 0.0);
+        assertEquals(1.234f, MathUtils.round(1.2345f, 3, BigDecimal.ROUND_HALF_EVEN), 0.0);
+        assertEquals(-1.234f, MathUtils.round(-1.2345f, 3, BigDecimal.ROUND_HALF_EVEN), 0.0);
+        assertEquals(1.236f, MathUtils.round(1.2355f, 3, BigDecimal.ROUND_HALF_EVEN), 0.0);
+        assertEquals(-1.236f, MathUtils.round(-1.2355f, 3, BigDecimal.ROUND_HALF_EVEN), 0.0);
+
+        assertEquals(1.23f, MathUtils.round(x, 2, BigDecimal.ROUND_HALF_UP), 0.0);
+        assertEquals(1.235f, MathUtils.round(x, 3, BigDecimal.ROUND_HALF_UP), 0.0);
+        assertEquals(1.2346f, MathUtils.round(x, 4, BigDecimal.ROUND_HALF_UP), 0.0);
+        assertEquals(-1.23f, MathUtils.round(-x, 2, BigDecimal.ROUND_HALF_UP), 0.0);
+        assertEquals(-1.235f, MathUtils.round(-x, 3, BigDecimal.ROUND_HALF_UP), 0.0);
+        assertEquals(-1.2346f, MathUtils.round(-x, 4, BigDecimal.ROUND_HALF_UP), 0.0);
+        assertEquals(1.235f, MathUtils.round(1.2345f, 3, BigDecimal.ROUND_HALF_UP), 0.0);
+        assertEquals(-1.235f, MathUtils.round(-1.2345f, 3, BigDecimal.ROUND_HALF_UP), 0.0);
+
+        assertEquals(-1.23f, MathUtils.round(-1.23f, 2, BigDecimal.ROUND_UNNECESSARY), 0.0);
+        assertEquals(1.23f, MathUtils.round(1.23f, 2, BigDecimal.ROUND_UNNECESSARY), 0.0);
+
+        try {
+            MathUtils.round(1.234f, 2, BigDecimal.ROUND_UNNECESSARY);
+            fail();
+        } catch (ArithmeticException ex) {
+            // success
+        }
+
+        assertEquals(1.24f, MathUtils.round(x, 2, BigDecimal.ROUND_UP), 0.0);
+        assertEquals(1.235f, MathUtils.round(x, 3, BigDecimal.ROUND_UP), 0.0);
+        assertEquals(1.2346f, MathUtils.round(x, 4, BigDecimal.ROUND_UP), 0.0);
+        assertEquals(-1.24f, MathUtils.round(-x, 2, BigDecimal.ROUND_UP), 0.0);
+        assertEquals(-1.235f, MathUtils.round(-x, 3, BigDecimal.ROUND_UP), 0.0);
+        assertEquals(-1.2346f, MathUtils.round(-x, 4, BigDecimal.ROUND_UP), 0.0);
+
+        try {
+            MathUtils.round(1.234f, 2, 1923);
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // success
+        }
+
+        // special values
+        TestUtils.assertEquals(Float.NaN, MathUtils.round(Float.NaN, 2), 0.0f);
+        assertEquals(0.0f, MathUtils.round(0.0f, 2), 0.0f);
+        assertEquals(Float.POSITIVE_INFINITY, MathUtils.round(Float.POSITIVE_INFINITY, 2), 0.0f);
+        assertEquals(Float.NEGATIVE_INFINITY, MathUtils.round(Float.NEGATIVE_INFINITY, 2), 0.0f);
+    }
+
+    public void testSignByte() {
+        assertEquals((byte) 1, MathUtils.sign((byte) 2));
+        assertEquals((byte) 0, MathUtils.sign((byte) 0));
+        assertEquals((byte) (-1), MathUtils.sign((byte) (-2)));
+    }
+
+    public void testSignDouble() {
+        double delta = 0.0;
+        assertEquals(1.0, MathUtils.sign(2.0), delta);
+        assertEquals(0.0, MathUtils.sign(0.0), delta);
+        assertEquals(-1.0, MathUtils.sign(-2.0), delta);
+        TestUtils.assertSame(-0. / 0., MathUtils.sign(Double.NaN));
+    }
+
+    public void testSignFloat() {
+        float delta = 0.0F;
+        assertEquals(1.0F, MathUtils.sign(2.0F), delta);
+        assertEquals(0.0F, MathUtils.sign(0.0F), delta);
+        assertEquals(-1.0F, MathUtils.sign(-2.0F), delta);
+        TestUtils.assertSame(Float.NaN, MathUtils.sign(Float.NaN));
+    }
+
+    public void testSignInt() {
+        assertEquals(1, MathUtils.sign(2));
+        assertEquals(0, MathUtils.sign(0));
+        assertEquals((-1), MathUtils.sign((-2)));
+    }
+
+    public void testSignLong() {
+        assertEquals(1L, MathUtils.sign(2L));
+        assertEquals(0L, MathUtils.sign(0L));
+        assertEquals(-1L, MathUtils.sign(-2L));
+    }
+
+    public void testSignShort() {
+        assertEquals((short) 1, MathUtils.sign((short) 2));
+        assertEquals((short) 0, MathUtils.sign((short) 0));
+        assertEquals((short) (-1), MathUtils.sign((short) (-2)));
+    }
+
+    public void testSinh() {
+        double x = 3.0;
+        double expected = 10.01787;
+        assertEquals(expected, MathUtils.sinh(x), 1.0e-5);
+    }
+
+    public void testSinhNaN() {
+        assertTrue(Double.isNaN(MathUtils.sinh(Double.NaN)));
+    }
+
+    public void testSubAndCheck() {
+        int big = Integer.MAX_VALUE;
+        int bigNeg = Integer.MIN_VALUE;
+        assertEquals(big, MathUtils.subAndCheck(big, 0));
+        assertEquals(bigNeg + 1, MathUtils.subAndCheck(bigNeg, -1));
+        assertEquals(-1, MathUtils.subAndCheck(bigNeg, -big));
+        try {
+            MathUtils.subAndCheck(big, -1);
+            fail("Expecting ArithmeticException");
+        } catch (ArithmeticException ex) {
+        }
+        try {
+            MathUtils.subAndCheck(bigNeg, 1);
+            fail("Expecting ArithmeticException");
+        } catch (ArithmeticException ex) {
+        }
+    }
+
+    public void testSubAndCheckErrorMessage() {
+        int big = Integer.MAX_VALUE;
+        try {
+            MathUtils.subAndCheck(big, -1);
+            fail("Expecting ArithmeticException");
+        } catch (ArithmeticException ex) {
+            assertTrue(ex.getMessage().length() > 1);
+        }
+    }
+
+    public void testSubAndCheckLong() {
+        long max = Long.MAX_VALUE;
+        long min = Long.MIN_VALUE;
+        assertEquals(max, MathUtils.subAndCheck(max, 0));
+        assertEquals(min, MathUtils.subAndCheck(min, 0));
+        assertEquals(-max, MathUtils.subAndCheck(0, max));
+        assertEquals(min + 1, MathUtils.subAndCheck(min, -1));
+        // min == -1-max
+        assertEquals(-1, MathUtils.subAndCheck(-max - 1, -max));
+        assertEquals(max, MathUtils.subAndCheck(-1, -1 - max));
+        testSubAndCheckLongFailure(0L, min);
+        testSubAndCheckLongFailure(max, -1L);
+        testSubAndCheckLongFailure(min, 1L);
+    }
+
+    private void testSubAndCheckLongFailure(long a, long b) {
+        try {
+            MathUtils.subAndCheck(a, b);
+            fail("Expecting ArithmeticException");
+        } catch (ArithmeticException ex) {
+            // success
+        }
+
+    }
+
+    public void testPow() {
+
+        assertEquals(1801088541, MathUtils.pow(21, 7));
+        assertEquals(1, MathUtils.pow(21, 0));
+        try {
+            MathUtils.pow(21, -7);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected behavior
+        }
+
+        assertEquals(1801088541, MathUtils.pow(21, 7l));
+        assertEquals(1, MathUtils.pow(21, 0l));
+        try {
+            MathUtils.pow(21, -7l);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected behavior
+        }
+
+        assertEquals(1801088541l, MathUtils.pow(21l, 7));
+        assertEquals(1l, MathUtils.pow(21l, 0));
+        try {
+            MathUtils.pow(21l, -7);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected behavior
+        }
+
+        assertEquals(1801088541l, MathUtils.pow(21l, 7l));
+        assertEquals(1l, MathUtils.pow(21l, 0l));
+        try {
+            MathUtils.pow(21l, -7l);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected behavior
+        }
+
+        BigInteger twentyOne = BigInteger.valueOf(21l);
+        assertEquals(BigInteger.valueOf(1801088541l), MathUtils.pow(twentyOne, 7));
+        assertEquals(BigInteger.ONE, MathUtils.pow(twentyOne, 0));
+        try {
+            MathUtils.pow(twentyOne, -7);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected behavior
+        }
+
+        assertEquals(BigInteger.valueOf(1801088541l), MathUtils.pow(twentyOne, 7l));
+        assertEquals(BigInteger.ONE, MathUtils.pow(twentyOne, 0l));
+        try {
+            MathUtils.pow(twentyOne, -7l);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected behavior
+        }
+
+        assertEquals(BigInteger.valueOf(1801088541l), MathUtils.pow(twentyOne, BigInteger.valueOf(7l)));
+        assertEquals(BigInteger.ONE, MathUtils.pow(twentyOne, BigInteger.ZERO));
+        try {
+            MathUtils.pow(twentyOne, BigInteger.valueOf(-7l));
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
+            // expected behavior
+        }
+
+        BigInteger bigOne =
+            new BigInteger("1543786922199448028351389769265814882661837148" +
+                           "4763915343722775611762713982220306372888519211" +
+                           "560905579993523402015636025177602059044911261");
+        assertEquals(bigOne, MathUtils.pow(twentyOne, 103));
+        assertEquals(bigOne, MathUtils.pow(twentyOne, 103l));
+        assertEquals(bigOne, MathUtils.pow(twentyOne, BigInteger.valueOf(103l)));
+
+    }
+
+    public void testL1DistanceDouble() {
+        double[] p1 = { 2.5,  0.0 };
+        double[] p2 = { -0.5, 4.0 };
+        assertEquals(7.0, MathUtils.distance1(p1, p2));
+    }
+
+    public void testL1DistanceInt() {
+        int[] p1 = { 3, 0 };
+        int[] p2 = { 0, 4 };
+        assertEquals(7, MathUtils.distance1(p1, p2));
+    }
+
+    public void testL2DistanceDouble() {
+        double[] p1 = { 2.5,  0.0 };
+        double[] p2 = { -0.5, 4.0 };
+        assertEquals(5.0, MathUtils.distance(p1, p2));
+    }
+
+    public void testL2DistanceInt() {
+        int[] p1 = { 3, 0 };
+        int[] p2 = { 0, 4 };
+        assertEquals(5.0, MathUtils.distance(p1, p2));
+    }
+
+    public void testLInfDistanceDouble() {
+        double[] p1 = { 2.5,  0.0 };
+        double[] p2 = { -0.5, 4.0 };
+        assertEquals(4.0, MathUtils.distanceInf(p1, p2));
+    }
+
+    public void testLInfDistanceInt() {
+        int[] p1 = { 3, 0 };
+        int[] p2 = { 0, 4 };
+        assertEquals(4, MathUtils.distanceInf(p1, p2));
+    }
+
+    public void testCheckOrder21() { // Test from 2.1
+        MathUtils.checkOrder(new double[] {-15, -5.5, -1, 2, 15}, 1, true);
+        MathUtils.checkOrder(new double[] {-15, -5.5, -1, 2, 2}, 1, false);
+        MathUtils.checkOrder(new double[] {3, -5.5, -11, -27.5}, -1, true);
+        MathUtils.checkOrder(new double[] {3, 0, 0, -5.5, -11, -27.5}, -1, false);
+
+        try {
+            MathUtils.checkOrder(new double[] {-15, -5.5, -1, -1, 2, 15}, 1, true);
+            fail("an exception should have been thrown");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+        try {
+            MathUtils.checkOrder(new double[] {-15, -5.5, -1, -2, 2}, 1, false);
+            fail("an exception should have been thrown");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+        try {
+            MathUtils.checkOrder(new double[] {3, 3, -5.5, -11, -27.5}, -1, true);
+            fail("an exception should have been thrown");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+        try {
+            MathUtils.checkOrder(new double[] {3, -1, 0, -5.5, -11, -27.5}, -1, false);
+            fail("an exception should have been thrown");
+        } catch (IllegalArgumentException e) {
+            // Expected
+        }
+    }
+
+    public void testCheckOrder() {
+        MathUtils.checkOrder(new double[] {-15, -5.5, -1, 2, 15},
+                             MathUtils.OrderDirection.INCREASING, true);
+        MathUtils.checkOrder(new double[] {-15, -5.5, -1, 2, 2},
+                             MathUtils.OrderDirection.INCREASING, false);
+        MathUtils.checkOrder(new double[] {3, -5.5, -11, -27.5},
+                             MathUtils.OrderDirection.DECREASING, true);
+        MathUtils.checkOrder(new double[] {3, 0, 0, -5.5, -11, -27.5},
+                             MathUtils.OrderDirection.DECREASING, false);
+
+        try {
+            MathUtils.checkOrder(new double[] {-15, -5.5, -1, -1, 2, 15},
+                                 MathUtils.OrderDirection.INCREASING, true);
+            fail("an exception should have been thrown");
+        } catch (NonMonotonousSequenceException e) {
+            // Expected
+        }
+        try {
+            MathUtils.checkOrder(new double[] {-15, -5.5, -1, -2, 2},
+                                 MathUtils.OrderDirection.INCREASING, false);
+            fail("an exception should have been thrown");
+        } catch (NonMonotonousSequenceException e) {
+            // Expected
+        }
+        try {
+            MathUtils.checkOrder(new double[] {3, 3, -5.5, -11, -27.5},
+                                 MathUtils.OrderDirection.DECREASING, true);
+            fail("an exception should have been thrown");
+        } catch (NonMonotonousSequenceException e) {
+            // Expected
+        }
+        try {
+            MathUtils.checkOrder(new double[] {3, -1, 0, -5.5, -11, -27.5},
+                                 MathUtils.OrderDirection.DECREASING, false);
+            fail("an exception should have been thrown");
+        } catch (NonMonotonousSequenceException e) {
+            // Expected
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/util/MultidimensionalCounterTest.java b/src/test/java/org/apache/commons/math/util/MultidimensionalCounterTest.java
new file mode 100644
index 0000000..827030d
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/util/MultidimensionalCounterTest.java
@@ -0,0 +1,169 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.util;
+
+import org.apache.commons.math.exception.DimensionMismatchException;
+import org.apache.commons.math.exception.OutOfRangeException;
+import org.apache.commons.math.exception.NotStrictlyPositiveException;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ *
+ */
+public class MultidimensionalCounterTest {
+    @Test
+    public void testPreconditions() {
+        MultidimensionalCounter c;
+
+        try {
+            c = new MultidimensionalCounter(0, 1);
+            Assert.fail("NotStrictlyPositiveException expected");
+        } catch (NotStrictlyPositiveException e) {
+            // Expected.
+        }
+        try {
+            c = new MultidimensionalCounter(2, 0);
+            Assert.fail("NotStrictlyPositiveException expected");
+        } catch (NotStrictlyPositiveException e) {
+            // Expected.
+        }
+        try {
+            c = new MultidimensionalCounter(-1, 1);
+            Assert.fail("NotStrictlyPositiveException expected");
+        } catch (NotStrictlyPositiveException e) {
+            // Expected.
+        }
+
+        c = new MultidimensionalCounter(2, 3);
+        try {
+            c.getCount(1, 1, 1);
+            Assert.fail("DimensionMismatchException expected");
+        } catch (DimensionMismatchException e) {
+            // Expected.
+        }
+        try {
+            c.getCount(3, 1);
+            Assert.fail("OutOfRangeException expected");
+        } catch (OutOfRangeException e) {
+            // Expected.
+        }
+        try {
+            c.getCount(0, -1);
+            Assert.fail("OutOfRangeException expected");
+        } catch (OutOfRangeException e) {
+            // Expected.
+        }
+        try {
+            c.getCounts(-1);
+            Assert.fail("OutOfRangeException expected");
+        } catch (OutOfRangeException e) {
+            // Expected.
+        }
+        try {
+            c.getCounts(6);
+            Assert.fail("OutOfRangeException expected");
+        } catch (OutOfRangeException e) {
+            // Expected.
+        }
+    }
+
+    @Test
+    public void testIteratorPreconditions() {
+        MultidimensionalCounter.Iterator iter = (new MultidimensionalCounter(2, 3)).iterator();
+        try {
+            iter.getCount(-1);
+            Assert.fail("IndexOutOfBoundsException expected");
+        } catch (IndexOutOfBoundsException e) {
+            // Expected.
+        }
+        try {
+            iter.getCount(2);
+            Assert.fail("IndexOutOfBoundsException expected");
+        } catch (IndexOutOfBoundsException e) {
+            // Expected.
+        }
+    }
+
+    @Test
+    public void testMulti2UniConversion() {
+        final MultidimensionalCounter c = new MultidimensionalCounter(2, 4, 5);
+        Assert.assertEquals(c.getCount(1, 2, 3), 33);
+    }
+
+    @Test
+    public void testAccessors() {
+        final int[] originalSize = new int[] {2, 6, 5};
+        final MultidimensionalCounter c = new MultidimensionalCounter(originalSize);
+        final int nDim = c.getDimension();
+        Assert.assertEquals(nDim, originalSize.length);
+
+        final int[] size = c.getSizes();
+        for (int i = 0; i < nDim; i++) {
+            Assert.assertEquals(originalSize[i], size[i]);
+        }
+    }
+
+    @Test
+    public void testIterationConsistency() {
+        final MultidimensionalCounter c = new MultidimensionalCounter(2, 3, 2);
+        final int[][] expected = new int[][] {
+            { 0, 0, 0 },
+            { 0, 0, 1 },
+            { 0, 1, 0 },
+            { 0, 1, 1 },
+            { 0, 2, 0 },
+            { 0, 2, 1 },
+            { 1, 0, 0 },
+            { 1, 0, 1 },
+            { 1, 1, 0 },
+            { 1, 1, 1 },
+            { 1, 2, 0 },
+            { 1, 2, 1 }
+        };
+
+        final int totalSize = c.getSize();
+        final int nDim = c.getDimension();
+        final MultidimensionalCounter.Iterator iter = c.iterator();
+        for (int i = 0; i < totalSize; i++) {
+            if (!iter.hasNext()) {
+                Assert.fail("Too short");
+            }
+            final int uniDimIndex = iter.next();
+            Assert.assertEquals("Wrong iteration at " + i, i, uniDimIndex);
+
+            for (int dimIndex = 0; dimIndex < nDim; dimIndex++) {
+                Assert.assertEquals("Wrong multidimensional index for [" + i + "][" + dimIndex + "]",
+                                    expected[i][dimIndex], iter.getCount(dimIndex));
+            }
+
+            Assert.assertEquals("Wrong unidimensional index for [" + i + "]",
+                                c.getCount(expected[i]), uniDimIndex);
+
+            final int[] indices = c.getCounts(uniDimIndex);
+            for (int dimIndex = 0; dimIndex < nDim; dimIndex++) {
+                Assert.assertEquals("Wrong multidimensional index for [" + i + "][" + dimIndex + "]",
+                                    expected[i][dimIndex], indices[dimIndex]);
+            }
+        }
+
+        if (iter.hasNext()) {
+            Assert.fail("Too long");
+        }
+    }
+}
diff --git a/src/test/java/org/apache/commons/math/util/OpenIntToDoubleHashMapTest.java b/src/test/java/org/apache/commons/math/util/OpenIntToDoubleHashMapTest.java
new file mode 100644
index 0000000..5c0c641
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/util/OpenIntToDoubleHashMapTest.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.util;
+
+import java.util.ConcurrentModificationException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Random;
+import java.util.Set;
+
+import junit.framework.TestCase;
+
+/**
+ * Test cases for the {@link OpenIntToDoubleHashMap}.
+ */
+public class OpenIntToDoubleHashMapTest extends TestCase {
+
+    private Map<Integer, Double> javaMap = new HashMap<Integer, Double>();
+
+    @Override
+    protected void setUp() throws Exception {
+        javaMap.put(50, 100.0);
+        javaMap.put(75, 75.0);
+        javaMap.put(25, 500.0);
+        javaMap.put(Integer.MAX_VALUE, Double.MAX_VALUE);
+        javaMap.put(0, -1.0);
+        javaMap.put(1, 0.0);
+        javaMap.put(33, -0.1);
+        javaMap.put(23234234, -242343.0);
+        javaMap.put(23321, Double.MIN_VALUE);
+        javaMap.put(-4444, 332.0);
+        javaMap.put(-1, -2323.0);
+        javaMap.put(Integer.MIN_VALUE, 44.0);
+
+        /* Add a few more to cause the table to rehash */
+        javaMap.putAll(generate());
+
+    }
+
+    private Map<Integer, Double> generate() {
+        Map<Integer, Double> map = new HashMap<Integer, Double>();
+        Random r = new Random();
+        for (int i = 0; i < 2000; ++i)
+            map.put(r.nextInt(), r.nextDouble());
+        return map;
+    }
+
+    private OpenIntToDoubleHashMap createFromJavaMap() {
+        OpenIntToDoubleHashMap map = new OpenIntToDoubleHashMap();
+        for (Map.Entry<Integer, Double> mapEntry : javaMap.entrySet()) {
+            map.put(mapEntry.getKey(), mapEntry.getValue());
+        }
+        return map;
+    }
+
+    public void testPutAndGetWith0ExpectedSize() {
+        OpenIntToDoubleHashMap map = new OpenIntToDoubleHashMap(0);
+        assertPutAndGet(map);
+    }
+
+    public void testPutAndGetWithExpectedSize() {
+        OpenIntToDoubleHashMap map = new OpenIntToDoubleHashMap(500);
+        assertPutAndGet(map);
+    }
+
+    public void testPutAndGet() {
+        OpenIntToDoubleHashMap map = new OpenIntToDoubleHashMap();
+        assertPutAndGet(map);
+    }
+
+    private void assertPutAndGet(OpenIntToDoubleHashMap map) {
+        assertPutAndGet(map, 0, new HashSet<Integer>());
+    }
+
+    private void assertPutAndGet(OpenIntToDoubleHashMap map, int mapSize,
+            Set<Integer> keysInMap) {
+        assertEquals(mapSize, map.size());
+        for (Map.Entry<Integer, Double> mapEntry : javaMap.entrySet()) {
+            map.put(mapEntry.getKey(), mapEntry.getValue());
+            if (!keysInMap.contains(mapEntry.getKey()))
+                ++mapSize;
+            assertEquals(mapSize, map.size());
+            assertEquals(mapEntry.getValue(), map.get(mapEntry.getKey()));
+        }
+    }
+
+    public void testPutAbsentOnExisting() {
+        OpenIntToDoubleHashMap map = createFromJavaMap();
+        int size = javaMap.size();
+        for (Map.Entry<Integer, Double> mapEntry : generateAbsent().entrySet()) {
+            map.put(mapEntry.getKey(), mapEntry.getValue());
+            assertEquals(++size, map.size());
+            assertEquals(mapEntry.getValue(), map.get(mapEntry.getKey()));
+        }
+    }
+
+    public void testPutOnExisting() {
+        OpenIntToDoubleHashMap map = createFromJavaMap();
+        for (Map.Entry<Integer, Double> mapEntry : javaMap.entrySet()) {
+            map.put(mapEntry.getKey(), mapEntry.getValue());
+            assertEquals(javaMap.size(), map.size());
+            assertEquals(mapEntry.getValue(), map.get(mapEntry.getKey()));
+        }
+    }
+
+    public void testGetAbsent() {
+        Map<Integer, Double> generated = generateAbsent();
+        OpenIntToDoubleHashMap map = createFromJavaMap();
+
+        for (Map.Entry<Integer, Double> mapEntry : generated.entrySet())
+            assertTrue(Double.isNaN(map.get(mapEntry.getKey())));
+    }
+
+    public void testGetFromEmpty() {
+        OpenIntToDoubleHashMap map = new OpenIntToDoubleHashMap();
+        assertTrue(Double.isNaN(map.get(5)));
+        assertTrue(Double.isNaN(map.get(0)));
+        assertTrue(Double.isNaN(map.get(50)));
+    }
+
+    public void testRemove() {
+        OpenIntToDoubleHashMap map = createFromJavaMap();
+        int mapSize = javaMap.size();
+        assertEquals(mapSize, map.size());
+        for (Map.Entry<Integer, Double> mapEntry : javaMap.entrySet()) {
+            map.remove(mapEntry.getKey());
+            assertEquals(--mapSize, map.size());
+            assertTrue(Double.isNaN(map.get(mapEntry.getKey())));
+        }
+
+        /* Ensure that put and get still work correctly after removals */
+        assertPutAndGet(map);
+    }
+
+    /* This time only remove some entries */
+    public void testRemove2() {
+        OpenIntToDoubleHashMap map = createFromJavaMap();
+        int mapSize = javaMap.size();
+        int count = 0;
+        Set<Integer> keysInMap = new HashSet<Integer>(javaMap.keySet());
+        for (Map.Entry<Integer, Double> mapEntry : javaMap.entrySet()) {
+            keysInMap.remove(mapEntry.getKey());
+            map.remove(mapEntry.getKey());
+            assertEquals(--mapSize, map.size());
+            assertTrue(Double.isNaN(map.get(mapEntry.getKey())));
+            if (count++ > 5)
+                break;
+        }
+
+        /* Ensure that put and get still work correctly after removals */
+        assertPutAndGet(map, mapSize, keysInMap);
+    }
+
+    public void testRemoveFromEmpty() {
+        OpenIntToDoubleHashMap map = new OpenIntToDoubleHashMap();
+        assertTrue(Double.isNaN(map.remove(50)));
+    }
+
+    public void testRemoveAbsent() {
+        Map<Integer, Double> generated = generateAbsent();
+
+        OpenIntToDoubleHashMap map = createFromJavaMap();
+        int mapSize = map.size();
+
+        for (Map.Entry<Integer, Double> mapEntry : generated.entrySet()) {
+            map.remove(mapEntry.getKey());
+            assertEquals(mapSize, map.size());
+            assertTrue(Double.isNaN(map.get(mapEntry.getKey())));
+        }
+    }
+
+    /**
+     * Returns a map with at least 100 elements where each element is absent from javaMap.
+     */
+    private Map<Integer, Double> generateAbsent() {
+        Map<Integer, Double> generated = new HashMap<Integer, Double>();
+        do {
+            generated.putAll(generate());
+            for (Integer key : javaMap.keySet())
+                generated.remove(key);
+        } while (generated.size() < 100);
+        return generated;
+    }
+
+    public void testCopy() {
+        OpenIntToDoubleHashMap copy =
+            new OpenIntToDoubleHashMap(createFromJavaMap());
+        assertEquals(javaMap.size(), copy.size());
+
+        for (Map.Entry<Integer, Double> mapEntry : javaMap.entrySet())
+            assertEquals(mapEntry.getValue(), copy.get(mapEntry.getKey()));
+    }
+
+    public void testContainsKey() {
+        OpenIntToDoubleHashMap map = createFromJavaMap();
+        for (Map.Entry<Integer, Double> mapEntry : javaMap.entrySet()) {
+            assertTrue(map.containsKey(mapEntry.getKey()));
+        }
+        for (Map.Entry<Integer, Double> mapEntry : generateAbsent().entrySet()) {
+            assertFalse(map.containsKey(mapEntry.getKey()));
+        }
+        for (Map.Entry<Integer, Double> mapEntry : javaMap.entrySet()) {
+            int key = mapEntry.getKey();
+            assertTrue(map.containsKey(key));
+            map.remove(key);
+            assertFalse(map.containsKey(key));
+        }
+    }
+
+    public void testIterator() {
+        OpenIntToDoubleHashMap map = createFromJavaMap();
+        OpenIntToDoubleHashMap.Iterator iterator = map.iterator();
+        for (int i = 0; i < map.size(); ++i) {
+            assertTrue(iterator.hasNext());
+            iterator.advance();
+            int key = iterator.key();
+            assertTrue(map.containsKey(key));
+            assertEquals(javaMap.get(key), map.get(key), 0);
+            assertEquals(javaMap.get(key), iterator.value(), 0);
+            assertTrue(javaMap.containsKey(key));
+        }
+        assertFalse(iterator.hasNext());
+        try {
+            iterator.advance();
+            fail("an exception should have been thrown");
+        } catch (NoSuchElementException nsee) {
+            // expected
+        }
+    }
+
+    public void testConcurrentModification() {
+        OpenIntToDoubleHashMap map = createFromJavaMap();
+        OpenIntToDoubleHashMap.Iterator iterator = map.iterator();
+        map.put(3, 3);
+        try {
+            iterator.advance();
+            fail("an exception should have been thrown");
+        } catch (ConcurrentModificationException cme) {
+            // expected
+        }
+    }
+
+    /**
+     * Regression test for a bug in findInsertionIndex where the hashing in the second probing
+     * loop was inconsistent with the first causing duplicate keys after the right sequence
+     * of puts and removes.
+     */
+    public void testPutKeysWithCollisions() {
+        OpenIntToDoubleHashMap map = new OpenIntToDoubleHashMap();
+        int key1 = -1996012590;
+        double value1 = 1.0;
+        map.put(key1, value1);
+        int key2 = 835099822;
+        map.put(key2, value1);
+        int key3 = 1008859686;
+        map.put(key3, value1);
+        assertEquals(value1, map.get(key3));
+        assertEquals(3, map.size());
+
+        map.remove(key2);
+        double value2 = 2.0;
+        map.put(key3, value2);
+        assertEquals(value2, map.get(key3));
+        assertEquals(2, map.size());
+    }
+
+    /**
+     * Similar to testPutKeysWithCollisions() but exercises the codepaths in a slightly
+     * different manner.
+     */
+    public void testPutKeysWithCollision2() {
+        OpenIntToDoubleHashMap map = new OpenIntToDoubleHashMap();
+        int key1 = 837989881;
+        double value1 = 1.0;
+        map.put(key1, value1);
+        int key2 = 476463321;
+        map.put(key2, value1);
+        assertEquals(2, map.size());
+        assertEquals(value1, map.get(key2));
+
+        map.remove(key1);
+        double value2 = 2.0;
+        map.put(key2, value2);
+        assertEquals(1, map.size());
+        assertEquals(value2, map.get(key2));
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/util/OpenIntToFieldTest.java b/src/test/java/org/apache/commons/math/util/OpenIntToFieldTest.java
new file mode 100644
index 0000000..f47fdce
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/util/OpenIntToFieldTest.java
@@ -0,0 +1,315 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.util;
+
+import java.util.ConcurrentModificationException;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Random;
+import java.util.Set;
+import java.util.Map.Entry;
+
+import org.apache.commons.math.Field;
+import org.apache.commons.math.fraction.Fraction;
+import org.apache.commons.math.fraction.FractionConversionException;
+import org.apache.commons.math.fraction.FractionField;
+
+import junit.framework.TestCase;
+
+public class OpenIntToFieldTest extends TestCase {
+
+    private Map<Integer, Fraction> javaMap = new HashMap<Integer, Fraction>();
+    private FractionField field = FractionField.getInstance();
+
+    @Override
+    protected void setUp() throws Exception {
+        javaMap.put(50, new Fraction(100.0));
+        javaMap.put(75, new Fraction(75.0));
+        javaMap.put(25, new Fraction(500.0));
+        javaMap.put(Integer.MAX_VALUE, new Fraction(Integer.MAX_VALUE));
+        javaMap.put(0, new Fraction(-1.0));
+        javaMap.put(1, new Fraction(0.0));
+        javaMap.put(33, new Fraction(-0.1));
+        javaMap.put(23234234, new Fraction(-242343.0));
+        javaMap.put(23321, new Fraction (Integer.MIN_VALUE));
+        javaMap.put(-4444, new Fraction(332.0));
+        javaMap.put(-1, new Fraction(-2323.0));
+        javaMap.put(Integer.MIN_VALUE, new Fraction(44.0));
+
+        /* Add a few more to cause the table to rehash */
+        javaMap.putAll(generate());
+
+    }
+
+    private Map<Integer, Fraction> generate() {
+        Map<Integer, Fraction> map = new HashMap<Integer, Fraction>();
+        Random r = new Random();
+        double dd=0;
+        for (int i = 0; i < 2000; ++i)
+            dd = r.nextDouble();
+            try {
+                map.put(r.nextInt(), new Fraction(dd));
+            } catch (FractionConversionException e) {
+                throw new IllegalStateException("Invalid :"+dd, e);
+            }
+        return map;
+    }
+
+    private OpenIntToFieldHashMap<Fraction> createFromJavaMap(Field<Fraction> field) {
+        OpenIntToFieldHashMap<Fraction> map = new OpenIntToFieldHashMap<Fraction>(field);
+        for (Map.Entry<Integer, Fraction> mapEntry : javaMap.entrySet()) {
+            map.put(mapEntry.getKey(), mapEntry.getValue());
+        }
+        return map;
+    }
+
+    public void testPutAndGetWith0ExpectedSize() {
+        OpenIntToFieldHashMap<Fraction> map = new OpenIntToFieldHashMap<Fraction>(field,0);
+        assertPutAndGet(map);
+    }
+
+    public void testPutAndGetWithExpectedSize() {
+        OpenIntToFieldHashMap<Fraction> map = new OpenIntToFieldHashMap<Fraction>(field,500);
+        assertPutAndGet(map);
+    }
+
+    public void testPutAndGet() {
+        OpenIntToFieldHashMap<Fraction> map = new OpenIntToFieldHashMap<Fraction>(field);
+        assertPutAndGet(map);
+    }
+
+    private void assertPutAndGet(OpenIntToFieldHashMap<Fraction> map) {
+        assertPutAndGet(map, 0, new HashSet<Integer>());
+    }
+
+    private void assertPutAndGet(OpenIntToFieldHashMap<Fraction> map, int mapSize,
+            Set<Integer> keysInMap) {
+        assertEquals(mapSize, map.size());
+        for (Map.Entry<Integer, Fraction> mapEntry : javaMap.entrySet()) {
+            map.put(mapEntry.getKey(), mapEntry.getValue());
+            if (!keysInMap.contains(mapEntry.getKey()))
+                ++mapSize;
+            assertEquals(mapSize, map.size());
+            assertEquals(mapEntry.getValue(), map.get(mapEntry.getKey()));
+        }
+    }
+
+    public void testPutAbsentOnExisting() {
+        OpenIntToFieldHashMap<Fraction> map = createFromJavaMap(field);
+        int size = javaMap.size();
+        for (Map.Entry<Integer, Fraction> mapEntry : generateAbsent().entrySet()) {
+            map.put(mapEntry.getKey(), mapEntry.getValue());
+            assertEquals(++size, map.size());
+            assertEquals(mapEntry.getValue(), map.get(mapEntry.getKey()));
+        }
+    }
+
+    public void testPutOnExisting() {
+        OpenIntToFieldHashMap<Fraction> map = createFromJavaMap(field);
+        for (Map.Entry<Integer, Fraction> mapEntry : javaMap.entrySet()) {
+            map.put(mapEntry.getKey(), mapEntry.getValue());
+            assertEquals(javaMap.size(), map.size());
+            assertEquals(mapEntry.getValue(), map.get(mapEntry.getKey()));
+        }
+    }
+
+    public void testGetAbsent() {
+        Map<Integer, Fraction> generated = generateAbsent();
+        OpenIntToFieldHashMap<Fraction> map = createFromJavaMap(field);
+
+        for (Map.Entry<Integer, Fraction> mapEntry : generated.entrySet())
+            assertTrue(field.getZero().equals(map.get(mapEntry.getKey())));
+    }
+
+    public void testGetFromEmpty() {
+        OpenIntToFieldHashMap<Fraction> map = new OpenIntToFieldHashMap<Fraction>(field);
+        assertTrue(field.getZero().equals(map.get(5)));
+        assertTrue(field.getZero().equals(map.get(0)));
+        assertTrue(field.getZero().equals(map.get(50)));
+    }
+
+    public void testRemove() {
+        OpenIntToFieldHashMap<Fraction> map = createFromJavaMap(field);
+        int mapSize = javaMap.size();
+        assertEquals(mapSize, map.size());
+        for (Map.Entry<Integer, Fraction> mapEntry : javaMap.entrySet()) {
+            map.remove(mapEntry.getKey());
+            assertEquals(--mapSize, map.size());
+            assertTrue(field.getZero().equals(map.get(mapEntry.getKey())));
+        }
+
+        /* Ensure that put and get still work correctly after removals */
+        assertPutAndGet(map);
+    }
+
+    /* This time only remove some entries */
+    public void testRemove2() {
+        OpenIntToFieldHashMap<Fraction> map = createFromJavaMap(field);
+        int mapSize = javaMap.size();
+        int count = 0;
+        Set<Integer> keysInMap = new HashSet<Integer>(javaMap.keySet());
+        for (Map.Entry<Integer, Fraction> mapEntry : javaMap.entrySet()) {
+            keysInMap.remove(mapEntry.getKey());
+            map.remove(mapEntry.getKey());
+            assertEquals(--mapSize, map.size());
+            assertTrue(field.getZero().equals(map.get(mapEntry.getKey())));
+            if (count++ > 5)
+                break;
+        }
+
+        /* Ensure that put and get still work correctly after removals */
+        assertPutAndGet(map, mapSize, keysInMap);
+    }
+
+    public void testRemoveFromEmpty() {
+        OpenIntToFieldHashMap<Fraction> map = new OpenIntToFieldHashMap<Fraction>(field);
+        assertTrue(field.getZero().equals(map.remove(50)));
+    }
+
+    public void testRemoveAbsent() {
+        Map<Integer, Fraction> generated = generateAbsent();
+
+        OpenIntToFieldHashMap<Fraction> map = createFromJavaMap(field);
+        int mapSize = map.size();
+
+        for (Map.Entry<Integer, Fraction> mapEntry : generated.entrySet()) {
+            map.remove(mapEntry.getKey());
+            assertEquals(mapSize, map.size());
+            assertTrue(field.getZero().equals(map.get(mapEntry.getKey())));
+        }
+    }
+
+    /**
+     * Returns a map with at least 100 elements where each element is absent from javaMap.
+     */
+    private Map<Integer, Fraction> generateAbsent() {
+        Map<Integer, Fraction> generated = new HashMap<Integer, Fraction>();
+        do {
+            generated.putAll(generate());
+            for (Integer key : javaMap.keySet())
+                generated.remove(key);
+        } while (generated.size() < 100);
+        return generated;
+    }
+
+    public void testCopy() {
+        OpenIntToFieldHashMap<Fraction> copy =
+            new OpenIntToFieldHashMap<Fraction>(createFromJavaMap(field));
+        assertEquals(javaMap.size(), copy.size());
+
+        for (Map.Entry<Integer, Fraction> mapEntry : javaMap.entrySet())
+            assertEquals(mapEntry.getValue(), copy.get(mapEntry.getKey()));
+    }
+
+    public void testContainsKey() {
+        OpenIntToFieldHashMap<Fraction> map = createFromJavaMap(field);
+        for (Entry<Integer, Fraction> mapEntry : javaMap.entrySet()) {
+            assertTrue(map.containsKey(mapEntry.getKey()));
+        }
+        for (Map.Entry<Integer, Fraction> mapEntry : generateAbsent().entrySet()) {
+            assertFalse(map.containsKey(mapEntry.getKey()));
+        }
+        for (Entry<Integer, Fraction> mapEntry : javaMap.entrySet()) {
+            int key = mapEntry.getKey();
+            assertTrue(map.containsKey(key));
+            map.remove(key);
+            assertFalse(map.containsKey(key));
+        }
+    }
+
+    public void testIterator() {
+        OpenIntToFieldHashMap<Fraction> map = createFromJavaMap(field);
+        OpenIntToFieldHashMap<Fraction>.Iterator iterator = map.iterator();
+        for (int i = 0; i < map.size(); ++i) {
+            assertTrue(iterator.hasNext());
+            iterator.advance();
+            int key = iterator.key();
+            assertTrue(map.containsKey(key));
+            assertEquals(javaMap.get(key), map.get(key));
+            assertEquals(javaMap.get(key), iterator.value());
+            assertTrue(javaMap.containsKey(key));
+        }
+        assertFalse(iterator.hasNext());
+        try {
+            iterator.advance();
+            fail("an exception should have been thrown");
+        } catch (NoSuchElementException nsee) {
+            // expected
+        }
+    }
+
+    public void testConcurrentModification() {
+        OpenIntToFieldHashMap<Fraction> map = createFromJavaMap(field);
+        OpenIntToFieldHashMap<Fraction>.Iterator iterator = map.iterator();
+        map.put(3, new Fraction(3));
+        try {
+            iterator.advance();
+            fail("an exception should have been thrown");
+        } catch (ConcurrentModificationException cme) {
+            // expected
+        }
+    }
+
+    /**
+     * Regression test for a bug in findInsertionIndex where the hashing in the second probing
+     * loop was inconsistent with the first causing duplicate keys after the right sequence
+     * of puts and removes.
+     */
+    public void testPutKeysWithCollisions() {
+        OpenIntToFieldHashMap<Fraction> map = new OpenIntToFieldHashMap<Fraction>(field);
+        int key1 = -1996012590;
+        Fraction value1 = new Fraction(1);
+        map.put(key1, value1);
+        int key2 = 835099822;
+        map.put(key2, value1);
+        int key3 = 1008859686;
+        map.put(key3, value1);
+        assertEquals(value1, map.get(key3));
+        assertEquals(3, map.size());
+
+        map.remove(key2);
+        Fraction value2 = new Fraction(2);
+        map.put(key3, value2);
+        assertEquals(value2, map.get(key3));
+        assertEquals(2, map.size());
+    }
+
+    /**
+     * Similar to testPutKeysWithCollisions() but exercises the codepaths in a slightly
+     * different manner.
+     */
+    public void testPutKeysWithCollision2() {
+        OpenIntToFieldHashMap<Fraction>map = new OpenIntToFieldHashMap<Fraction>(field);
+        int key1 = 837989881;
+        Fraction value1 = new Fraction(1);
+        map.put(key1, value1);
+        int key2 = 476463321;
+        map.put(key2, value1);
+        assertEquals(2, map.size());
+        assertEquals(value1, map.get(key2));
+
+        map.remove(key1);
+        Fraction value2 = new Fraction(2);
+        map.put(key2, value2);
+        assertEquals(1, map.size());
+        assertEquals(value2, map.get(key2));
+    }
+
+
+}
diff --git a/src/test/java/org/apache/commons/math/util/ResizableDoubleArrayTest.java b/src/test/java/org/apache/commons/math/util/ResizableDoubleArrayTest.java
new file mode 100644
index 0000000..ac539b8
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/util/ResizableDoubleArrayTest.java
@@ -0,0 +1,540 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.math.util;
+import org.apache.commons.math.random.RandomDataImpl;
+import org.apache.commons.math.random.RandomData;
+
+
+/**
+ * This class contains test cases for the ResizableDoubleArray.
+ *
+ * @version $Revision: 1053282 $ $Date: 2010-12-28 10:03:40 +0100 (mar. 28 déc. 2010) $
+ */
+public class ResizableDoubleArrayTest extends DoubleArrayAbstractTest {
+
+    public ResizableDoubleArrayTest(String name) {
+        super( name );
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        da = null;
+        ra = null;
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        da = new ResizableDoubleArray();
+        ra = new ResizableDoubleArray();
+    }
+
+    public void testConstructors() {
+        float defaultExpansionFactor = 2.0f;
+        float defaultContractionCriteria = 2.5f;
+        int defaultMode = ResizableDoubleArray.MULTIPLICATIVE_MODE;
+
+        ResizableDoubleArray testDa = new ResizableDoubleArray(2);
+        assertEquals(0, testDa.getNumElements());
+        assertEquals(2, testDa.getInternalLength());
+        assertEquals(defaultExpansionFactor, testDa.getExpansionFactor(), 0);
+        assertEquals(defaultContractionCriteria, testDa.getContractionCriteria(), 0);
+        assertEquals(defaultMode, testDa.getExpansionMode());
+        try {
+            da = new ResizableDoubleArray(-1);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        
+        testDa = new ResizableDoubleArray((double[]) null);
+        assertEquals(0, testDa.getNumElements());
+        
+        double[] initialArray = new double[] { 0, 1, 2 };        
+        testDa = new ResizableDoubleArray(initialArray);
+        assertEquals(3, testDa.getNumElements());
+
+        testDa = new ResizableDoubleArray(2, 2.0f);
+        assertEquals(0, testDa.getNumElements());
+        assertEquals(2, testDa.getInternalLength());
+        assertEquals(defaultExpansionFactor, testDa.getExpansionFactor(), 0);
+        assertEquals(defaultContractionCriteria, testDa.getContractionCriteria(), 0);
+        assertEquals(defaultMode, testDa.getExpansionMode());
+
+        try {
+            da = new ResizableDoubleArray(2, 0.5f);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        testDa = new ResizableDoubleArray(2, 3.0f);
+        assertEquals(3.0f, testDa.getExpansionFactor(), 0);
+        assertEquals(3.5f, testDa.getContractionCriteria(), 0);
+
+        testDa = new ResizableDoubleArray(2, 2.0f, 3.0f);
+        assertEquals(0, testDa.getNumElements());
+        assertEquals(2, testDa.getInternalLength());
+        assertEquals(defaultExpansionFactor, testDa.getExpansionFactor(), 0);
+        assertEquals(3.0f, testDa.getContractionCriteria(), 0);
+        assertEquals(defaultMode, testDa.getExpansionMode());
+
+        try {
+            da = new ResizableDoubleArray(2, 2.0f, 1.5f);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        testDa = new ResizableDoubleArray(2, 2.0f, 3.0f,
+                ResizableDoubleArray.ADDITIVE_MODE);
+        assertEquals(0, testDa.getNumElements());
+        assertEquals(2, testDa.getInternalLength());
+        assertEquals(defaultExpansionFactor, testDa.getExpansionFactor(), 0);
+        assertEquals(3.0f, testDa.getContractionCriteria(), 0);
+        assertEquals(ResizableDoubleArray.ADDITIVE_MODE,
+                testDa.getExpansionMode());
+
+        try {
+            da = new ResizableDoubleArray(2, 2.0f, 2.5f, -1);
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+
+        // Copy constructor
+        testDa = new ResizableDoubleArray(2, 2.0f, 3.0f,
+                ResizableDoubleArray.ADDITIVE_MODE);
+        testDa.addElement(2.0);
+        testDa.addElement(3.2);
+        ResizableDoubleArray copyDa = new ResizableDoubleArray(testDa);
+        assertEquals(copyDa, testDa);
+        assertEquals(testDa, copyDa);
+    }
+
+
+    public void testSetElementArbitraryExpansion() {
+
+        // MULTIPLICATIVE_MODE
+        da.addElement(2.0);
+        da.addElement(4.0);
+        da.addElement(6.0);
+        da.setElement(1, 3.0);
+
+        // Expand the array arbitrarily to 1000 items
+        da.setElement(1000, 3.4);
+
+        assertEquals( "The number of elements should now be 1001, it isn't",
+                da.getNumElements(), 1001);
+
+        assertEquals( "Uninitialized Elements are default value of 0.0, index 766 wasn't", 0.0,
+                da.getElement( 760 ), Double.MIN_VALUE );
+
+        assertEquals( "The 1000th index should be 3.4, it isn't", 3.4, da.getElement(1000),
+                Double.MIN_VALUE );
+        assertEquals( "The 0th index should be 2.0, it isn't", 2.0, da.getElement(0),
+                Double.MIN_VALUE);
+
+        // Make sure numElements and expansion work correctly for expansion boundary cases
+        da.clear();
+        da.addElement(2.0);
+        da.addElement(4.0);
+        da.addElement(6.0);
+        assertEquals(4, ((ResizableDoubleArray) da).getInternalLength());
+        assertEquals(3, da.getNumElements());
+        da.setElement(3, 7.0);
+        assertEquals(4, ((ResizableDoubleArray) da).getInternalLength());
+        assertEquals(4, da.getNumElements());
+        da.setElement(10, 10.0);
+        assertEquals(11, ((ResizableDoubleArray) da).getInternalLength());
+        assertEquals(11, da.getNumElements());
+        da.setElement(9, 10.0);
+        assertEquals(11, ((ResizableDoubleArray) da).getInternalLength());
+        assertEquals(11, da.getNumElements());
+
+        try {
+            da.setElement(-2, 3);
+            fail("Expecting ArrayIndexOutOfBoundsException for negative index");
+        } catch (ArrayIndexOutOfBoundsException ex) {
+            // expected
+        }
+
+        // ADDITIVE_MODE
+
+        ResizableDoubleArray testDa = new ResizableDoubleArray(2, 2.0f, 3.0f,
+                ResizableDoubleArray.ADDITIVE_MODE);
+        assertEquals(2, testDa.getInternalLength());
+        testDa.addElement(1d);
+        testDa.addElement(1d);
+        assertEquals(2, testDa.getInternalLength());
+        testDa.addElement(1d);
+        assertEquals(4, testDa.getInternalLength());
+    }
+
+    @Override
+    public void testAdd1000() {
+        super.testAdd1000();
+        assertEquals("Internal Storage length should be 1024 if we started out with initial capacity of " +
+                "16 and an expansion factor of 2.0",
+                1024, ((ResizableDoubleArray) da).getInternalLength());
+    }
+    
+    public void testAddElements() {
+        ResizableDoubleArray testDa = new ResizableDoubleArray();
+        
+        // MULTIPLICATIVE_MODE
+        testDa.addElements(new double[] {4, 5, 6});
+        assertEquals(3, testDa.getNumElements(), 0);
+        assertEquals(4, testDa.getElement(0), 0);
+        assertEquals(5, testDa.getElement(1), 0);
+        assertEquals(6, testDa.getElement(2), 0);
+        
+        testDa.addElements(new double[] {4, 5, 6});
+        assertEquals(6, testDa.getNumElements());
+
+        // ADDITIVE_MODE  (x's are occupied storage locations, 0's are open)
+        testDa = new ResizableDoubleArray(2, 2.0f, 2.5f,
+                ResizableDoubleArray.ADDITIVE_MODE);        
+        assertEquals(2, testDa.getInternalLength());
+        testDa.addElements(new double[] { 1d }); // x,0
+        testDa.addElements(new double[] { 2d }); // x,x
+        testDa.addElements(new double[] { 3d }); // x,x,x,0 -- expanded
+        assertEquals(1d, testDa.getElement(0), 0);
+        assertEquals(2d, testDa.getElement(1), 0);
+        assertEquals(3d, testDa.getElement(2), 0);
+        assertEquals(4, testDa.getInternalLength());  // x,x,x,0
+        assertEquals(3, testDa.getNumElements());
+    }
+
+    @Override
+    public void testAddElementRolling() {
+        super.testAddElementRolling();
+
+        // MULTIPLICATIVE_MODE
+        da.clear();
+        da.addElement(1);
+        da.addElement(2);
+        da.addElementRolling(3);
+        assertEquals(3, da.getElement(1), 0);
+        da.addElementRolling(4);
+        assertEquals(3, da.getElement(0), 0);
+        assertEquals(4, da.getElement(1), 0);
+        da.addElement(5);
+        assertEquals(5, da.getElement(2), 0);
+        da.addElementRolling(6);
+        assertEquals(4, da.getElement(0), 0);
+        assertEquals(5, da.getElement(1), 0);
+        assertEquals(6, da.getElement(2), 0);
+
+        // ADDITIVE_MODE  (x's are occupied storage locations, 0's are open)
+        ResizableDoubleArray testDa = new ResizableDoubleArray(2, 2.0f, 2.5f,
+                ResizableDoubleArray.ADDITIVE_MODE);
+        assertEquals(2, testDa.getInternalLength());
+        testDa.addElement(1d); // x,0
+        testDa.addElement(2d); // x,x
+        testDa.addElement(3d); // x,x,x,0 -- expanded
+        assertEquals(1d, testDa.getElement(0), 0);
+        assertEquals(2d, testDa.getElement(1), 0);
+        assertEquals(3d, testDa.getElement(2), 0);
+        assertEquals(4, testDa.getInternalLength());  // x,x,x,0
+        assertEquals(3, testDa.getNumElements());
+        testDa.addElementRolling(4d);
+        assertEquals(2d, testDa.getElement(0), 0);
+        assertEquals(3d, testDa.getElement(1), 0);
+        assertEquals(4d, testDa.getElement(2), 0);
+        assertEquals(4, testDa.getInternalLength());  // 0,x,x,x
+        assertEquals(3, testDa.getNumElements());
+        testDa.addElementRolling(5d);   // 0,0,x,x,x,0 -- time to contract
+        assertEquals(3d, testDa.getElement(0), 0);
+        assertEquals(4d, testDa.getElement(1), 0);
+        assertEquals(5d, testDa.getElement(2), 0);
+        assertEquals(4, testDa.getInternalLength());  // contracted -- x,x,x,0
+        assertEquals(3, testDa.getNumElements());
+        try {
+            testDa.getElement(4);
+            fail("Expecting ArrayIndexOutOfBoundsException");
+        } catch (ArrayIndexOutOfBoundsException ex) {
+            // expected
+        }
+        try {
+            testDa.getElement(-1);
+            fail("Expecting ArrayIndexOutOfBoundsException");
+        } catch (ArrayIndexOutOfBoundsException ex) {
+            // expected
+        }
+    }
+
+    public void testSetNumberOfElements() {
+        da.addElement( 1.0 );
+        da.addElement( 1.0 );
+        da.addElement( 1.0 );
+        da.addElement( 1.0 );
+        da.addElement( 1.0 );
+        da.addElement( 1.0 );
+        assertEquals( "Number of elements should equal 6", da.getNumElements(), 6);
+
+        ((ResizableDoubleArray) da).setNumElements( 3 );
+        assertEquals( "Number of elements should equal 3", da.getNumElements(), 3);
+
+        try {
+            ((ResizableDoubleArray) da).setNumElements( -3 );
+            fail( "Setting number of elements to negative should've thrown an exception");
+        } catch( IllegalArgumentException iae ) {
+        }
+
+        ((ResizableDoubleArray) da).setNumElements(1024);
+        assertEquals( "Number of elements should now be 1024", da.getNumElements(), 1024);
+        assertEquals( "Element 453 should be a default double", da.getElement( 453 ), 0.0, Double.MIN_VALUE);
+
+    }
+
+    public void testWithInitialCapacity() {
+
+        ResizableDoubleArray eDA2 = new ResizableDoubleArray(2);
+        assertEquals("Initial number of elements should be 0", 0, eDA2.getNumElements());
+
+        RandomData randomData = new RandomDataImpl();
+        int iterations = randomData.nextInt(100, 1000);
+
+        for( int i = 0; i < iterations; i++) {
+            eDA2.addElement( i );
+        }
+
+        assertEquals("Number of elements should be equal to " + iterations, iterations, eDA2.getNumElements());
+
+        eDA2.addElement( 2.0 );
+
+        assertEquals("Number of elements should be equals to " + (iterations +1),
+                iterations + 1 , eDA2.getNumElements() );
+    }
+
+    public void testWithInitialCapacityAndExpansionFactor() {
+
+        ResizableDoubleArray eDA3 = new ResizableDoubleArray(3, 3.0f, 3.5f);
+        assertEquals("Initial number of elements should be 0", 0, eDA3.getNumElements() );
+
+        RandomData randomData = new RandomDataImpl();
+        int iterations = randomData.nextInt(100, 3000);
+
+        for( int i = 0; i < iterations; i++) {
+            eDA3.addElement( i );
+        }
+
+        assertEquals("Number of elements should be equal to " + iterations, iterations,eDA3.getNumElements());
+
+        eDA3.addElement( 2.0 );
+
+        assertEquals("Number of elements should be equals to " + (iterations +1),
+                iterations +1, eDA3.getNumElements() );
+
+        assertEquals("Expansion factor should equal 3.0", 3.0f, eDA3.getExpansionFactor(), Double.MIN_VALUE);
+    }
+
+    public void testDiscard() {
+        da.addElement(2.0);
+        da.addElement(2.0);
+        da.addElement(2.0);
+        da.addElement(2.0);
+        da.addElement(2.0);
+        da.addElement(2.0);
+        da.addElement(2.0);
+        da.addElement(2.0);
+        da.addElement(2.0);
+        da.addElement(2.0);
+        da.addElement(2.0);
+        assertEquals( "Number of elements should be 11", 11, da.getNumElements());
+
+        ((ResizableDoubleArray)da).discardFrontElements(5);
+        assertEquals( "Number of elements should be 6", 6, da.getNumElements());
+
+        da.addElement(2.0);
+        da.addElement(2.0);
+        da.addElement(2.0);
+        da.addElement(2.0);
+        assertEquals( "Number of elements should be 10", 10, da.getNumElements());
+
+        ((ResizableDoubleArray)da).discardMostRecentElements(2);
+        assertEquals( "Number of elements should be 8", 8, da.getNumElements());
+
+        try {
+            ((ResizableDoubleArray)da).discardFrontElements(-1);
+            fail( "Trying to discard a negative number of element is not allowed");
+        } catch( Exception e ){
+        }
+
+        try {
+            ((ResizableDoubleArray)da).discardMostRecentElements(-1);
+            fail( "Trying to discard a negative number of element is not allowed");
+        } catch( Exception e ){
+        }
+
+        try {
+            ((ResizableDoubleArray)da).discardFrontElements( 10000 );
+            fail( "You can't discard more elements than the array contains");
+        } catch( Exception e ){
+        }
+
+        try {
+            ((ResizableDoubleArray)da).discardMostRecentElements( 10000 );
+            fail( "You can't discard more elements than the array contains");
+        } catch( Exception e ){
+        }
+
+    }
+
+    public void testSubstitute() {
+
+        da.addElement(2.0);
+        da.addElement(2.0);
+        da.addElement(2.0);
+        da.addElement(2.0);
+        da.addElement(2.0);
+        da.addElement(2.0);
+        da.addElement(2.0);
+        da.addElement(2.0);
+        da.addElement(2.0);
+        da.addElement(2.0);
+        da.addElement(2.0);
+        assertEquals( "Number of elements should be 11", 11, da.getNumElements());
+
+        ((ResizableDoubleArray)da).substituteMostRecentElement(24);
+
+        assertEquals( "Number of elements should be 11", 11, da.getNumElements());
+
+        try {
+            ((ResizableDoubleArray)da).discardMostRecentElements(10);
+        } catch( Exception e ){
+            fail( "Trying to discard a negative number of element is not allowed");
+        }
+
+        ((ResizableDoubleArray)da).substituteMostRecentElement(24);
+
+        assertEquals( "Number of elements should be 1", 1, da.getNumElements());
+
+    }
+
+    public void testMutators() {
+        ((ResizableDoubleArray)da).setContractionCriteria(10f);
+        assertEquals(10f, ((ResizableDoubleArray)da).getContractionCriteria(), 0);
+        ((ResizableDoubleArray)da).setExpansionFactor(8f);
+        assertEquals(8f, ((ResizableDoubleArray)da).getExpansionFactor(), 0);
+        try {
+            ((ResizableDoubleArray)da).setExpansionFactor(11f);  // greater than contractionCriteria
+            fail("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+        ((ResizableDoubleArray)da).setExpansionMode(
+                ResizableDoubleArray.ADDITIVE_MODE);
+        assertEquals(ResizableDoubleArray.ADDITIVE_MODE,
+                ((ResizableDoubleArray)da).getExpansionMode());
+        try {
+            ((ResizableDoubleArray)da).setExpansionMode(-1);
+            fail ("Expecting IllegalArgumentException");
+        } catch (IllegalArgumentException ex) {
+            // expected
+        }
+    }
+
+    public void testEqualsAndHashCode() throws Exception {
+
+        // Wrong type
+        ResizableDoubleArray first = new ResizableDoubleArray();
+        Double other = new Double(2);
+        assertFalse(first.equals(other));
+
+        // Null
+        other = null;
+        assertFalse(first.equals(other));
+
+        // Reflexive
+        assertTrue(first.equals(first));
+
+        // Argumentless constructor
+        ResizableDoubleArray second = new ResizableDoubleArray();
+        verifyEquality(first, second);
+
+        // Equals iff same data, same properties
+        ResizableDoubleArray third = new ResizableDoubleArray(3, 2.0f, 2.0f);
+        verifyInequality(third, first);
+        ResizableDoubleArray fourth = new ResizableDoubleArray(3, 2.0f, 2.0f);
+        ResizableDoubleArray fifth = new ResizableDoubleArray(2, 2.0f, 2.0f);
+        verifyEquality(third, fourth);
+        verifyInequality(third, fifth);
+        third.addElement(4.1);
+        third.addElement(4.2);
+        third.addElement(4.3);
+        fourth.addElement(4.1);
+        fourth.addElement(4.2);
+        fourth.addElement(4.3);
+        verifyEquality(third, fourth);
+
+        // expand
+        fourth.addElement(4.4);
+        verifyInequality(third, fourth);
+        third.addElement(4.4);
+        verifyEquality(third, fourth);
+        fourth.addElement(4.4);
+        verifyInequality(third, fourth);
+        third.addElement(4.4);
+        verifyEquality(third, fourth);
+        fourth.addElementRolling(4.5);
+        third.addElementRolling(4.5);
+        verifyEquality(third, fourth);
+
+        // discard
+        third.discardFrontElements(1);
+        verifyInequality(third, fourth);
+        fourth.discardFrontElements(1);
+        verifyEquality(third, fourth);
+
+        // discard recent
+        third.discardMostRecentElements(2);
+        fourth.discardMostRecentElements(2);
+        verifyEquality(third, fourth);
+
+        // wrong order
+        third.addElement(18);
+        fourth.addElement(17);
+        third.addElement(17);
+        fourth.addElement(18);
+        verifyInequality(third, fourth);
+
+        // copy
+        ResizableDoubleArray.copy(fourth, fifth);
+        verifyEquality(fourth, fifth);
+
+        // Copy constructor
+        verifyEquality(fourth, new ResizableDoubleArray(fourth));
+
+        // Instance copy
+        verifyEquality(fourth, fourth.copy());
+
+    }
+
+    private void verifyEquality(ResizableDoubleArray a, ResizableDoubleArray b) {
+        assertTrue(b.equals(a));
+        assertTrue(a.equals(b));
+        assertEquals(a.hashCode(), b.hashCode());
+    }
+
+    private void verifyInequality(ResizableDoubleArray a, ResizableDoubleArray b) {
+        assertFalse(b.equals(a));
+        assertFalse(a.equals(b));
+        assertFalse(a.hashCode() == b.hashCode());
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/util/TestBean.java b/src/test/java/org/apache/commons/math/util/TestBean.java
new file mode 100644
index 0000000..3c8f999
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/util/TestBean.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.util;
+
+import org.apache.commons.math.MathRuntimeException;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+
+/**
+ * @version $Revision: 983921 $ $Date: 2010-08-10 12:46:06 +0200 (mar. 10 août 2010) $
+ */
+public class TestBean {
+    private Double x = Double.valueOf(1.0);
+
+    private String y = "1.0";
+
+    /**
+     *
+     */
+    public Double getX() {
+        return x;
+    }
+
+    /**
+     *
+     */
+    public String getY() {
+        return y;
+    }
+
+    /**
+     *
+     */
+    public void setX(Double double1) {
+        x = double1;
+    }
+
+    /**
+     *
+     */
+    public void setY(String string) {
+        y = string;
+    }
+
+    /**
+     *
+     */
+    public Double getZ() {
+        throw new MathRuntimeException(LocalizedFormats.SIMPLE_MESSAGE, "?");
+    }
+
+    /**
+     *
+     */
+    public void setZ(Double double1) {
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/math/util/TransformerMapTest.java b/src/test/java/org/apache/commons/math/util/TransformerMapTest.java
new file mode 100644
index 0000000..7c47fcd
--- /dev/null
+++ b/src/test/java/org/apache/commons/math/util/TransformerMapTest.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.util;
+
+import org.apache.commons.math.TestUtils;
+
+import junit.framework.TestCase;
+
+/**
+ * @version $Revision: 811685 $ $Date: 2009-09-05 19:36:48 +0200 (sam. 05 sept. 2009) $
+ */
+public class TransformerMapTest extends TestCase {
+    /**
+     *
+     */
+    public void testPutTransformer(){
+        NumberTransformer expected = new DefaultTransformer();
+
+        TransformerMap map = new TransformerMap();
+        map.putTransformer(TransformerMapTest.class, expected);
+        assertEquals(expected, map.getTransformer(TransformerMapTest.class));
+    }
+
+    /**
+     *
+     */
+    public void testContainsClass(){
+        NumberTransformer expected = new DefaultTransformer();
+        TransformerMap map = new TransformerMap();
+        map.putTransformer(TransformerMapTest.class, expected);
+        assertTrue(map.containsClass(TransformerMapTest.class));
+    }
+
+    /**
+     *
+     */
+    public void testContainsTransformer(){
+        NumberTransformer expected = new DefaultTransformer();
+        TransformerMap map = new TransformerMap();
+        map.putTransformer(TransformerMapTest.class, expected);
+        assertTrue(map.containsTransformer(expected));
+    }
+
+    /**
+     *
+     */
+    public void testRemoveTransformer(){
+        NumberTransformer expected = new DefaultTransformer();
+
+        TransformerMap map = new TransformerMap();
+        map.putTransformer(TransformerMapTest.class, expected);
+        assertTrue(map.containsClass(TransformerMapTest.class));
+        assertTrue(map.containsTransformer(expected));
+        map.removeTransformer(TransformerMapTest.class);
+        assertFalse(map.containsClass(TransformerMapTest.class));
+        assertFalse(map.containsTransformer(expected));
+    }
+
+    /**
+     *
+     */
+    public void testClear(){
+        NumberTransformer expected = new DefaultTransformer();
+
+        TransformerMap map = new TransformerMap();
+        map.putTransformer(TransformerMapTest.class, expected);
+        assertTrue(map.containsClass(TransformerMapTest.class));
+        map.clear();
+        assertFalse(map.containsClass(TransformerMapTest.class));
+    }
+
+    /**
+     *
+     */
+    public void testClasses(){
+        NumberTransformer expected = new DefaultTransformer();
+        TransformerMap map = new TransformerMap();
+        map.putTransformer(TransformerMapTest.class, expected);
+        assertTrue(map.classes().contains(TransformerMapTest.class));
+    }
+
+    /**
+     *
+     */
+    public void testTransformers(){
+        NumberTransformer expected = new DefaultTransformer();
+        TransformerMap map = new TransformerMap();
+        map.putTransformer(TransformerMapTest.class, expected);
+        assertTrue(map.transformers().contains(expected));
+    }
+
+    public void testSerial(){
+        NumberTransformer expected = new DefaultTransformer();
+        TransformerMap map = new TransformerMap();
+        map.putTransformer(TransformerMapTest.class, expected);
+        assertEquals(map, TestUtils.serializeAndRecover(map));
+    }
+
+}
diff --git a/src/test/resources/org/apache/commons/math/random/emptyFile.txt b/src/test/resources/org/apache/commons/math/random/emptyFile.txt
new file mode 100644
index 0000000..e69de29
diff --git a/src/test/resources/org/apache/commons/math/random/testData.txt b/src/test/resources/org/apache/commons/math/random/testData.txt
new file mode 100644
index 0000000..4a10132
--- /dev/null
+++ b/src/test/resources/org/apache/commons/math/random/testData.txt
@@ -0,0 +1,1000 @@
+4.038625496201205
+3.6485326248346936
+3.6651209675932845
+5.814896279561131
+5.384126469824717
+5.251190723365563
+4.465213440111648
+4.736608014129308
+5.566383814840726
+3.8872277480629114
+5.246598498086048
+3.7511487364188176
+6.733371385175343
+5.388632419618035
+6.036263402962769
+3.8105605069222905
+5.738599503606028
+4.994552792425298
+2.945504336988336
+4.095314381239862
+5.760924710543879
+3.889753944419315
+3.808861160991701
+5.084302950012555
+6.370292933705698
+5.9615431859588455
+4.8790354385481445
+4.068164663649243
+4.26491661935213
+5.911067976258105
+4.1316140545022115
+4.0479985648577115
+5.560425919351912
+5.7777862258090265
+4.6491664757229145
+5.322284766164775
+3.9643060017297818
+3.3374606422520423
+4.070818520139152
+5.162814971692577
+4.68959876937858
+5.729533112912969
+7.160010937980058
+5.154920628387343
+5.992604820701763
+5.317279162752973
+6.3388993007264
+4.38451874656454
+5.024014917973479
+3.7534872319471946
+5.042363342784924
+5.528064915562473
+4.645749024871185
+2.5572755520373756
+3.953716919712825
+3.479482401208564
+4.676100783445583
+4.602236051449888
+7.136692300563229
+3.2411466558895095
+4.188618724984135
+3.6403999184454445
+3.4104206071160776
+4.807963390662261
+4.039073733966207
+4.201017826594899
+4.381868005163936
+5.0635235098658935
+5.9840760229548735
+4.195400406346137
+5.649256144660377
+4.679088153774095
+4.169683379901892
+5.671299804360453
+6.159089864893807
+5.315685219074694
+6.3327786025390305
+5.57348047674858
+6.124073677151904
+4.599177837977919
+4.792320274522308
+5.670142645005109
+5.479531549270221
+4.7740747976996705
+4.99464290442364
+5.474337090086012
+5.232353744280737
+6.411298157619626
+4.757268066271138
+5.217779158748026
+5.07395379944902
+5.5759984176628965
+4.521182520094989
+5.738026445940142
+4.742204968903384
+4.670762511507285
+4.884925361512115
+3.2573282568729462
+4.548387675110013
+4.337950021352034
+3.7875587274144618
+3.6055586455442974
+7.361861332197413
+4.834945622163155
+6.019473054836964
+4.453304225895349
+3.258695681592217
+5.794572588445252
+3.706438851580378
+6.079300672323756
+4.828008457182361
+5.315261102210712
+3.981847695058188
+4.039767325290114
+5.790863349525503
+5.160671471128728
+4.835236459763434
+4.405281184174698
+6.036293520404953
+5.889067983920324
+4.645514887430352
+4.347244670972515
+6.447181244432997
+6.564267268399325
+5.1013992003059885
+4.123378901103389
+2.7101740954226283
+4.209200680057913
+5.085704888132955
+4.26810090240086
+5.54254381015526
+4.721081268239747
+6.890088385585999
+3.9983110493877954
+5.321006894021748
+4.316867375040024
+3.694764624577479
+5.453875921043777
+3.7798857421649927
+3.7228653199742623
+4.807698651287013
+3.953745662132547
+4.821189486606762
+4.453489509051613
+6.4517275696030225
+4.823034188588044
+4.722822481625316
+5.810689805246644
+2.79248319144007
+4.928162269110059
+4.661918219482871
+4.574123379557206
+5.241478194631993
+5.8345087395944155
+7.024415739565572
+3.5536052565954
+6.095523994939967
+5.714650096778455
+4.846741263282074
+6.002769586957791
+5.982356840660745
+5.045480762532407
+6.461077219605347
+4.806649266171423
+6.350848113862498
+6.402679936682352
+3.8190196431210914
+4.189064677946727
+4.868517260374518
+2.145198341547173
+5.9469461091347
+5.88772432287321
+4.258280909990726
+6.740134075161574
+4.6047650031608445
+3.9273659909763774
+4.291244045368331
+5.183827109845055
+5.696810583954774
+3.608472134466666
+4.169004030425733
+3.9965477474540467
+3.7571221568428017
+5.575534565152322
+5.0764208309825065
+5.3185446180363485
+5.157450995663762
+4.961815558094033
+5.687338919107788
+4.185906295178724
+4.382007991332045
+3.5280961455873676
+4.531506809760329
+4.5870808989545635
+4.1932173503939625
+7.213813469684956
+3.1814836225682908
+4.647297462243001
+5.223975935315364
+5.585001659776854
+5.120918864744974
+5.026571594328509
+6.348097968923147
+6.50023470519147
+5.712556147497515
+5.206026515338916
+5.749621140061565
+3.0714726650374033
+6.576852312067237
+7.873101351668455
+6.565410149266118
+6.42923283018875
+4.576910183319347
+4.891822273316748
+6.059357175219146
+3.5324494806223328
+5.02539429500825
+6.078049839679652
+4.395054417468175
+5.710022806162443
+5.577091376894232
+3.131753802875934
+5.4869998928318
+6.413992453090146
+6.368380820674971
+6.052068461844252
+5.480278219624535
+7.051236227914206
+4.748415087916627
+4.559239556696287
+4.780665068784505
+5.349223485326002
+4.522305152002386
+5.678473361027541
+6.734219964637535
+6.713281662687456
+6.22214905332774
+5.716102543806569
+6.336616632829493
+4.8399311234283635
+5.811391244308217
+4.3965755331585905
+5.297963707368242
+5.021726117260926
+4.497125082388555
+4.735667209277485
+6.048804114181307
+4.264048138073914
+7.181013762432201
+4.781684059171574
+5.779533272721499
+4.164421460389599
+3.6986809242837757
+4.8415576143236185
+4.924528568365373
+4.758045335351253
+5.628351489493518
+5.7967639104855415
+4.491988822693669
+2.776750089391839
+4.704679957076673
+4.039607278211126
+5.660350110077993
+4.955611684289963
+3.0641653090331107
+4.896101875253389
+3.6528358436331603
+5.552472713484715
+4.857203367367906
+6.698826102960048
+4.485176970803183
+3.359916665137426
+4.036570806638963
+3.48793689188148
+4.19214761961293
+3.9792199677002866
+6.760873252923419
+4.333561067615548
+5.018626873497648
+3.377671327382937
+4.426183834007672
+8.806961710969402
+5.2068790380550265
+5.483008251803699
+4.287267636533901
+5.931330465014387
+5.257260104496106
+4.623908313559696
+4.365112456604631
+5.600514050451817
+6.184093404453588
+4.9116883675838485
+6.019780977080248
+7.485280872899538
+3.5982660410679284
+4.232210941334369
+5.446496617538108
+6.487976163896015
+3.3960660696641156
+4.733884295853101
+5.352545256764909
+4.107747627715545
+3.949506252011129
+5.017847997679714
+4.24906287118262
+6.720303792581198
+5.832137142236708
+5.010377506040941
+6.458070081692352
+6.501223021355141
+4.612768564431788
+3.801740464538825
+4.469721893125596
+5.061713024524103
+6.872685648715577
+6.145993249521355
+4.638532388190405
+4.70471535512485
+6.417576222531886
+4.118577249932789
+4.431328683019108
+4.747884983644578
+4.495605382683923
+3.5972439735401767
+5.210796056817244
+2.9160129894156026
+3.4596190721900797
+3.972452277026154
+5.5237190584690214
+6.104711796147512
+4.787324556447702
+4.548676032231089
+6.356843891618192
+3.6148998030697816
+4.760679260180754
+4.698041709266617
+4.244003753086054
+5.595546833817678
+3.2784025595193267
+5.326657347561453
+6.213858447402109
+5.213011705084566
+7.232075882741927
+4.806572191866972
+4.680144670146755
+3.946663831660007
+3.6143084085883554
+7.789315918667734
+7.099181638561095
+3.672742516732736
+5.953845998789752
+6.28847712720666
+6.946689084108734
+6.325454389782429
+4.334133006331358
+3.039424552213366
+4.847328734611504
+4.249781519880862
+6.126424188695286
+3.3823135936253257
+6.3280255743100025
+6.150431997847958
+5.4226742397784005
+5.94864601826791
+4.425906835511732
+4.897545227095195
+6.26027333638832
+3.647858418615367
+5.276322437292433
+4.176876069581277
+4.346107259567459
+3.1384418250329995
+4.212957347421948
+4.637757894698186
+6.535589923573854
+5.193072556110316
+5.017309492685374
+5.1750466093509
+4.6381038872450375
+6.071604634493695
+4.357240286904764
+5.122391923621759
+6.556903940099011
+3.8006848201076036
+4.522363890394659
+6.2456602471550635
+5.829300589393535
+4.452647643620209
+5.371890862621703
+4.948677662633424
+5.661113800822228
+5.773629402623548
+6.139823333391851
+6.093004328053013
+5.362399773949667
+6.915042845179306
+5.394739321037944
+5.141451574018252
+5.053294383161769
+4.9834920876470665
+6.812746808763125
+3.5705971688428266
+4.664119854301202
+6.310596552569324
+5.674835228932813
+3.4639740645984807
+4.788956793299906
+5.1005488900135845
+4.534989910256703
+3.931742089464332
+3.572625977535623
+5.374511045697475
+3.859408179493194
+5.767053789854141
+5.1414168750827285
+4.7490168496463525
+7.481142748403815
+4.5189492261011575
+5.40235395980428
+6.700234279658992
+3.5063554778412183
+3.9690452319798735
+3.00630763890251
+7.23611608840341
+5.018006325958164
+4.523410620276403
+4.076684362167451
+5.916234395538267
+7.047286572236027
+3.8682363461132017
+4.390658924201581
+4.5292330092964255
+5.07906584568947
+4.671213490610071
+4.095193931403399
+4.054590162572947
+3.2227278245030027
+6.132646335444107
+2.8407359953623814
+4.7279370282096655
+5.593872406613741
+3.382542536766184
+5.85844025043303
+6.461000354065181
+3.4994741020969773
+4.132683344034104
+5.647894883473891
+5.011301190267978
+4.401435886120444
+3.3496957519609927
+6.119687677370172
+4.644762759286699
+4.5205629205178735
+3.0320051244977195
+4.596487037061894
+5.14520534308978
+5.282269168918912
+5.761372455401502
+4.148416743583162
+6.372742039103559
+5.649143130777574
+5.084494528193606
+4.811163551671385
+3.9806520282362476
+4.411511792047385
+4.670987670787611
+5.768451736319585
+3.984558689428816
+5.3293696591099975
+5.413539058295544
+6.40970782426591
+5.481145473625602
+4.36515208836978
+5.161811987273001
+3.963554978392394
+5.098946908474979
+7.786683053797615
+4.927631219070586
+5.524180021898693
+4.523736107490982
+3.557364094609177
+6.128701594561169
+4.2509207146594115
+3.944944115965259
+4.966138264389299
+5.394430219583224
+6.77531735530901
+4.128069102169693
+5.2683457909620355
+3.8872836447608496
+4.486696800422189
+6.5335585640393825
+4.916400608546338
+4.270979919569207
+4.311416898242187
+4.498167295277512
+6.132808180917634
+5.1041291367018875
+5.498388642491546
+5.584526454067219
+6.142894025331306
+4.944671156061267
+4.1686843376349945
+4.818977261651865
+6.235820918635881
+3.3212806573760028
+5.68435151611855
+6.189749316228399
+3.591267557367338
+5.043902793214377
+7.818905185451641
+4.768708643560666
+5.669288800286096
+6.398657810380692
+5.3717200778027605
+5.2573487416126525
+4.822935237131512
+6.182572962936934
+5.6072955002277105
+3.9675191626756288
+6.350858167126948
+6.283995295688788
+4.445782391191543
+6.877548745307459
+5.3208290700871315
+6.09847688940267
+6.434994026138841
+4.32779758193763
+7.2924037238697
+5.419935895280957
+4.288818201810987
+7.242433265647824
+4.947890367713541
+5.916218606455959
+6.490437527083841
+4.617582424838291
+7.957708355752131
+4.879357620439287
+6.103294400805588
+5.639488259504568
+4.335236791293937
+5.202542624850618
+5.4406339076225505
+5.782530244910674
+4.055314639567904
+5.552293301411749
+5.290496801505254
+4.022580394801182
+4.625571974654451
+5.5086593656510825
+4.913637297182931
+4.906396844626936
+6.439485089212817
+5.7942799739945325
+7.158136207286507
+4.280431104751667
+3.9206066719991517
+5.127791240556268
+6.70098532482022
+4.657147097255419
+4.524267698037553
+4.647534545829241
+4.839690189371444
+6.798322548455047
+5.094754599613737
+5.916399329150566
+5.767837713902285
+5.294550523894544
+4.161295164684424
+5.233358678928891
+5.546871474458429
+4.897048191655597
+3.939430251326603
+5.005888208270397
+3.2926576330038655
+4.0159694347757835
+5.056229917378723
+6.568879235955665
+4.497327615853924
+4.690014685240942
+4.746884105330737
+4.841384111334085
+4.14796180246966
+5.461902744235217
+5.869304766250897
+3.52354738655413
+5.582741221891035
+4.997825621424692
+5.439611672191855
+4.819402835865619
+5.76136287301575
+6.143090288547951
+5.976125217642891
+6.157007787875113
+4.912778652906766
+6.540414953620538
+3.8210262932626495
+4.727149320768898
+4.955255599543759
+5.7983414047818265
+5.167409288825197
+5.059246623397723
+6.965380962189423
+5.531311904089661
+5.4022568784996885
+4.344352255655229
+5.745261070226892
+5.118820012265567
+4.960430609470355
+4.487905086804239
+3.8537512154805835
+4.839114062528739
+5.367538410451759
+6.202050661574205
+4.001800559371117
+6.119617239220475
+3.236283913097008
+5.610134770285298
+5.757041556538514
+4.083399027093518
+5.055588718117847
+4.580930359877383
+6.545516697552579
+5.916270431823864
+3.761559453909257
+6.037777237143994
+7.29718541816528
+4.8965176227762734
+3.941358569293476
+3.9289815988008847
+3.2604315357316436
+4.639329221347256
+6.570997662310685
+3.851958625190621
+5.859087244914328
+4.647365626452129
+6.076778087850363
+4.627936340272149
+4.422345848512504
+6.2183675417422
+5.243889853389288
+5.90909311946919
+6.09260484846961
+6.0271781583360475
+6.913810502971691
+5.285845705409185
+5.318460367681083
+5.179580543035928
+4.6834977896331615
+5.382546996207003
+4.606307320228796
+4.038858683454586
+6.271279252908354
+6.0668723017439365
+5.713564644555386
+5.144428649779485
+5.2496039700779615
+3.8392027391676207
+4.7050632415088876
+7.137275712725372
+4.208879180827962
+4.81480733241727
+4.699941077536472
+4.423440083005291
+5.742161495602944
+4.592506326637019
+6.224046541049566
+4.611093653533141
+6.1166037746546165
+5.904004955760586
+5.589433336321981
+4.57489510266225
+5.500028469975376
+4.382479722617695
+4.257470376496386
+6.373209588018213
+5.375461447759072
+2.8662337475774584
+4.699832117278568
+3.102810935311515
+6.501460955698313
+4.550333534073201
+7.944860661426514
+5.69020177364993
+4.006199611798767
+5.11813341012065
+4.896479097282944
+4.816212778128475
+4.940296064591277
+5.419056166663748
+3.4659391357743616
+7.246324501631043
+5.907112751874067
+5.614502217435809
+4.750614593197476
+7.0951293897280205
+4.3819944682346055
+4.958360318480322
+4.962367933857186
+5.715499159595656
+5.220101872658552
+6.088622700649866
+5.491586360474799
+4.656477235994459
+3.8695533953710326
+3.7143742249025538
+3.7411936672155304
+6.603415889834424
+5.62928670505705
+5.5959396553858785
+5.6577330557176095
+6.003846653929077
+4.508563176717718
+5.549881486113916
+4.953305865372426
+6.203554032873964
+5.612208234244517
+4.854464793588011
+5.263585016371758
+3.897600440182904
+5.981235398882815
+5.531277293213279
+4.8817070690071445
+3.712544699529063
+3.513432242611217
+5.006035295792077
+7.124520658535316
+3.4782033127607037
+4.829578059223972
+5.742892584711905
+4.361333503197903
+4.601687049512891
+6.035189727259647
+4.711273209758127
+4.272043208125593
+4.447702393976457
+5.17487393756583
+4.741015989802225
+4.953808452791662
+4.6645084492292765
+4.276788530554644
+7.325515154525428
+4.602597440231014
+5.082884146093998
+3.068409439905545
+4.809983425115099
+3.8747882947708083
+4.893233436073575
+5.376932606908371
+6.239910432522629
+6.041695571547008
+5.317735375674715
+5.160517819092331
+5.283748111202239
+6.5357867130743745
+5.537247902605441
+5.4185896683530235
+5.287616337544387
+5.981700012459223
+5.992385624329782
+5.758772999982491
+4.599744432168506
+5.7237660286844605
+2.5862937961454855
+4.319918124665613
+7.566860260437548
+3.202784785619934
+6.67642720284947
+5.215802050091852
+5.452814592454087
+4.192858032386887
+5.299199379721475
+3.291677765243241
+4.632695766333648
+5.115714853147839
+4.996260485718097
+3.5271286032511773
+4.659715887897552
+6.587392147323261
+5.989132075359954
+3.8378063660060056
+4.975951043892332
+3.90853196371359
+5.708783809093124
+6.591895462100242
+5.653528117636727
+3.665428787393319
+4.324537690925271
+6.234413976864244
+4.053504794002944
+5.713371183460703
+4.670243561862966
+3.352660528859447
+4.020147292531281
+5.121933145078237
+4.282377411958472
+4.088770874857499
+4.275716553910016
+4.284046155337823
+6.449567142111275
+3.3275914286077084
+4.837717853228399
+5.261182985672333
+6.073443097165901
+5.40483608136289
+4.690566013556853
+4.222184746341714
+5.790245443382679
+5.020060832906476
+5.576527321711127
+5.340393035828579
+5.301460661931292
+5.076040366457228
+6.296482877500045
+3.037720796600903
+6.321850760102656
+5.701339165316606
+4.991940459105436
+5.758970102557518
+4.322111367356909
+5.721255109646473
+5.511881303620453
+4.9563635195228954
+6.861001584068987
+3.8299029968884195
+4.322974731453332
+5.3047403550360634
+6.0756269754391825
+6.117153630436378
+4.5085862451026495
+4.832132638553977
+4.699215334058029
+7.982648077178181
+3.303778194960711
+6.845166964779691
+5.175136241842978
+5.611538016661082
+4.293354218279116
+6.2617605857039775
+4.646868778200023
+5.596211970851805
+6.4731028962866635
+5.9737535333484795
+6.411386536458501
+2.7695062051965302
+3.5560570906765894
+5.451690061978083
+6.503535887841675
+4.695530301460264
+4.706120568510652
+2.800841111510871
+5.364729318170148
+5.1911558656154835
+5.947415408072919
+4.777513714112934
+4.596459418828304
+5.043317097051506
+5.174749896541634
+5.258257882159918
+3.887023257269741
+5.131383317673293
+5.843231353166214
+6.472487193651527
+5.763704927517821
+6.024396779444038
+4.926879229092987
+6.558645082464584
+5.447575064546803
+4.286751335276036
+3.9071252303818644
+4.618489035299945
+5.088217807208579
+3.808752600228301
+5.861810119867259
+4.033532296400091
+5.74542761207288
+4.925806147050348
+3.679404591586196
+4.05604604887352
+5.87881882930846
+4.513573760688276
+4.915009783906388
+3.654483449601882
+4.912095784340134
+3.3774256594506396
+4.188548007093734
+5.4860540834510445
+4.483111427918742
+6.091604204270534
+4.913639044459108
+6.347957296069254
+5.777137740280461
+4.996625717628335
+3.357832000765961
+4.529640780144531
+6.655383658310578
+5.187418414545693
+5.275067584707507
+5.50723064248028
+4.636201988408981
+4.947416066568987
+7.027581910469225
+4.570962245627946
+5.947355941474328
+4.7057667163042245
+4.786943520378938
+5.615852784022176
+4.645129057815488
+5.263882354785195
+3.844951724466573
+5.554260404852657
+4.684091248268045
+5.13336102667963
+3.417837773686996
+4.392489033666552
+6.270027300253521
+6.102372796945901
+4.219653651099504
+5.076173402237902
+4.383422445264855
+3.0437995085361025
+5.377941796580727
+6.276975902314367
+4.315133675763909
+5.507204150696545
+4.886780791403244
+7.147240935203286
+3.900457465197911
+5.102470142455588
+7.084247234995372
+5.457300111792919
+4.60867925423519
+6.2840312118540815
+7.236947706509271
+7.133509547170027
+4.3015318378968
+5.043756433592529
+5.108881706267525
+5.5240023845728645
+5.858632364389344
+5.981971317600007
+6.259948473084726
+4.062783955426871
+5.218852203995356
+3.8038254404258813
+4.758585778361602
+4.376196481713867
+4.458880802424765
+3.96326498727664
+3.6778134622710104
+4.374934998721925
+7.489468914416122
+5.700987063590436
+3.3100952240676955
+5.1696122166092415
+6.541584919841012
+4.4595571152023465
+4.366611842258099
+5.382259676070623
+4.7794428978336825
+3.757838857759169
+6.545307984939696
+4.881890171568036
+5.7063933726311165
+4.7730257133517116
+3.873677842944983
+3.840259191338565
+4.593661080441791
+4.511107632929962
+5.5385052402039605
+5.441167937479936
+5.984890322415174
+5.403820054129332
+5.148546201719365
+4.838476271562129
+6.2440438844133075
+3.9741885421050913
+6.327490860577795
+4.633940514497735
+5.232122748521683
+4.456999940494487
+5.576626928088951
+3.2818610857426584
+5.134684374559793
+4.602466559265273
+5.891324885962796
+5.517816321593768
+6.624687761337339
+5.2683180340267874
+4.662418552035468
+4.622236368091395
+5.536060664096081
+3.272870360657461
+3.9899131914173696
+5.121549579739896
+5.928806028927443
+4.259133981719825
+5.313734011651727
+5.635277610987355
+4.524627655490917
diff --git a/src/test/resources/org/apache/commons/math/stat/data/Lew.txt b/src/test/resources/org/apache/commons/math/stat/data/Lew.txt
new file mode 100644
index 0000000..2474560
--- /dev/null
+++ b/src/test/resources/org/apache/commons/math/stat/data/Lew.txt
@@ -0,0 +1,252 @@
+#####################################################################
+# Dataset Name:  Lew (Beam Deflection Data)
+#
+# Description:   This is an observed/"real world" data set
+#               consisting of 200 deflections of a steel-concrete
+#               beam while subjected to periodic pressure.
+#               The experimenter was H. S. Lew of the
+#               Center for Building Technology at NIST.
+#               We here use this data to test accuracy
+#               in summary statistics calculations.
+#
+# Stat Category: Univariate: Summary Statistics
+#
+# Reference:     http://www.itl.nist.gov/div898/strd/univ/lew.html
+#
+# Data:         "Real World"
+#               1    Response          : y = beam deflection
+#               0    Predictors
+#               200  Observations
+#
+# Model:        Lower Level of Difficulty
+#               2    Parameters        : mu, sigma
+#               1    Response Variable : y
+#               0    Predictor Variables
+#               y    = mu + e
+#####################################################################
+
+#####################################################################
+#
+# Certified Values
+#
+#####################################################################
+n                          =  200
+mean                       = -177.435000000000
+standardDeviation          =  277.332168044316
+autocorrelationCoefficient = -0.307304800605679
+
+#####################################################################
+#
+# R Generated Values
+#
+#####################################################################
+variance =  76913.13143
+max      =  300
+min      = -579
+sum      = -35487
+
+#####################################################################
+#
+# Data
+#
+#####################################################################
+    -213
+    -564
+     -35
+     -15
+     141
+     115
+    -420
+    -360
+     203
+    -338
+    -431
+     194
+    -220
+    -513
+     154
+    -125
+    -559
+      92
+     -21
+    -579
+     -52
+      99
+    -543
+    -175
+     162
+    -457
+    -346
+     204
+    -300
+    -474
+     164
+    -107
+    -572
+      -8
+      83
+    -541
+    -224
+     180
+    -420
+    -374
+     201
+    -236
+    -531
+      83
+      27
+    -564
+    -112
+     131
+    -507
+    -254
+     199
+    -311
+    -495
+     143
+     -46
+    -579
+     -90
+     136
+    -472
+    -338
+     202
+    -287
+    -477
+     169
+    -124
+    -568
+      17
+      48
+    -568
+    -135
+     162
+    -430
+    -422
+     172
+     -74
+    -577
+     -13
+      92
+    -534
+    -243
+     194
+    -355
+    -465
+     156
+     -81
+    -578
+     -64
+     139
+    -449
+    -384
+     193
+    -198
+    -538
+     110
+     -44
+    -577
+      -6
+      66
+    -552
+    -164
+     161
+    -460
+    -344
+     205
+    -281
+    -504
+     134
+     -28
+    -576
+    -118
+     156
+    -437
+    -381
+     200
+    -220
+    -540
+      83
+      11
+    -568
+    -160
+     172
+    -414
+    -408
+     188
+    -125
+    -572
+     -32
+     139
+    -492
+    -321
+     205
+    -262
+    -504
+     142
+     -83
+    -574
+       0
+      48
+    -571
+    -106
+     137
+    -501
+    -266
+     190
+    -391
+    -406
+     194
+    -186
+    -553
+      83
+     -13
+    -577
+     -49
+     103
+    -515
+    -280
+     201
+     300
+    -506
+     131
+     -45
+    -578
+     -80
+     138
+    -462
+    -361
+     201
+    -211
+    -554
+      32
+      74
+    -533
+    -235
+     187
+    -372
+    -442
+     182
+    -147
+    -566
+      25
+      68
+    -535
+    -244
+     194
+    -351
+    -463
+     174
+    -125
+    -570
+      15
+      72
+    -550
+    -190
+     172
+    -424
+    -385
+     198
+    -218
+    -536
+      96
diff --git a/src/test/resources/org/apache/commons/math/stat/data/Lottery.txt b/src/test/resources/org/apache/commons/math/stat/data/Lottery.txt
new file mode 100644
index 0000000..ef5e931
--- /dev/null
+++ b/src/test/resources/org/apache/commons/math/stat/data/Lottery.txt
@@ -0,0 +1,261 @@
+#####################################################################
+# Dataset Name:  Lottery
+#
+#Description:   This is an observed/"real world" data set
+#               consisting of 218 lottery values
+#               from September 3, 1989 to April 14, 1990 (32 weeks).
+#               One 3-digit random number (from 000 to 999)
+#               is drawn per day, 7 days per week for most
+#               weeks, but fewer days per week for some weeks.
+#               We here use this data to test accuracy
+#               in summary statistics calculations.
+#
+# Stat Category: Univariate: Summary Statistics
+#
+# Reference:     http://www.itl.nist.gov/div898/strd/univ/lottery.html
+#
+# Data:          "Real World"
+#               1    Response          : y = 3-digit random number
+#               0    Predictors
+#               218  Observations
+#
+# Model:         Lower Level of Difficulty
+#               2    Parameters        : mu, sigma
+#               1    Response Variable : y
+#               0    Predictor Variables#
+#               y    = mu + e
+#####################################################################
+
+#####################################################################
+#
+# Certified Values
+#
+#####################################################################
+mean                       = 518.958715596330
+standardDeviation          = 291.699727470969
+autocorrelationCoefficient =  -0.120948622967393
+n                          = 218
+
+#####################################################################
+#
+# Data
+#
+#####################################################################
+     162
+     671
+     933
+     414
+     788
+     730
+     817
+      33
+     536
+     875
+     670
+     236
+     473
+     167
+     877
+     980
+     316
+     950
+     456
+      92
+     517
+     557
+     956
+     954
+     104
+     178
+     794
+     278
+     147
+     773
+     437
+     435
+     502
+     610
+     582
+     780
+     689
+     562
+     964
+     791
+      28
+      97
+     848
+     281
+     858
+     538
+     660
+     972
+     671
+     613
+     867
+     448
+     738
+     966
+     139
+     636
+     847
+     659
+     754
+     243
+     122
+     455
+     195
+     968
+     793
+      59
+     730
+     361
+     574
+     522
+      97
+     762
+     431
+     158
+     429
+     414
+      22
+     629
+     788
+     999
+     187
+     215
+     810
+     782
+      47
+      34
+     108
+     986
+      25
+     644
+     829
+     630
+     315
+     567
+     919
+     331
+     207
+     412
+     242
+     607
+     668
+     944
+     749
+     168
+     864
+     442
+     533
+     805
+     372
+      63
+     458
+     777
+     416
+     340
+     436
+     140
+     919
+     350
+     510
+     572
+     905
+     900
+      85
+     389
+     473
+     758
+     444
+     169
+     625
+     692
+     140
+     897
+     672
+     288
+     312
+     860
+     724
+     226
+     884
+     508
+     976
+     741
+     476
+     417
+     831
+      15
+     318
+     432
+     241
+     114
+     799
+     955
+     833
+     358
+     935
+     146
+     630
+     830
+     440
+     642
+     356
+     373
+     271
+     715
+     367
+     393
+     190
+     669
+       8
+     861
+     108
+     795
+     269
+     590
+     326
+     866
+      64
+     523
+     862
+     840
+     219
+     382
+     998
+       4
+     628
+     305
+     747
+     247
+      34
+     747
+     729
+     645
+     856
+     974
+      24
+     568
+      24
+     694
+     608
+     480
+     410
+     729
+     947
+     293
+      53
+     930
+     223
+     203
+     677
+     227
+      62
+     455
+     387
+     318
+     562
+     242
+     428
+     968
diff --git a/src/test/resources/org/apache/commons/math/stat/data/Mavro.txt b/src/test/resources/org/apache/commons/math/stat/data/Mavro.txt
new file mode 100644
index 0000000..a3f7c7a
--- /dev/null
+++ b/src/test/resources/org/apache/commons/math/stat/data/Mavro.txt
@@ -0,0 +1,110 @@
+File Name:     Mavro.dat
+
+File Format:   ASCII
+               Header          : lines  1 to  60     (= 60)
+               Certified Values: lines 41 to  43     (=  3)
+               Data            : lines 61 to 110     (= 50)
+
+Dataset Name:  Mavro (Filter Transmittance Data)
+
+Description:   This is an observed/"real world" data set
+               consisting of 50 transmittance measurements
+               (at a sampling rate of 10 observations per second)
+               from a filter with a nominal value of 2.
+               The experimenter was Radu Mavrodineaunu,
+               a member of the chemistry staff at NIST.
+               We here use this data to test accuracy
+               in summary statistics calculations.
+
+Stat Category: Univariate: Summary Statistics
+
+Reference:     None
+
+Data:          "Real World"
+               1    Response          : y = transmittance
+               0    Predictors
+               50   Observations
+
+Model:         Lower Level of Difficulty
+               2    Parameters        : mu, sigma
+               1    Response Variable : y
+               0    Predictor Variables
+
+               y    = mu + e
+
+
+
+
+
+
+                                                  Certified Values
+Sample Mean                                ybar:  2.00185600000000
+Sample Standard Deviation (denom. = n-1)      s:  0.000429123454003053
+Sample Autocorrelation Coefficient (lag 1) r(1):  0.937989183438248
+
+Number of Observations:                              50
+
+
+
+
+
+
+
+
+
+
+
+
+
+Data: Y
+-------------
+   2.00180
+   2.00170
+   2.00180
+   2.00190
+   2.00180
+   2.00170
+   2.00150
+   2.00140
+   2.00150
+   2.00150
+   2.00170
+   2.00180
+   2.00180
+   2.00190
+   2.00190
+   2.00210
+   2.00200
+   2.00160
+   2.00140
+   2.00130
+   2.00130
+   2.00150
+   2.00150
+   2.00160
+   2.00150
+   2.00140
+   2.00130
+   2.00140
+   2.00150
+   2.00140
+   2.00150
+   2.00160
+   2.00150
+   2.00160
+   2.00190
+   2.00200
+   2.00200
+   2.00210
+   2.00220
+   2.00230
+   2.00240
+   2.00250
+   2.00270
+   2.00260
+   2.00260
+   2.00260
+   2.00270
+   2.00260
+   2.00250
+   2.00240
diff --git a/src/test/resources/org/apache/commons/math/stat/data/Michelso.txt b/src/test/resources/org/apache/commons/math/stat/data/Michelso.txt
new file mode 100644
index 0000000..91322cd
--- /dev/null
+++ b/src/test/resources/org/apache/commons/math/stat/data/Michelso.txt
@@ -0,0 +1,160 @@
+File Name:     Michelso.dat
+
+File Format:   ASCII
+               Header          : lines  1 to  60     (=  60)
+               Certified Values: lines 41 to  43     (=   3)
+               Data            : lines 61 to 160     (= 100)
+
+Dataset Name:  Michelso (Speed of Light Data, in millions of meters per second)
+
+Description:   This is an observed/"real world" data set
+               consisting of 100 measurements of the
+               speed of light in air.  This classic experiment
+               was carried out by Michelson is 1879.
+               We here use this data to test accuracy
+               in summary statistics calculations.
+
+Stat Category: Univariate: Summary Statistics
+
+Reference:     Dorsey, Ernest N. (1944). The Velocity of Light.
+               Transactions of the American Philiosophical
+               Society, Volume 34, Part 1, Pages 1-110, Table 22.
+
+               y    = mu + e
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+                                                  Certified Values
+Sample Mean                                ybar:  299.852400000000
+Sample Standard Deviation (denom. = n-1)      s:  0.0790105478190518
+Sample Autocorrelation Coefficient (lag 1) r(1):  0.535199668621283
+
+Number of Observations:                             100
+
+
+
+
+
+
+
+
+
+
+
+
+
+Data: Y
+----------
+  299.85
+  299.74
+  299.90
+  300.07
+  299.93
+  299.85
+  299.95
+  299.98
+  299.98
+  299.88
+  300.00
+  299.98
+  299.93
+  299.65
+  299.76
+  299.81
+  300.00
+  300.00
+  299.96
+  299.96
+  299.96
+  299.94
+  299.96
+  299.94
+  299.88
+  299.80
+  299.85
+  299.88
+  299.90
+  299.84
+  299.83
+  299.79
+  299.81
+  299.88
+  299.88
+  299.83
+  299.80
+  299.79
+  299.76
+  299.80
+  299.88
+  299.88
+  299.88
+  299.86
+  299.72
+  299.72
+  299.62
+  299.86
+  299.97
+  299.95
+  299.88
+  299.91
+  299.85
+  299.87
+  299.84
+  299.84
+  299.85
+  299.84
+  299.84
+  299.84
+  299.89
+  299.81
+  299.81
+  299.82
+  299.80
+  299.77
+  299.76
+  299.74
+  299.75
+  299.76
+  299.91
+  299.92
+  299.89
+  299.86
+  299.88
+  299.72
+  299.84
+  299.85
+  299.85
+  299.78
+  299.89
+  299.84
+  299.78
+  299.81
+  299.76
+  299.81
+  299.79
+  299.81
+  299.82
+  299.85
+  299.87
+  299.87
+  299.81
+  299.74
+  299.81
+  299.94
+  299.95
+  299.80
+  299.81
+  299.87
diff --git a/src/test/resources/org/apache/commons/math/stat/data/NumAcc1.txt b/src/test/resources/org/apache/commons/math/stat/data/NumAcc1.txt
new file mode 100644
index 0000000..b7a368e
--- /dev/null
+++ b/src/test/resources/org/apache/commons/math/stat/data/NumAcc1.txt
@@ -0,0 +1,63 @@
+File Name:     NumAcc1.dat
+
+File Format:   ASCII
+               Header          : lines  1 to 60       (= 60)
+               Certified Values: lines 41 to 43       (=  3)
+               Data            : lines 61 to 63       (=  3)
+
+Dataset Name:  NumAcc1
+
+Description:   This is a constructed/fabricated data set
+               to test accuracy in summary statistic calculations.
+               The numbers are large (8-digit integers) and
+               differ only in the last decimal place.
+               Note--by construction, this data set has
+                     sample mean                  = 10000002 (exact)
+                     sample standard deviation    =        1 (exact)
+                     sample autocorrelation coef. =     -0.5 (exact)
+
+Stat Category: Univariate: Summary Statistics
+
+Reference:     Simon, Stephen D. and Lesage, James P. (1989).
+               Assessing the Accuracy of ANOVA Caluclations
+               in Statistical Software", Computational
+               Statistics & data Analysis, 8, pp. 325-332.
+
+Data:          Constructed
+               1    Response           : y
+               0    Predictors
+               3    Observations
+
+Model:         Lower Level of Difficulty
+               2    Parameters         : mu, sigma
+               1    Response Variable  : y
+               0    Predictor Variables
+
+               y    = mu + e
+
+
+
+                                                  Certified Values
+Sample Mean                                ybar:  10000002 
+Sample Standard Deviation (denom. = n-1)      s:         1 
+Sample Autocorrelation Coefficient (lag 1) r(1):      -0.5 
+
+Number of Observations:                                       3
+
+
+
+
+
+
+
+
+
+
+
+
+
+Data: Y
+---------
+10000001
+10000003
+10000002
diff --git a/src/test/resources/org/apache/commons/math/stat/data/NumAcc2.txt b/src/test/resources/org/apache/commons/math/stat/data/NumAcc2.txt
new file mode 100644
index 0000000..0efde02
--- /dev/null
+++ b/src/test/resources/org/apache/commons/math/stat/data/NumAcc2.txt
@@ -0,0 +1,1061 @@
+File Name:     NumAcc2.dat
+
+File Format:   ASCII
+               Header          : lines  1 to   60     (=   60)
+               Certified Values: lines 41 to   43     (=    3)
+               Data            : lines 61 to 1061     (= 1001)
+
+Dataset Name:  NumAcc2
+
+Description:   This is a constructed/fabricated data set
+               to test accuracy in summary statistic calculations.
+               The numbers are 2-digit floating point values and
+               differ only in the last decimal place.
+               Note--by construction, this data set has
+                     sample mean                  =  1.2   (exact)
+                     sample standard deviation    =  0.1   (exact)
+                     sample autocorrelation coef. = -0.999 (exact)
+
+Stat Category: Univariate
+
+Reference:     Simon, Stephen D. and Lesage, James P. (1989).
+               Assessing the Accuracy of ANOVA Caluclations
+               in Statistical Software", Computational
+               Statistics & data Analysis, 8, pp. 325-332.
+
+
+Data:          Constructed
+               1    Response           : y
+               0    Predictors
+               1001 Observations
+
+Model:         Average Level of Difficulty
+               2    Parameters         : mu, sigma
+               1    Response Variable  : y
+               0    Predictor Variables
+
+               y    = mu + e
+
+
+                                                  Certified Values
+Sample Mean                                ybar:  1.2   
+Sample Standard Deviation (denom. = n-1)      s:  0.1    
+Sample Autocorrelation Coefficient (lag 1) r(1):  -0.999 
+
+Number of Observations:                             1001
+
+
+
+
+
+
+
+
+
+
+
+
+
+Data: Y
+---------
+     1.2
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
+     1.1
+     1.3
diff --git a/src/test/resources/org/apache/commons/math/stat/data/NumAcc3.txt b/src/test/resources/org/apache/commons/math/stat/data/NumAcc3.txt
new file mode 100644
index 0000000..292d8e0
--- /dev/null
+++ b/src/test/resources/org/apache/commons/math/stat/data/NumAcc3.txt
@@ -0,0 +1,1061 @@
+File Name:     NumAcc3.dat
+
+File Format:   ASCII
+               Header          : lines  1 to   60     (=   60)
+               Certified Values: lines 41 to   43     (=    3)
+               Data            : lines 61 to 1061     (= 1001)
+
+Dataset Name:  NumAcc3
+
+Description:   This is a constructed/fabricated data set
+               to test accuracy in summary statistic calculations.
+               The numbers are 8-digit floating point values and
+               differ only in the last decimal place.
+               Note--by construction, this data set has
+                     sample mean            =  1000000.2   (exact)
+                     sample standard dev.   =        0.1   (exact)
+                     sample autocorr. coef. =     -0.999   (exact)
+
+Stat Category: Univariate: Summary Statistics
+
+Reference:     Simon, Stephen D. and Lesage, James P. (1989).
+               Assessing the Accuracy of ANOVA Caluclations
+               in Statistical Software", Computational
+               Statistics & data Analysis, 8, pp. 325-332.
+
+
+Data:          Constructed
+               1    Response           : y
+               0    Predictors
+               1001 Observations
+
+Model:         Average Level of Difficulty
+               2    Parameters         : mu, sigma
+               1    Response Variable  : y
+               0    Predictor Variables
+
+               y    = mu + e
+
+
+                                                  Certified Values
+Sample Mean                                ybar:  1000000.2 
+Sample Standard Deviation (denom. = n-1)      s:  0.1       
+Sample Autocorrelation Coefficient (lag 1) r(1): -0.999     
+
+Number of Observations:                             1001
+
+
+
+
+
+
+
+
+
+
+
+
+
+Data: Y
+-------------
+  1000000.2
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
+  1000000.1
+  1000000.3
diff --git a/src/test/resources/org/apache/commons/math/stat/data/NumAcc4.txt b/src/test/resources/org/apache/commons/math/stat/data/NumAcc4.txt
new file mode 100644
index 0000000..382c040
--- /dev/null
+++ b/src/test/resources/org/apache/commons/math/stat/data/NumAcc4.txt
@@ -0,0 +1,1061 @@
+File Name:     NumAcc4.dat
+
+File Format:   ASCII
+               Header          : lines  1 to   60     (=   60)
+               Certified Values: lines 41 to   43     (=    3)
+               Data            : lines 61 to 1061     (= 1001)
+
+Dataset Name:  NumAcc4
+
+Description:   This is a constructed/fabricated data set
+               to test accuracy in summary statistic calculations.
+               The numbers are 9-digit floating point values and
+               differ only in the last decimal place.
+                     sample mean            =  10000000.2   (exact)
+                     sample standard dev.   =         0.1   (exact)
+                     sample autocorr. coef. =      -0.999   (exact)
+
+Stat Category: Univariate
+
+Reference:     Simon, Stephen D. and Lesage, James P. (1989).
+               Assessing the Accuracy of ANOVA Caluclations
+               in Statistical Software", Computational
+               Statistics & data Analysis, 8, pp. 325-332.
+
+Data:          Constructed
+               1    Response           : y
+               0    Predictors
+               1001 Observations
+
+Model:         Higher Level of Difficulty
+               2    Parameters         : mu, sigma
+               1    Response Variable  : y
+               0    Predictor Variables
+
+               y    = mu + e
+
+
+
+
+                                                  Certified Values
+Sample Mean                                ybar:   10000000.2 
+Sample Standard Deviation (denom. = n-1)      s:   0.1        
+Sample Autocorrelation Coefficient (lag 1) r(1):   -0.999     
+
+Number of Observations:                             1001
+
+
+
+
+
+
+
+
+
+
+
+
+
+Data: Y
+--------------
+  10000000.2
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
+  10000000.1
+  10000000.3
diff --git a/src/test/resources/org/apache/commons/math/stat/data/PiDigits.txt b/src/test/resources/org/apache/commons/math/stat/data/PiDigits.txt
new file mode 100644
index 0000000..1e407b6
--- /dev/null
+++ b/src/test/resources/org/apache/commons/math/stat/data/PiDigits.txt
@@ -0,0 +1,5060 @@
+File Name:     PiDigits.dat
+
+File Format:   ASCII
+               Header          : lines  1 to   60     (=   60)
+               Certified Values: lines 41 to   43     (=    3)
+               Data            : lines 61 to 5060     (= 5000)
+
+Dataset Name:  PiDigits
+
+Description:   This is a constructed/fabricated data set
+               to test accuracy in summary statistic calculations.
+               The numbers are the first 5000 digits of the
+               mathematical constant pi (= 3.1415926535897932384...).
+
+Stat Category: Univariate
+
+Reference:     Mathematics of Computation.
+               January 1962, page 76.
+
+Data:          Constructed Variable
+               -->    1 Response           : y = pi digits
+               -->    0 Predictors
+               --> 5000 Observations
+
+Model:         Lower Level of Difficulty
+               -->    2 Parameters         : mu, sigma
+               -->    1 Response  Variable : y
+               -->    0 Predictor Variables
+
+               y    = mu + e
+
+
+
+
+
+
+
+
+
+                                                  Certified Values
+Sample Mean                                ybar:  4.53480000000000
+Sample Standard Deviation (denom. = n-1)      s:  2.86733906028871
+Sample Autocorrelation Coefficient (lag 1) r(1): -0.00355099287237972
+
+Number of Observations:                           5000
+
+
+
+
+
+
+
+
+
+
+
+
+
+Data: Y
+---------
+      3
+      1
+      4
+      1
+      5
+      9
+      2
+      6
+      5
+      3
+      5
+      8
+      9
+      7
+      9
+      3
+      2
+      3
+      8
+      4
+      6
+      2
+      6
+      4
+      3
+      3
+      8
+      3
+      2
+      7
+      9
+      5
+      0
+      2
+      8
+      8
+      4
+      1
+      9
+      7
+      1
+      6
+      9
+      3
+      9
+      9
+      3
+      7
+      5
+      1
+      0
+      5
+      8
+      2
+      0
+      9
+      7
+      4
+      9
+      4
+      4
+      5
+      9
+      2
+      3
+      0
+      7
+      8
+      1
+      6
+      4
+      0
+      6
+      2
+      8
+      6
+      2
+      0
+      8
+      9
+      9
+      8
+      6
+      2
+      8
+      0
+      3
+      4
+      8
+      2
+      5
+      3
+      4
+      2
+      1
+      1
+      7
+      0
+      6
+      7
+      9
+      8
+      2
+      1
+      4
+      8
+      0
+      8
+      6
+      5
+      1
+      3
+      2
+      8
+      2
+      3
+      0
+      6
+      6
+      4
+      7
+      0
+      9
+      3
+      8
+      4
+      4
+      6
+      0
+      9
+      5
+      5
+      0
+      5
+      8
+      2
+      2
+      3
+      1
+      7
+      2
+      5
+      3
+      5
+      9
+      4
+      0
+      8
+      1
+      2
+      8
+      4
+      8
+      1
+      1
+      1
+      7
+      4
+      5
+      0
+      2
+      8
+      4
+      1
+      0
+      2
+      7
+      0
+      1
+      9
+      3
+      8
+      5
+      2
+      1
+      1
+      0
+      5
+      5
+      5
+      9
+      6
+      4
+      4
+      6
+      2
+      2
+      9
+      4
+      8
+      9
+      5
+      4
+      9
+      3
+      0
+      3
+      8
+      1
+      9
+      6
+      4
+      4
+      2
+      8
+      8
+      1
+      0
+      9
+      7
+      5
+      6
+      6
+      5
+      9
+      3
+      3
+      4
+      4
+      6
+      1
+      2
+      8
+      4
+      7
+      5
+      6
+      4
+      8
+      2
+      3
+      3
+      7
+      8
+      6
+      7
+      8
+      3
+      1
+      6
+      5
+      2
+      7
+      1
+      2
+      0
+      1
+      9
+      0
+      9
+      1
+      4
+      5
+      6
+      4
+      8
+      5
+      6
+      6
+      9
+      2
+      3
+      4
+      6
+      0
+      3
+      4
+      8
+      6
+      1
+      0
+      4
+      5
+      4
+      3
+      2
+      6
+      6
+      4
+      8
+      2
+      1
+      3
+      3
+      9
+      3
+      6
+      0
+      7
+      2
+      6
+      0
+      2
+      4
+      9
+      1
+      4
+      1
+      2
+      7
+      3
+      7
+      2
+      4
+      5
+      8
+      7
+      0
+      0
+      6
+      6
+      0
+      6
+      3
+      1
+      5
+      5
+      8
+      8
+      1
+      7
+      4
+      8
+      8
+      1
+      5
+      2
+      0
+      9
+      2
+      0
+      9
+      6
+      2
+      8
+      2
+      9
+      2
+      5
+      4
+      0
+      9
+      1
+      7
+      1
+      5
+      3
+      6
+      4
+      3
+      6
+      7
+      8
+      9
+      2
+      5
+      9
+      0
+      3
+      6
+      0
+      0
+      1
+      1
+      3
+      3
+      0
+      5
+      3
+      0
+      5
+      4
+      8
+      8
+      2
+      0
+      4
+      6
+      6
+      5
+      2
+      1
+      3
+      8
+      4
+      1
+      4
+      6
+      9
+      5
+      1
+      9
+      4
+      1
+      5
+      1
+      1
+      6
+      0
+      9
+      4
+      3
+      3
+      0
+      5
+      7
+      2
+      7
+      0
+      3
+      6
+      5
+      7
+      5
+      9
+      5
+      9
+      1
+      9
+      5
+      3
+      0
+      9
+      2
+      1
+      8
+      6
+      1
+      1
+      7
+      3
+      8
+      1
+      9
+      3
+      2
+      6
+      1
+      1
+      7
+      9
+      3
+      1
+      0
+      5
+      1
+      1
+      8
+      5
+      4
+      8
+      0
+      7
+      4
+      4
+      6
+      2
+      3
+      7
+      9
+      9
+      6
+      2
+      7
+      4
+      9
+      5
+      6
+      7
+      3
+      5
+      1
+      8
+      8
+      5
+      7
+      5
+      2
+      7
+      2
+      4
+      8
+      9
+      1
+      2
+      2
+      7
+      9
+      3
+      8
+      1
+      8
+      3
+      0
+      1
+      1
+      9
+      4
+      9
+      1
+      2
+      9
+      8
+      3
+      3
+      6
+      7
+      3
+      3
+      6
+      2
+      4
+      4
+      0
+      6
+      5
+      6
+      6
+      4
+      3
+      0
+      8
+      6
+      0
+      2
+      1
+      3
+      9
+      4
+      9
+      4
+      6
+      3
+      9
+      5
+      2
+      2
+      4
+      7
+      3
+      7
+      1
+      9
+      0
+      7
+      0
+      2
+      1
+      7
+      9
+      8
+      6
+      0
+      9
+      4
+      3
+      7
+      0
+      2
+      7
+      7
+      0
+      5
+      3
+      9
+      2
+      1
+      7
+      1
+      7
+      6
+      2
+      9
+      3
+      1
+      7
+      6
+      7
+      5
+      2
+      3
+      8
+      4
+      6
+      7
+      4
+      8
+      1
+      8
+      4
+      6
+      7
+      6
+      6
+      9
+      4
+      0
+      5
+      1
+      3
+      2
+      0
+      0
+      0
+      5
+      6
+      8
+      1
+      2
+      7
+      1
+      4
+      5
+      2
+      6
+      3
+      5
+      6
+      0
+      8
+      2
+      7
+      7
+      8
+      5
+      7
+      7
+      1
+      3
+      4
+      2
+      7
+      5
+      7
+      7
+      8
+      9
+      6
+      0
+      9
+      1
+      7
+      3
+      6
+      3
+      7
+      1
+      7
+      8
+      7
+      2
+      1
+      4
+      6
+      8
+      4
+      4
+      0
+      9
+      0
+      1
+      2
+      2
+      4
+      9
+      5
+      3
+      4
+      3
+      0
+      1
+      4
+      6
+      5
+      4
+      9
+      5
+      8
+      5
+      3
+      7
+      1
+      0
+      5
+      0
+      7
+      9
+      2
+      2
+      7
+      9
+      6
+      8
+      9
+      2
+      5
+      8
+      9
+      2
+      3
+      5
+      4
+      2
+      0
+      1
+      9
+      9
+      5
+      6
+      1
+      1
+      2
+      1
+      2
+      9
+      0
+      2
+      1
+      9
+      6
+      0
+      8
+      6
+      4
+      0
+      3
+      4
+      4
+      1
+      8
+      1
+      5
+      9
+      8
+      1
+      3
+      6
+      2
+      9
+      7
+      7
+      4
+      7
+      7
+      1
+      3
+      0
+      9
+      9
+      6
+      0
+      5
+      1
+      8
+      7
+      0
+      7
+      2
+      1
+      1
+      3
+      4
+      9
+      9
+      9
+      9
+      9
+      9
+      8
+      3
+      7
+      2
+      9
+      7
+      8
+      0
+      4
+      9
+      9
+      5
+      1
+      0
+      5
+      9
+      7
+      3
+      1
+      7
+      3
+      2
+      8
+      1
+      6
+      0
+      9
+      6
+      3
+      1
+      8
+      5
+      9
+      5
+      0
+      2
+      4
+      4
+      5
+      9
+      4
+      5
+      5
+      3
+      4
+      6
+      9
+      0
+      8
+      3
+      0
+      2
+      6
+      4
+      2
+      5
+      2
+      2
+      3
+      0
+      8
+      2
+      5
+      3
+      3
+      4
+      4
+      6
+      8
+      5
+      0
+      3
+      5
+      2
+      6
+      1
+      9
+      3
+      1
+      1
+      8
+      8
+      1
+      7
+      1
+      0
+      1
+      0
+      0
+      0
+      3
+      1
+      3
+      7
+      8
+      3
+      8
+      7
+      5
+      2
+      8
+      8
+      6
+      5
+      8
+      7
+      5
+      3
+      3
+      2
+      0
+      8
+      3
+      8
+      1
+      4
+      2
+      0
+      6
+      1
+      7
+      1
+      7
+      7
+      6
+      6
+      9
+      1
+      4
+      7
+      3
+      0
+      3
+      5
+      9
+      8
+      2
+      5
+      3
+      4
+      9
+      0
+      4
+      2
+      8
+      7
+      5
+      5
+      4
+      6
+      8
+      7
+      3
+      1
+      1
+      5
+      9
+      5
+      6
+      2
+      8
+      6
+      3
+      8
+      8
+      2
+      3
+      5
+      3
+      7
+      8
+      7
+      5
+      9
+      3
+      7
+      5
+      1
+      9
+      5
+      7
+      7
+      8
+      1
+      8
+      5
+      7
+      7
+      3
+      0
+      5
+      3
+      2
+      1
+      7
+      1
+      2
+      2
+      6
+      8
+      0
+      6
+      6
+      1
+      3
+      0
+      0
+      1
+      9
+      2
+      7
+      8
+      7
+      6
+      6
+      1
+      1
+      1
+      9
+      5
+      9
+      0
+      9
+      2
+      1
+      6
+      4
+      2
+      0
+      1
+      9
+      8
+      9
+      3
+      8
+      0
+      9
+      5
+      2
+      5
+      7
+      2
+      0
+      1
+      0
+      6
+      5
+      4
+      8
+      5
+      8
+      6
+      3
+      2
+      7
+      8
+      8
+      6
+      5
+      9
+      3
+      6
+      1
+      5
+      3
+      3
+      8
+      1
+      8
+      2
+      7
+      9
+      6
+      8
+      2
+      3
+      0
+      3
+      0
+      1
+      9
+      5
+      2
+      0
+      3
+      5
+      3
+      0
+      1
+      8
+      5
+      2
+      9
+      6
+      8
+      9
+      9
+      5
+      7
+      7
+      3
+      6
+      2
+      2
+      5
+      9
+      9
+      4
+      1
+      3
+      8
+      9
+      1
+      2
+      4
+      9
+      7
+      2
+      1
+      7
+      7
+      5
+      2
+      8
+      3
+      4
+      7
+      9
+      1
+      3
+      1
+      5
+      1
+      5
+      5
+      7
+      4
+      8
+      5
+      7
+      2
+      4
+      2
+      4
+      5
+      4
+      1
+      5
+      0
+      6
+      9
+      5
+      9
+      5
+      0
+      8
+      2
+      9
+      5
+      3
+      3
+      1
+      1
+      6
+      8
+      6
+      1
+      7
+      2
+      7
+      8
+      5
+      5
+      8
+      8
+      9
+      0
+      7
+      5
+      0
+      9
+      8
+      3
+      8
+      1
+      7
+      5
+      4
+      6
+      3
+      7
+      4
+      6
+      4
+      9
+      3
+      9
+      3
+      1
+      9
+      2
+      5
+      5
+      0
+      6
+      0
+      4
+      0
+      0
+      9
+      2
+      7
+      7
+      0
+      1
+      6
+      7
+      1
+      1
+      3
+      9
+      0
+      0
+      9
+      8
+      4
+      8
+      8
+      2
+      4
+      0
+      1
+      2
+      8
+      5
+      8
+      3
+      6
+      1
+      6
+      0
+      3
+      5
+      6
+      3
+      7
+      0
+      7
+      6
+      6
+      0
+      1
+      0
+      4
+      7
+      1
+      0
+      1
+      8
+      1
+      9
+      4
+      2
+      9
+      5
+      5
+      5
+      9
+      6
+      1
+      9
+      8
+      9
+      4
+      6
+      7
+      6
+      7
+      8
+      3
+      7
+      4
+      4
+      9
+      4
+      4
+      8
+      2
+      5
+      5
+      3
+      7
+      9
+      7
+      7
+      4
+      7
+      2
+      6
+      8
+      4
+      7
+      1
+      0
+      4
+      0
+      4
+      7
+      5
+      3
+      4
+      6
+      4
+      6
+      2
+      0
+      8
+      0
+      4
+      6
+      6
+      8
+      4
+      2
+      5
+      9
+      0
+      6
+      9
+      4
+      9
+      1
+      2
+      9
+      3
+      3
+      1
+      3
+      6
+      7
+      7
+      0
+      2
+      8
+      9
+      8
+      9
+      1
+      5
+      2
+      1
+      0
+      4
+      7
+      5
+      2
+      1
+      6
+      2
+      0
+      5
+      6
+      9
+      6
+      6
+      0
+      2
+      4
+      0
+      5
+      8
+      0
+      3
+      8
+      1
+      5
+      0
+      1
+      9
+      3
+      5
+      1
+      1
+      2
+      5
+      3
+      3
+      8
+      2
+      4
+      3
+      0
+      0
+      3
+      5
+      5
+      8
+      7
+      6
+      4
+      0
+      2
+      4
+      7
+      4
+      9
+      6
+      4
+      7
+      3
+      2
+      6
+      3
+      9
+      1
+      4
+      1
+      9
+      9
+      2
+      7
+      2
+      6
+      0
+      4
+      2
+      6
+      9
+      9
+      2
+      2
+      7
+      9
+      6
+      7
+      8
+      2
+      3
+      5
+      4
+      7
+      8
+      1
+      6
+      3
+      6
+      0
+      0
+      9
+      3
+      4
+      1
+      7
+      2
+      1
+      6
+      4
+      1
+      2
+      1
+      9
+      9
+      2
+      4
+      5
+      8
+      6
+      3
+      1
+      5
+      0
+      3
+      0
+      2
+      8
+      6
+      1
+      8
+      2
+      9
+      7
+      4
+      5
+      5
+      5
+      7
+      0
+      6
+      7
+      4
+      9
+      8
+      3
+      8
+      5
+      0
+      5
+      4
+      9
+      4
+      5
+      8
+      8
+      5
+      8
+      6
+      9
+      2
+      6
+      9
+      9
+      5
+      6
+      9
+      0
+      9
+      2
+      7
+      2
+      1
+      0
+      7
+      9
+      7
+      5
+      0
+      9
+      3
+      0
+      2
+      9
+      5
+      5
+      3
+      2
+      1
+      1
+      6
+      5
+      3
+      4
+      4
+      9
+      8
+      7
+      2
+      0
+      2
+      7
+      5
+      5
+      9
+      6
+      0
+      2
+      3
+      6
+      4
+      8
+      0
+      6
+      6
+      5
+      4
+      9
+      9
+      1
+      1
+      9
+      8
+      8
+      1
+      8
+      3
+      4
+      7
+      9
+      7
+      7
+      5
+      3
+      5
+      6
+      6
+      3
+      6
+      9
+      8
+      0
+      7
+      4
+      2
+      6
+      5
+      4
+      2
+      5
+      2
+      7
+      8
+      6
+      2
+      5
+      5
+      1
+      8
+      1
+      8
+      4
+      1
+      7
+      5
+      7
+      4
+      6
+      7
+      2
+      8
+      9
+      0
+      9
+      7
+      7
+      7
+      7
+      2
+      7
+      9
+      3
+      8
+      0
+      0
+      0
+      8
+      1
+      6
+      4
+      7
+      0
+      6
+      0
+      0
+      1
+      6
+      1
+      4
+      5
+      2
+      4
+      9
+      1
+      9
+      2
+      1
+      7
+      3
+      2
+      1
+      7
+      2
+      1
+      4
+      7
+      7
+      2
+      3
+      5
+      0
+      1
+      4
+      1
+      4
+      4
+      1
+      9
+      7
+      3
+      5
+      6
+      8
+      5
+      4
+      8
+      1
+      6
+      1
+      3
+      6
+      1
+      1
+      5
+      7
+      3
+      5
+      2
+      5
+      5
+      2
+      1
+      3
+      3
+      4
+      7
+      5
+      7
+      4
+      1
+      8
+      4
+      9
+      4
+      6
+      8
+      4
+      3
+      8
+      5
+      2
+      3
+      3
+      2
+      3
+      9
+      0
+      7
+      3
+      9
+      4
+      1
+      4
+      3
+      3
+      3
+      4
+      5
+      4
+      7
+      7
+      6
+      2
+      4
+      1
+      6
+      8
+      6
+      2
+      5
+      1
+      8
+      9
+      8
+      3
+      5
+      6
+      9
+      4
+      8
+      5
+      5
+      6
+      2
+      0
+      9
+      9
+      2
+      1
+      9
+      2
+      2
+      2
+      1
+      8
+      4
+      2
+      7
+      2
+      5
+      5
+      0
+      2
+      5
+      4
+      2
+      5
+      6
+      8
+      8
+      7
+      6
+      7
+      1
+      7
+      9
+      0
+      4
+      9
+      4
+      6
+      0
+      1
+      6
+      5
+      3
+      4
+      6
+      6
+      8
+      0
+      4
+      9
+      8
+      8
+      6
+      2
+      7
+      2
+      3
+      2
+      7
+      9
+      1
+      7
+      8
+      6
+      0
+      8
+      5
+      7
+      8
+      4
+      3
+      8
+      3
+      8
+      2
+      7
+      9
+      6
+      7
+      9
+      7
+      6
+      6
+      8
+      1
+      4
+      5
+      4
+      1
+      0
+      0
+      9
+      5
+      3
+      8
+      8
+      3
+      7
+      8
+      6
+      3
+      6
+      0
+      9
+      5
+      0
+      6
+      8
+      0
+      0
+      6
+      4
+      2
+      2
+      5
+      1
+      2
+      5
+      2
+      0
+      5
+      1
+      1
+      7
+      3
+      9
+      2
+      9
+      8
+      4
+      8
+      9
+      6
+      0
+      8
+      4
+      1
+      2
+      8
+      4
+      8
+      8
+      6
+      2
+      6
+      9
+      4
+      5
+      6
+      0
+      4
+      2
+      4
+      1
+      9
+      6
+      5
+      2
+      8
+      5
+      0
+      2
+      2
+      2
+      1
+      0
+      6
+      6
+      1
+      1
+      8
+      6
+      3
+      0
+      6
+      7
+      4
+      4
+      2
+      7
+      8
+      6
+      2
+      2
+      0
+      3
+      9
+      1
+      9
+      4
+      9
+      4
+      5
+      0
+      4
+      7
+      1
+      2
+      3
+      7
+      1
+      3
+      7
+      8
+      6
+      9
+      6
+      0
+      9
+      5
+      6
+      3
+      6
+      4
+      3
+      7
+      1
+      9
+      1
+      7
+      2
+      8
+      7
+      4
+      6
+      7
+      7
+      6
+      4
+      6
+      5
+      7
+      5
+      7
+      3
+      9
+      6
+      2
+      4
+      1
+      3
+      8
+      9
+      0
+      8
+      6
+      5
+      8
+      3
+      2
+      6
+      4
+      5
+      9
+      9
+      5
+      8
+      1
+      3
+      3
+      9
+      0
+      4
+      7
+      8
+      0
+      2
+      7
+      5
+      9
+      0
+      0
+      9
+      9
+      4
+      6
+      5
+      7
+      6
+      4
+      0
+      7
+      8
+      9
+      5
+      1
+      2
+      6
+      9
+      4
+      6
+      8
+      3
+      9
+      8
+      3
+      5
+      2
+      5
+      9
+      5
+      7
+      0
+      9
+      8
+      2
+      5
+      8
+      2
+      2
+      6
+      2
+      0
+      5
+      2
+      2
+      4
+      8
+      9
+      4
+      0
+      7
+      7
+      2
+      6
+      7
+      1
+      9
+      4
+      7
+      8
+      2
+      6
+      8
+      4
+      8
+      2
+      6
+      0
+      1
+      4
+      7
+      6
+      9
+      9
+      0
+      9
+      0
+      2
+      6
+      4
+      0
+      1
+      3
+      6
+      3
+      9
+      4
+      4
+      3
+      7
+      4
+      5
+      5
+      3
+      0
+      5
+      0
+      6
+      8
+      2
+      0
+      3
+      4
+      9
+      6
+      2
+      5
+      2
+      4
+      5
+      1
+      7
+      4
+      9
+      3
+      9
+      9
+      6
+      5
+      1
+      4
+      3
+      1
+      4
+      2
+      9
+      8
+      0
+      9
+      1
+      9
+      0
+      6
+      5
+      9
+      2
+      5
+      0
+      9
+      3
+      7
+      2
+      2
+      1
+      6
+      9
+      6
+      4
+      6
+      1
+      5
+      1
+      5
+      7
+      0
+      9
+      8
+      5
+      8
+      3
+      8
+      7
+      4
+      1
+      0
+      5
+      9
+      7
+      8
+      8
+      5
+      9
+      5
+      9
+      7
+      7
+      2
+      9
+      7
+      5
+      4
+      9
+      8
+      9
+      3
+      0
+      1
+      6
+      1
+      7
+      5
+      3
+      9
+      2
+      8
+      4
+      6
+      8
+      1
+      3
+      8
+      2
+      6
+      8
+      6
+      8
+      3
+      8
+      6
+      8
+      9
+      4
+      2
+      7
+      7
+      4
+      1
+      5
+      5
+      9
+      9
+      1
+      8
+      5
+      5
+      9
+      2
+      5
+      2
+      4
+      5
+      9
+      5
+      3
+      9
+      5
+      9
+      4
+      3
+      1
+      0
+      4
+      9
+      9
+      7
+      2
+      5
+      2
+      4
+      6
+      8
+      0
+      8
+      4
+      5
+      9
+      8
+      7
+      2
+      7
+      3
+      6
+      4
+      4
+      6
+      9
+      5
+      8
+      4
+      8
+      6
+      5
+      3
+      8
+      3
+      6
+      7
+      3
+      6
+      2
+      2
+      2
+      6
+      2
+      6
+      0
+      9
+      9
+      1
+      2
+      4
+      6
+      0
+      8
+      0
+      5
+      1
+      2
+      4
+      3
+      8
+      8
+      4
+      3
+      9
+      0
+      4
+      5
+      1
+      2
+      4
+      4
+      1
+      3
+      6
+      5
+      4
+      9
+      7
+      6
+      2
+      7
+      8
+      0
+      7
+      9
+      7
+      7
+      1
+      5
+      6
+      9
+      1
+      4
+      3
+      5
+      9
+      9
+      7
+      7
+      0
+      0
+      1
+      2
+      9
+      6
+      1
+      6
+      0
+      8
+      9
+      4
+      4
+      1
+      6
+      9
+      4
+      8
+      6
+      8
+      5
+      5
+      5
+      8
+      4
+      8
+      4
+      0
+      6
+      3
+      5
+      3
+      4
+      2
+      2
+      0
+      7
+      2
+      2
+      2
+      5
+      8
+      2
+      8
+      4
+      8
+      8
+      6
+      4
+      8
+      1
+      5
+      8
+      4
+      5
+      6
+      0
+      2
+      8
+      5
+      0
+      6
+      0
+      1
+      6
+      8
+      4
+      2
+      7
+      3
+      9
+      4
+      5
+      2
+      2
+      6
+      7
+      4
+      6
+      7
+      6
+      7
+      8
+      8
+      9
+      5
+      2
+      5
+      2
+      1
+      3
+      8
+      5
+      2
+      2
+      5
+      4
+      9
+      9
+      5
+      4
+      6
+      6
+      6
+      7
+      2
+      7
+      8
+      2
+      3
+      9
+      8
+      6
+      4
+      5
+      6
+      5
+      9
+      6
+      1
+      1
+      6
+      3
+      5
+      4
+      8
+      8
+      6
+      2
+      3
+      0
+      5
+      7
+      7
+      4
+      5
+      6
+      4
+      9
+      8
+      0
+      3
+      5
+      5
+      9
+      3
+      6
+      3
+      4
+      5
+      6
+      8
+      1
+      7
+      4
+      3
+      2
+      4
+      1
+      1
+      2
+      5
+      1
+      5
+      0
+      7
+      6
+      0
+      6
+      9
+      4
+      7
+      9
+      4
+      5
+      1
+      0
+      9
+      6
+      5
+      9
+      6
+      0
+      9
+      4
+      0
+      2
+      5
+      2
+      2
+      8
+      8
+      7
+      9
+      7
+      1
+      0
+      8
+      9
+      3
+      1
+      4
+      5
+      6
+      6
+      9
+      1
+      3
+      6
+      8
+      6
+      7
+      2
+      2
+      8
+      7
+      4
+      8
+      9
+      4
+      0
+      5
+      6
+      0
+      1
+      0
+      1
+      5
+      0
+      3
+      3
+      0
+      8
+      6
+      1
+      7
+      9
+      2
+      8
+      6
+      8
+      0
+      9
+      2
+      0
+      8
+      7
+      4
+      7
+      6
+      0
+      9
+      1
+      7
+      8
+      2
+      4
+      9
+      3
+      8
+      5
+      8
+      9
+      0
+      0
+      9
+      7
+      1
+      4
+      9
+      0
+      9
+      6
+      7
+      5
+      9
+      8
+      5
+      2
+      6
+      1
+      3
+      6
+      5
+      5
+      4
+      9
+      7
+      8
+      1
+      8
+      9
+      3
+      1
+      2
+      9
+      7
+      8
+      4
+      8
+      2
+      1
+      6
+      8
+      2
+      9
+      9
+      8
+      9
+      4
+      8
+      7
+      2
+      2
+      6
+      5
+      8
+      8
+      0
+      4
+      8
+      5
+      7
+      5
+      6
+      4
+      0
+      1
+      4
+      2
+      7
+      0
+      4
+      7
+      7
+      5
+      5
+      5
+      1
+      3
+      2
+      3
+      7
+      9
+      6
+      4
+      1
+      4
+      5
+      1
+      5
+      2
+      3
+      7
+      4
+      6
+      2
+      3
+      4
+      3
+      6
+      4
+      5
+      4
+      2
+      8
+      5
+      8
+      4
+      4
+      4
+      7
+      9
+      5
+      2
+      6
+      5
+      8
+      6
+      7
+      8
+      2
+      1
+      0
+      5
+      1
+      1
+      4
+      1
+      3
+      5
+      4
+      7
+      3
+      5
+      7
+      3
+      9
+      5
+      2
+      3
+      1
+      1
+      3
+      4
+      2
+      7
+      1
+      6
+      6
+      1
+      0
+      2
+      1
+      3
+      5
+      9
+      6
+      9
+      5
+      3
+      6
+      2
+      3
+      1
+      4
+      4
+      2
+      9
+      5
+      2
+      4
+      8
+      4
+      9
+      3
+      7
+      1
+      8
+      7
+      1
+      1
+      0
+      1
+      4
+      5
+      7
+      6
+      5
+      4
+      0
+      3
+      5
+      9
+      0
+      2
+      7
+      9
+      9
+      3
+      4
+      4
+      0
+      3
+      7
+      4
+      2
+      0
+      0
+      7
+      3
+      1
+      0
+      5
+      7
+      8
+      5
+      3
+      9
+      0
+      6
+      2
+      1
+      9
+      8
+      3
+      8
+      7
+      4
+      4
+      7
+      8
+      0
+      8
+      4
+      7
+      8
+      4
+      8
+      9
+      6
+      8
+      3
+      3
+      2
+      1
+      4
+      4
+      5
+      7
+      1
+      3
+      8
+      6
+      8
+      7
+      5
+      1
+      9
+      4
+      3
+      5
+      0
+      6
+      4
+      3
+      0
+      2
+      1
+      8
+      4
+      5
+      3
+      1
+      9
+      1
+      0
+      4
+      8
+      4
+      8
+      1
+      0
+      0
+      5
+      3
+      7
+      0
+      6
+      1
+      4
+      6
+      8
+      0
+      6
+      7
+      4
+      9
+      1
+      9
+      2
+      7
+      8
+      1
+      9
+      1
+      1
+      9
+      7
+      9
+      3
+      9
+      9
+      5
+      2
+      0
+      6
+      1
+      4
+      1
+      9
+      6
+      6
+      3
+      4
+      2
+      8
+      7
+      5
+      4
+      4
+      4
+      0
+      6
+      4
+      3
+      7
+      4
+      5
+      1
+      2
+      3
+      7
+      1
+      8
+      1
+      9
+      2
+      1
+      7
+      9
+      9
+      9
+      8
+      3
+      9
+      1
+      0
+      1
+      5
+      9
+      1
+      9
+      5
+      6
+      1
+      8
+      1
+      4
+      6
+      7
+      5
+      1
+      4
+      2
+      6
+      9
+      1
+      2
+      3
+      9
+      7
+      4
+      8
+      9
+      4
+      0
+      9
+      0
+      7
+      1
+      8
+      6
+      4
+      9
+      4
+      2
+      3
+      1
+      9
+      6
+      1
+      5
+      6
+      7
+      9
+      4
+      5
+      2
+      0
+      8
+      0
+      9
+      5
+      1
+      4
+      6
+      5
+      5
+      0
+      2
+      2
+      5
+      2
+      3
+      1
+      6
+      0
+      3
+      8
+      8
+      1
+      9
+      3
+      0
+      1
+      4
+      2
+      0
+      9
+      3
+      7
+      6
+      2
+      1
+      3
+      7
+      8
+      5
+      5
+      9
+      5
+      6
+      6
+      3
+      8
+      9
+      3
+      7
+      7
+      8
+      7
+      0
+      8
+      3
+      0
+      3
+      9
+      0
+      6
+      9
+      7
+      9
+      2
+      0
+      7
+      7
+      3
+      4
+      6
+      7
+      2
+      2
+      1
+      8
+      2
+      5
+      6
+      2
+      5
+      9
+      9
+      6
+      6
+      1
+      5
+      0
+      1
+      4
+      2
+      1
+      5
+      0
+      3
+      0
+      6
+      8
+      0
+      3
+      8
+      4
+      4
+      7
+      7
+      3
+      4
+      5
+      4
+      9
+      2
+      0
+      2
+      6
+      0
+      5
+      4
+      1
+      4
+      6
+      6
+      5
+      9
+      2
+      5
+      2
+      0
+      1
+      4
+      9
+      7
+      4
+      4
+      2
+      8
+      5
+      0
+      7
+      3
+      2
+      5
+      1
+      8
+      6
+      6
+      6
+      0
+      0
+      2
+      1
+      3
+      2
+      4
+      3
+      4
+      0
+      8
+      8
+      1
+      9
+      0
+      7
+      1
+      0
+      4
+      8
+      6
+      3
+      3
+      1
+      7
+      3
+      4
+      6
+      4
+      9
+      6
+      5
+      1
+      4
+      5
+      3
+      9
+      0
+      5
+      7
+      9
+      6
+      2
+      6
+      8
+      5
+      6
+      1
+      0
+      0
+      5
+      5
+      0
+      8
+      1
+      0
+      6
+      6
+      5
+      8
+      7
+      9
+      6
+      9
+      9
+      8
+      1
+      6
+      3
+      5
+      7
+      4
+      7
+      3
+      6
+      3
+      8
+      4
+      0
+      5
+      2
+      5
+      7
+      1
+      4
+      5
+      9
+      1
+      0
+      2
+      8
+      9
+      7
+      0
+      6
+      4
+      1
+      4
+      0
+      1
+      1
+      0
+      9
+      7
+      1
+      2
+      0
+      6
+      2
+      8
+      0
+      4
+      3
+      9
+      0
+      3
+      9
+      7
+      5
+      9
+      5
+      1
+      5
+      6
+      7
+      7
+      1
+      5
+      7
+      7
+      0
+      0
+      4
+      2
+      0
+      3
+      3
+      7
+      8
+      6
+      9
+      9
+      3
+      6
+      0
+      0
+      7
+      2
+      3
+      0
+      5
+      5
+      8
+      7
+      6
+      3
+      1
+      7
+      6
+      3
+      5
+      9
+      4
+      2
+      1
+      8
+      7
+      3
+      1
+      2
+      5
+      1
+      4
+      7
+      1
+      2
+      0
+      5
+      3
+      2
+      9
+      2
+      8
+      1
+      9
+      1
+      8
+      2
+      6
+      1
+      8
+      6
+      1
+      2
+      5
+      8
+      6
+      7
+      3
+      2
+      1
+      5
+      7
+      9
+      1
+      9
+      8
+      4
+      1
+      4
+      8
+      4
+      8
+      8
+      2
+      9
+      1
+      6
+      4
+      4
+      7
+      0
+      6
+      0
+      9
+      5
+      7
+      5
+      2
+      7
+      0
+      6
+      9
+      5
+      7
+      2
+      2
+      0
+      9
+      1
+      7
+      5
+      6
+      7
+      1
+      1
+      6
+      7
+      2
+      2
+      9
+      1
+      0
+      9
+      8
+      1
+      6
+      9
+      0
+      9
+      1
+      5
+      2
+      8
+      0
+      1
+      7
+      3
+      5
+      0
+      6
+      7
+      1
+      2
+      7
+      4
+      8
+      5
+      8
+      3
+      2
+      2
+      2
+      8
+      7
+      1
+      8
+      3
+      5
+      2
+      0
+      9
+      3
+      5
+      3
+      9
+      6
+      5
+      7
+      2
+      5
+      1
+      2
+      1
+      0
+      8
+      3
+      5
+      7
+      9
+      1
+      5
+      1
+      3
+      6
+      9
+      8
+      8
+      2
+      0
+      9
+      1
+      4
+      4
+      4
+      2
+      1
+      0
+      0
+      6
+      7
+      5
+      1
+      0
+      3
+      3
+      4
+      6
+      7
+      1
+      1
+      0
+      3
+      1
+      4
+      1
+      2
+      6
+      7
+      1
+      1
+      1
+      3
+      6
+      9
+      9
+      0
+      8
+      6
+      5
+      8
+      5
+      1
+      6
+      3
+      9
+      8
+      3
+      1
+      5
+      0
+      1
+      9
+      7
+      0
+      1
+      6
+      5
+      1
+      5
+      1
+      1
+      6
+      8
+      5
+      1
+      7
+      1
+      4
+      3
+      7
+      6
+      5
+      7
+      6
+      1
+      8
+      3
+      5
+      1
+      5
+      5
+      6
+      5
+      0
+      8
+      8
+      4
+      9
+      0
+      9
+      9
+      8
+      9
+      8
+      5
+      9
+      9
+      8
+      2
+      3
+      8
+      7
+      3
+      4
+      5
+      5
+      2
+      8
+      3
+      3
+      1
+      6
+      3
+      5
+      5
+      0
+      7
+      6
+      4
+      7
+      9
+      1
+      8
+      5
+      3
+      5
+      8
+      9
+      3
+      2
+      2
+      6
+      1
+      8
+      5
+      4
+      8
+      9
+      6
+      3
+      2
+      1
+      3
+      2
+      9
+      3
+      3
+      0
+      8
+      9
+      8
+      5
+      7
+      0
+      6
+      4
+      2
+      0
+      4
+      6
+      7
+      5
+      2
+      5
+      9
+      0
+      7
+      0
+      9
+      1
+      5
+      4
+      8
+      1
+      4
+      1
+      6
+      5
+      4
+      9
+      8
+      5
+      9
+      4
+      6
+      1
+      6
+      3
+      7
+      1
+      8
+      0
+      2
+      7
+      0
+      9
+      8
+      1
+      9
+      9
+      4
+      3
+      0
+      9
+      9
+      2
+      4
+      4
+      8
+      8
+      9
+      5
+      7
+      5
+      7
+      1
+      2
+      8
+      2
+      8
+      9
+      0
+      5
+      9
+      2
+      3
+      2
+      3
+      3
+      2
+      6
+      0
+      9
+      7
+      2
+      9
+      9
+      7
+      1
+      2
+      0
+      8
+      4
+      4
+      3
+      3
+      5
+      7
+      3
+      2
+      6
+      5
+      4
+      8
+      9
+      3
+      8
+      2
+      3
+      9
+      1
+      1
+      9
+      3
+      2
+      5
+      9
+      7
+      4
+      6
+      3
+      6
+      6
+      7
+      3
+      0
+      5
+      8
+      3
+      6
+      0
+      4
+      1
+      4
+      2
+      8
+      1
+      3
+      8
+      8
+      3
+      0
+      3
+      2
+      0
+      3
+      8
+      2
+      4
+      9
+      0
+      3
+      7
+      5
+      8
+      9
+      8
+      5
+      2
+      4
+      3
+      7
+      4
+      4
+      1
+      7
+      0
+      2
+      9
+      1
+      3
+      2
+      7
+      6
+      5
+      6
+      1
+      8
+      0
+      9
+      3
+      7
+      7
+      3
+      4
+      4
+      4
+      0
+      3
+      0
+      7
+      0
+      7
+      4
+      6
+      9
+      2
+      1
+      1
+      2
+      0
+      1
+      9
+      1
+      3
+      0
+      2
+      0
+      3
+      3
+      0
+      3
+      8
+      0
+      1
+      9
+      7
+      6
+      2
+      1
+      1
+      0
+      1
+      1
+      0
+      0
+      4
+      4
+      9
+      2
+      9
+      3
+      2
+      1
+      5
+      1
+      6
+      0
+      8
+      4
+      2
+      4
+      4
+      4
+      8
+      5
+      9
+      6
+      3
+      7
+      6
+      6
+      9
+      8
+      3
+      8
+      9
+      5
+      2
+      2
+      8
+      6
+      8
+      4
+      7
+      8
+      3
+      1
+      2
+      3
+      5
+      5
+      2
+      6
+      5
+      8
+      2
+      1
+      3
+      1
+      4
+      4
+      9
+      5
+      7
+      6
+      8
+      5
+      7
+      2
+      6
+      2
+      4
+      3
+      3
+      4
+      4
+      1
+      8
+      9
+      3
+      0
+      3
+      9
+      6
+      8
+      6
+      4
+      2
+      6
+      2
+      4
+      3
+      4
+      1
+      0
+      7
+      7
+      3
+      2
+      2
+      6
+      9
+      7
+      8
+      0
+      2
+      8
+      0
+      7
+      3
+      1
+      8
+      9
+      1
+      5
+      4
+      4
+      1
+      1
+      0
+      1
+      0
+      4
+      4
+      6
+      8
+      2
+      3
+      2
+      5
+      2
+      7
+      1
+      6
+      2
+      0
+      1
+      0
+      5
+      2
+      6
+      5
+      2
+      2
+      7
+      2
+      1
+      1
+      1
+      6
+      6
+      0
+      3
+      9
+      6
+      6
+      6
+      5
+      5
+      7
+      3
+      0
+      9
+      2
+      5
+      4
+      7
+      1
+      1
+      0
+      5
+      5
+      7
+      8
+      5
+      3
+      7
+      6
+      3
+      4
+      6
+      6
+      8
+      2
+      0
+      6
+      5
+      3
+      1
+      0
+      9
+      8
+      9
+      6
+      5
+      2
+      6
+      9
+      1
+      8
+      6
+      2
+      0
+      5
+      6
+      4
+      7
+      6
+      9
+      3
+      1
+      2
+      5
+      7
+      0
+      5
+      8
+      6
+      3
+      5
+      6
+      6
+      2
+      0
+      1
+      8
+      5
+      5
+      8
+      1
+      0
+      0
+      7
+      2
+      9
+      3
+      6
+      0
+      6
+      5
+      9
+      8
+      7
+      6
+      4
+      8
+      6
+      1
+      1
+      7
+      9
+      1
+      0
+      4
+      5
+      3
+      3
+      4
+      8
+      8
+      5
+      0
+      3
+      4
+      6
+      1
+      1
+      3
+      6
+      5
+      7
+      6
+      8
+      6
+      7
+      5
+      3
+      2
+      4
+      9
+      4
+      4
+      1
+      6
+      6
+      8
+      0
+      3
+      9
+      6
+      2
+      6
+      5
+      7
+      9
+      7
+      8
+      7
+      7
+      1
+      8
+      5
+      5
+      6
+      0
+      8
+      4
+      5
+      5
+      2
+      9
+      6
+      5
+      4
+      1
+      2
+      6
+      6
+      5
+      4
+      0
+      8
+      5
+      3
+      0
+      6
+      1
+      4
+      3
+      4
+      4
+      4
+      3
+      1
+      8
+      5
+      8
+      6
+      7
+      6
+      9
+      7
+      5
+      1
+      4
+      5
+      6
+      6
+      1
+      4
+      0
+      6
+      8
+      0
+      0
+      7
+      0
+      0
+      2
+      3
+      7
+      8
+      7
+      7
+      6
+      5
+      9
+      1
+      3
+      4
+      4
+      0
+      1
+      7
+      1
+      2
+      7
+      4
+      9
+      4
+      7
+      0
+      4
+      2
+      0
+      5
+      6
+      2
+      2
+      3
+      0
+      5
+      3
+      8
+      9
+      9
+      4
+      5
+      6
+      1
+      3
+      1
+      4
+      0
+      7
+      1
+      1
+      2
+      7
+      0
+      0
+      0
+      4
+      0
+      7
+      8
+      5
+      4
+      7
+      3
+      3
+      2
+      6
+      9
+      9
+      3
+      9
+      0
+      8
+      1
+      4
+      5
+      4
+      6
+      6
+      4
+      6
+      4
+      5
+      8
+      8
+      0
+      7
+      9
+      7
+      2
+      7
+      0
+      8
+      2
+      6
+      6
+      8
+      3
+      0
+      6
+      3
+      4
+      3
+      2
+      8
+      5
+      8
+      7
+      8
+      5
+      6
+      9
+      8
+      3
+      0
+      5
+      2
+      3
+      5
+      8
+      0
+      8
+      9
+      3
+      3
+      0
+      6
+      5
+      7
+      5
+      7
+      4
+      0
+      6
+      7
+      9
+      5
+      4
+      5
+      7
+      1
+      6
+      3
+      7
+      7
+      5
+      2
+      5
+      4
+      2
+      0
+      2
+      1
+      1
+      4
+      9
+      5
+      5
+      7
+      6
+      1
+      5
+      8
+      1
+      4
+      0
+      0
+      2
+      5
+      0
+      1
+      2
+      6
+      2
+      2
+      8
+      5
+      9
+      4
+      1
+      3
+      0
+      2
+      1
+      6
+      4
+      7
+      1
+      5
+      5
+      0
+      9
+      7
+      9
+      2
+      5
+      9
+      2
+      3
+      0
+      9
+      9
+      0
+      7
+      9
+      6
+      5
+      4
+      7
+      3
+      7
+      6
+      1
+      2
+      5
+      5
+      1
+      7
+      6
+      5
+      6
+      7
+      5
+      1
+      3
+      5
+      7
+      5
+      1
+      7
+      8
+      2
+      9
+      6
+      6
+      6
+      4
+      5
+      4
+      7
+      7
+      9
+      1
+      7
+      4
+      5
+      0
+      1
+      1
+      2
+      9
+      9
+      6
+      1
+      4
+      8
+      9
+      0
+      3
+      0
+      4
+      6
+      3
+      9
+      9
+      4
+      7
+      1
+      3
+      2
+      9
+      6
+      2
+      1
+      0
+      7
+      3
+      4
+      0
+      4
+      3
+      7
+      5
+      1
+      8
+      9
+      5
+      7
+      3
+      5
+      9
+      6
+      1
+      4
+      5
+      8
+      9
+      0
+      1
+      9
+      3
+      8
+      9
+      7
+      1
+      3
+      1
+      1
+      1
+      7
+      9
+      0
+      4
+      2
+      9
+      7
+      8
+      2
+      8
+      5
+      6
+      4
+      7
+      5
+      0
+      3
+      2
+      0
+      3
+      1
+      9
+      8
+      6
+      9
+      1
+      5
+      1
+      4
+      0
+      2
+      8
+      7
+      0
+      8
+      0
+      8
+      5
+      9
+      9
+      0
+      4
+      8
+      0
+      1
+      0
+      9
+      4
+      1
+      2
+      1
+      4
+      7
+      2
+      2
+      1
+      3
+      1
+      7
+      9
+      4
+      7
+      6
+      4
+      7
+      7
+      7
+      2
+      6
+      2
+      2
+      4
+      1
+      4
+      2
+      5
+      4
+      8
+      5
+      4
+      5
+      4
+      0
+      3
+      3
+      2
+      1
+      5
+      7
+      1
+      8
+      5
+      3
+      0
+      6
+      1
+      4
+      2
+      2
+      8
+      8
+      1
+      3
+      7
+      5
+      8
+      5
+      0
+      4
+      3
+      0
+      6
+      3
+      3
+      2
+      1
+      7
+      5
+      1
+      8
+      2
+      9
+      7
+      9
+      8
+      6
+      6
+      2
+      2
+      3
+      7
+      1
+      7
+      2
+      1
+      5
+      9
+      1
+      6
+      0
+      7
+      7
+      1
+      6
+      6
+      9
+      2
+      5
+      4
+      7
+      4
+      8
+      7
+      3
+      8
+      9
+      8
+      6
+      6
+      5
+      4
+      9
+      4
+      9
+      4
+      5
+      0
+      1
+      1
+      4
+      6
+      5
+      4
+      0
+      6
+      2
+      8
+      4
+      3
+      3
+      6
+      6
+      3
+      9
+      3
+      7
+      9
+      0
+      0
+      3
+      9
+      7
+      6
+      9
+      2
+      6
+      5
+      6
+      7
+      2
+      1
+      4
+      6
+      3
+      8
+      5
+      3
+      0
+      6
+      7
+      3
+      6
+      0
+      9
+      6
+      5
+      7
+      1
+      2
+      0
+      9
+      1
+      8
+      0
+      7
+      6
+      3
+      8
+      3
+      2
+      7
+      1
+      6
+      6
+      4
+      1
+      6
+      2
+      7
+      4
+      8
+      8
+      8
+      8
+      0
+      0
+      7
+      8
+      6
+      9
+      2
+      5
+      6
+      0
+      2
+      9
+      0
+      2
+      2
+      8
+      4
+      7
+      2
+      1
+      0
+      4
+      0
+      3
+      1
+      7
+      2
+      1
+      1
+      8
+      6
+      0
+      8
+      2
+      0
+      4
+      1
+      9
+      0
+      0
+      0
+      4
+      2
+      2
+      9
+      6
+      6
+      1
+      7
+      1
+      1
+      9
+      6
+      3
+      7
+      7
+      9
+      2
+      1
+      3
+      3
+      7
+      5
+      7
+      5
+      1
+      1
+      4
+      9
+      5
+      9
+      5
+      0
+      1
+      5
+      6
+      6
+      0
+      4
+      9
+      6
+      3
+      1
+      8
+      6
+      2
+      9
+      4
+      7
+      2
+      6
+      5
+      4
+      7
+      3
+      6
+      4
+      2
+      5
+      2
+      3
+      0
+      8
+      1
+      7
+      7
+      0
+      3
+      6
+      7
+      5
+      1
+      5
+      9
+      0
+      6
+      7
+      3
+      5
+      0
+      2
+      3
+      5
+      0
+      7
+      2
+      8
+      3
+      5
+      4
+      0
+      5
+      6
+      7
+      0
+      4
+      0
+      3
+      8
+      6
+      7
+      4
+      3
+      5
+      1
+      3
+      6
+      2
+      2
+      2
+      2
+      4
+      7
+      7
+      1
+      5
+      8
+      9
+      1
+      5
+      0
+      4
+      9
+      5
+      3
+      0
+      9
+      8
+      4
+      4
+      4
+      8
+      9
+      3
+      3
+      3
+      0
+      9
+      6
+      3
+      4
+      0
+      8
+      7
+      8
+      0
+      7
+      6
+      9
+      3
+      2
+      5
+      9
+      9
+      3
+      9
+      7
+      8
+      0
+      5
+      4
+      1
+      9
+      3
+      4
+      1
+      4
+      4
+      7
+      3
+      7
+      7
+      4
+      4
+      1
+      8
+      4
+      2
+      6
+      3
+      1
+      2
+      9
+      8
+      6
+      0
+      8
+      0
+      9
+      9
+      8
+      8
+      8
+      6
+      8
+      7
+      4
+      1
+      3
+      2
+      6
+      0
+      4
+      7
+      2
diff --git a/test-jar.xml b/test-jar.xml
new file mode 100644
index 0000000..81316ee
--- /dev/null
+++ b/test-jar.xml
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+-->
+
+<!--
+   Compiles and runs unit tests against distribution jar(s).  Use .antrc or the 
+   command line to control the jdk used to execute this build file.  
+   
+   Assumes that the distribution jar to be tested is in the base directory. 
+   Use the "jardir" property to specify the path to the directory containing
+   the jar. Any other jars in this directory will also be added to the
+   classpath.  
+   
+   The default target, "test," executes clean as a dependency.
+-->
+  
+<project default="test" name="commons-math" basedir=".">
+  <!-- JDK level -->
+  <property name="compile.source"          value="1.5"/>
+  <property name="compile.target"          value="1.5"/>
+
+  <property name="defaulttargetdir" value="target"/>
+  <property name="testclassesdir" value="target/test-classes"/>
+  <property name="testreportdir" value="target/test-reports"/>
+  <property name="jardir" value="${basedir}"/>
+  <path id="build.classpath">
+    <fileset dir="${jardir}">
+      <include name="*.jar">
+      </include>
+    </fileset>
+  </path>
+
+  <target name="clean" description="o Clean up the generated directories">
+    <delete dir="${defaulttargetdir}"/>
+  </target>
+
+  <target name="init" description="o Initializes some properties">
+    <echo>
+    JAVA ENVIRONMENT
+    **************************Java runtime version*****************************
+    ${java.runtime.version}
+    **************************Java class path**********************************
+    ${java.class.path}
+    **************************Java home****************************************
+    ${java.home}
+     *************************Java library path*******************************
+    Java library path: ${java.library.path}
+    ===========================================================================
+    </echo>
+    <!--Test if JUNIT is present in ANT classpath-->
+    <available property="Junit.present" classname="junit.framework.Test">
+    </available>
+  </target>
+
+  <target name="test" description="o Run the test cases" if="test.failure" depends="internal-test">
+    <fail message="There were test failures.">
+    </fail>
+  </target>
+
+  <target name="internal-test" if="Junit.present" depends="clean,junit-present,compile-tests,test-only"/>
+
+  <target name="test-only">
+    <mkdir dir="${testreportdir}"/>
+    <junit dir="./" failureproperty="test.failure" printSummary="yes" fork="true" haltonerror="false">
+      <sysproperty key="basedir" value="."/>
+      <formatter usefile="true" type="brief"/>
+      <classpath>
+        <path refid="build.classpath"/>
+        <pathelement path="${testclassesdir}"/>
+      </classpath>
+      <batchtest todir="${testreportdir}">
+        <fileset dir="src/test/java">
+          <include name="**/*Test.java"/>
+          <include name="**/*TestBinary.java"/> 
+          <include name="**/*TestPermutations.java"/> 
+          <exclude name="**/*AbstractTest.java"/>
+        </fileset>
+      </batchtest>
+    </junit>
+  </target>
+
+  <target name="junit-present" unless="Junit.present" depends="init">
+  <echo>
+    ================================= WARNING ================================
+    Junit isn't present in your ${ANT_HOME}/lib directory. Tests not executed.
+    ==========================================================================
+  </echo>
+  </target>
+
+  <target name="compile-tests" if="Junit.present" depends="junit-present">
+    <mkdir dir="${testclassesdir}"/>
+    <!--  includeantruntime="true" because JUnit may be provided by Ant -->
+    <javac destdir="${testclassesdir}" 
+            deprecation="false" 
+            debug="true"
+            includeantruntime="true"
+            source="${compile.source}"
+            target="${compile.target}"
+            optimize="false" 
+            excludes="**/package.html">
+      <src>
+        <pathelement location="src/test"/>
+      </src>
+      <classpath>
+        <path refid="build.classpath"/>
+      </classpath>
+    </javac>
+    <copy todir="${testclassesdir}">
+      <fileset dir="src/test/resources">
+        <include name="**/*.xml"/>
+        <include name="**/*.txt"/>
+      </fileset>
+    </copy>
+  </target>
+</project>

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/commons-math.git



More information about the pkg-java-commits mailing list